fix(web): keep partner CTA 12px from title (ECO-472)

This commit is contained in:
CodingOnStar 2026-08-28 20:40:59 +08:00
parent d9d510e5ac
commit d8f30076b5
3 changed files with 68 additions and 12 deletions

View File

@ -4,17 +4,24 @@ import { page } from 'vite-plus/test/browser'
import { render } from 'vitest-browser-react'
import ListWithCollection from '../list-with-collection'
const mockState = vi.hoisted(() => ({
becomePartnerText: 'Become a Partner',
}))
vi.mock('#i18n', async () => {
const { withSelectorKey } = await import('@/test/i18n-mock')
const translations: Record<string, string> = {
'marketplace.becomePartner': 'Become a Partner',
'marketplace.carousel.scrollPrevious': 'Previous',
}
return {
useLocale: () => 'en-US',
useTranslation: () => ({
t: withSelectorKey((key: string) => translations[key] ?? key),
t: withSelectorKey((key: string) =>
key === 'marketplace.becomePartner'
? mockState.becomePartnerText
: (translations[key] ?? key),
),
}),
}
})
@ -54,15 +61,17 @@ const partnerPlugins = Array.from({ length: 9 }, (_, index) => ({
const renderPartnerCollection = ({
pluginCount = 9,
standalone = true,
width = 350,
}: {
pluginCount?: number
standalone?: boolean
width?: number
} = {}) =>
render(
<div
data-testid="collection-shell"
data-marketplace-standalone={standalone || undefined}
style={{ width: 350 }}
style={{ width }}
>
<ListWithCollection
marketplaceCollections={[partnerCollection]}
@ -71,7 +80,17 @@ const renderPartnerCollection = ({
</div>,
)
const getTextRect = (element: Element) => {
const range = document.createRange()
range.selectNodeContents(element)
return range.getBoundingClientRect()
}
describe('Partner collection header layout', () => {
beforeEach(() => {
mockState.becomePartnerText = 'Become a Partner'
})
it('keeps the mobile call to action beside the title and clear of carousel controls', async () => {
await page.viewport(390, 844)
const screen = await renderPartnerCollection()
@ -82,7 +101,7 @@ describe('Partner collection header layout', () => {
const partnerLink = screen.getByRole('link', { name: 'Become a Partner' }).element()
const previousButton = screen.getByRole('button', { name: 'Previous' }).element()
const titleRect = title.getBoundingClientRect()
const titleRect = getTextRect(title)
const descriptionRect = description.getBoundingClientRect()
const partnerLinkRect = partnerLink.getBoundingClientRect()
const previousButtonRect = previousButton.getBoundingClientRect()
@ -91,29 +110,49 @@ describe('Partner collection header layout', () => {
const partnerLinkCenter = partnerLinkRect.top + partnerLinkRect.height / 2
expect(Math.abs(titleCenter - partnerLinkCenter)).toBeLessThanOrEqual(2)
expect(partnerLinkRect.left).toBeGreaterThan(titleRect.right)
expect(partnerLinkRect.left - titleRect.right).toBeCloseTo(12, 0)
expect(previousButtonRect.left - partnerLinkRect.right).toBeGreaterThanOrEqual(8)
expect(previousButtonRect.left - partnerLinkRect.right).toBeLessThanOrEqual(16)
expect(descriptionRect.top).toBeGreaterThanOrEqual(
Math.max(titleRect.bottom, partnerLinkRect.bottom),
)
expect(getComputedStyle(separator).display).toBe('none')
})
it('right-aligns the mobile action without reserving space when navigation is absent', async () => {
it('keeps the mobile action 12px from the title when navigation is absent', async () => {
await page.viewport(390, 844)
const screen = await renderPartnerCollection({ pluginCount: 2 })
const shellRect = screen.getByTestId('collection-shell').element().getBoundingClientRect()
const titleRect = getTextRect(screen.getByText('Partners', { exact: true }).element())
const partnerLinkRect = screen
.getByRole('link', { name: 'Become a Partner' })
.element()
.getBoundingClientRect()
expect(shellRect.right - partnerLinkRect.right).toBeCloseTo(0)
expect(partnerLinkRect.left - titleRect.right).toBeCloseTo(12, 0)
expect(partnerLinkRect.right).toBeLessThanOrEqual(shellRect.right)
expect(screen.getByRole('button', { name: 'Previous' }).query()).toBeNull()
})
it('keeps the mobile action clear of navigation at a 320px viewport', async () => {
await page.viewport(320, 844)
mockState.becomePartnerText = 'Torne-se um parceiro'
const screen = await renderPartnerCollection({ width: 280 })
const titleRect = getTextRect(screen.getByText('Partners', { exact: true }).element())
const partnerLinkRect = screen
.getByRole('link', { name: 'Torne-se um parceiro' })
.element()
.getBoundingClientRect()
const previousButtonRect = screen
.getByRole('button', { name: 'Previous' })
.element()
.getBoundingClientRect()
expect(partnerLinkRect.left - titleRect.right).toBeCloseTo(12, 0)
expect(previousButtonRect.left - partnerLinkRect.right).toBeGreaterThanOrEqual(8)
})
it('preserves the narrow embedded metadata row', async () => {
await page.viewport(390, 844)
const screen = await renderPartnerCollection({ standalone: false })

View File

@ -6,7 +6,7 @@
grid-template-areas:
'title action'
'description description';
grid-template-columns: minmax(0, 1fr) max-content;
grid-template-columns: max-content minmax(0, 1fr);
align-items: center;
column-gap: 12px;
}
@ -34,7 +34,19 @@
:global([data-marketplace-standalone]) .partnerAction {
grid-area: action;
justify-self: end;
justify-self: start;
min-width: 0;
max-width: 100%;
white-space: nowrap;
}
:global([data-marketplace-standalone]) .partnerActionLabel {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
:global([data-marketplace-standalone]) .partnerActionIcon {
flex-shrink: 0;
}
}

View File

@ -227,8 +227,13 @@ const CollectionSection = ({
})
}}
>
<span>{t(($) => $['marketplace.becomePartner'], { ns: 'plugin' })}</span>
<span aria-hidden className="i-ri-external-link-line size-3" />
<span className={styles.partnerActionLabel}>
{t(($) => $['marketplace.becomePartner'], { ns: 'plugin' })}
</span>
<span
aria-hidden
className={cn(styles.partnerActionIcon, 'i-ri-external-link-line size-3')}
/>
</a>
</>
)}