From 53f5588039af4b13c4ba7119c17c16737b3e403d Mon Sep 17 00:00:00 2001 From: chengpeiquan Date: Wed, 1 Apr 2026 23:58:11 +0800 Subject: [PATCH] feat(e2e): expand user-path coverage for ShowMore and MiddleTruncate --- ...026-03-09-e2e-user-path-coverage-design.md | 81 ++++++ ...9-e2e-user-path-coverage-implementation.md | 255 ++++++++++++++++++ .../ControllableMiddleTruncate.tsx | 20 +- .../examples/show-more/BasicShowMore.tsx | 2 +- .../show-more/CustomButtonShowMore.tsx | 19 +- .../examples/show-more/DialogShowMore.tsx | 22 +- .../examples/show-more/TooltipShowMore.tsx | 8 +- e2e/app/App.tsx | 38 +++ e2e/docs/docs-pages.spec.ts | 205 +++++++++++++- e2e/global-setup.mjs | 2 +- e2e/tests/components.spec.ts | 32 ++- playwright.config.ts | 2 +- smoke/consumer/src/App.tsx | 34 +++ smoke/tests/package-smoke.spec.ts | 45 ++++ 14 files changed, 733 insertions(+), 32 deletions(-) create mode 100644 docs/plans/2026-03-09-e2e-user-path-coverage-design.md create mode 100644 docs/plans/2026-03-09-e2e-user-path-coverage-implementation.md diff --git a/docs/plans/2026-03-09-e2e-user-path-coverage-design.md b/docs/plans/2026-03-09-e2e-user-path-coverage-design.md new file mode 100644 index 0000000..336fac5 --- /dev/null +++ b/docs/plans/2026-03-09-e2e-user-path-coverage-design.md @@ -0,0 +1,81 @@ +# E2E User-Path Coverage Design + +**Goal:** Expand end-to-end coverage beyond the recent `ShowMore`-focused docs checks so the project exercises realistic user paths across docs live demos, the local browser harness, and the packed-package smoke consumer in both English and Chinese. + +**Context:** Current docs-page Playwright coverage is intentionally narrow and centered on the newly added `preserveMarkup` behavior in `ShowMore`. That was the right short-term focus, but it leaves gaps around navigation, language switching, `MiddleTruncate`, `Truncate`, custom actions such as dialog and tooltip flows, and consumer-package behavior. The user also reported prior language-specific truncation differences, so English-only checks are not sufficient. + +**Recommended Approach:** Use a layered user-path strategy. Keep docs E2E as the main source of truth for live demos, extend the main `e2e` harness to cover minimal but meaningful browser scenarios for core components, and extend the smoke suite to verify the packed package still behaves correctly in a consumer app. Prefer strong behavioral assertions over snapshots or overly permissive text checks. + +## Options Considered + +### Option A: Expand only docs E2E +- Smallest change surface +- Directly targets live demo regressions + +**Trade-off:** Misses regressions that only appear in the lightweight harness or packed consumer app. + +### Option B: Layered user-path coverage across docs, harness, and smoke +- Covers docs live demos where users actually interact +- Covers minimal core-component behavior outside the docs shell +- Covers real package consumption behavior after packing/installing +- Lets English and Chinese assertions exist at each layer where they add signal + +**Trade-off:** More tests and longer total runtime. + +### Option C: Full component-prop matrix in Playwright +- Broadest theoretical behavioral coverage + +**Trade-off:** High maintenance cost, duplicated assertions, and too much coupling between docs demos and implementation details. Rejected. + +## Design + +### Scope +- Extend docs-page Playwright coverage for: + - home page navigation and language switching + - `ShowMore` live demos in English and Chinese + - `MiddleTruncate` live demos in English and Chinese + - `Truncate` docs page sanity coverage in English and Chinese +- Extend the local browser harness so Playwright can assert bilingual core behavior without relying on the docs shell +- Extend the smoke consumer so packed-package behavior is validated through realistic user paths, including language-sensitive truncation examples + +### Test philosophy +- Assert the intended user-visible behavior, not implementation accidents. +- Do **not** weaken assertions to match incorrect output. +- If current code is wrong, tests must fail and expose it. +- Prefer explicit checks such as: + - collapsed vs. expanded state changes + - presence of `…` + - preserved inline rich-text nodes when `preserveMarkup` is enabled + - stable collapsed height with no extra line regressions + - preserved suffix for middle truncation + - language switch reaching the correct localized route +- Avoid screenshot or pixel-perfect assertions because they are brittle across text rendering differences. + +### Suite layout +- `e2e/docs/docs-pages.spec.ts` + - Organize by page with `test.describe()` blocks + - Split English and Chinese paths explicitly rather than hiding locale in loops that become hard to debug + - Add lightweight helpers for sliders, switches, text extraction, and localized route checks +- `e2e/tests/components.spec.ts` + - Keep the harness focused on minimal core browser behavior + - Add only the scenarios that add signal beyond docs, especially bilingual truncation and width recalculation +- `smoke/tests/package-smoke.spec.ts` + - Keep assertions high-value and consumer-focused + - Validate import success, rendering success, and core interactive behavior in both languages where supported + +### Selector strategy +- Reuse existing `data-testid` attributes wherever possible +- Add targeted new `data-testid` values only when a behavior cannot be asserted reliably through accessible roles or existing structure +- For Radix tooltip assertions, target visible popper content rather than the hidden accessibility helper node + +### Non-goals +- No visual regression snapshots +- No exhaustive prop Cartesian-product matrix +- No production refactor unless a real bug is discovered while writing tests +- No test behavior that “accepts” broken truncation output just to keep CI green + +### Success criteria +- `pnpm test:e2e:docs` passes with broader English and Chinese user-path coverage +- `pnpm test:e2e` passes with stronger bilingual core-harness coverage +- `pnpm test:smoke` passes with stronger consumer-path coverage +- New assertions are strict enough to fail on known classes of incorrect truncation behavior, especially locale-sensitive regressions diff --git a/docs/plans/2026-03-09-e2e-user-path-coverage-implementation.md b/docs/plans/2026-03-09-e2e-user-path-coverage-implementation.md new file mode 100644 index 0000000..393ffc1 --- /dev/null +++ b/docs/plans/2026-03-09-e2e-user-path-coverage-implementation.md @@ -0,0 +1,255 @@ +# E2E User-Path Coverage Implementation Plan + +**Goal:** Implement layered, user-path-focused end-to-end coverage across docs demos, the local component harness, and the packed consumer smoke app in both English and Chinese, without weakening assertions to accommodate incorrect behavior. + +**Architecture:** Use docs-page Playwright as the richest live-demo layer, keep the main harness as a minimal browser-level validation layer for core components, and keep smoke as the final packed-package consumer validation layer. Favor small reusable helpers inside tests and only add new test IDs when existing accessible selectors are insufficient. + +**Tech Stack:** pnpm, Playwright, Astro docs preview, Vite harness app, packed smoke consumer, React 19 + +--- + +### Task 1: Lock in the docs coverage target before changing tests + +**Files:** +- Review: `e2e/docs/docs-pages.spec.ts` +- Review: `docs/src/content/docs/reference/show-more.mdx` +- Review: `docs/src/content/docs/reference/middle-truncate.mdx` +- Review: `docs/src/content/docs/reference/truncate.mdx` + +**Step 1: Write the failing checklist** + +Capture the required docs behaviors: + +- home page language switch works in both directions +- `ShowMore` page covers English and Chinese live demos +- `ShowMore` custom button, dialog, tooltip, and `preserveMarkup` paths are exercised +- `MiddleTruncate` page covers English and Chinese live demos +- `Truncate` page verifies localized content and `preserveMarkup` examples are present + +**Step 2: Compare current tests to the checklist** + +Run: + +```bash +sed -n '1,260p' e2e/docs/docs-pages.spec.ts +``` + +Expected: FAIL the checklist because current coverage is mostly limited to `ShowMore preserveMarkup`. + +**Step 3: Record the selector gaps** + +Identify which interactions are already reachable via roles/text and which require new `data-testid` values. + +**Step 4: Keep the target strict** + +Document the assertions that must remain strict even if they currently fail: + +- no extra collapsed line regressions +- rich text preserved only when the option is enabled +- tooltip and dialog content must visibly appear +- localized routes must actually switch + +--- + +### Task 2: Add failing docs E2E for home and `ShowMore` user paths + +**Files:** +- Modify: `e2e/docs/docs-pages.spec.ts` +- Maybe modify: `docs/src/components/examples/show-more/*.tsx` +- Maybe modify: `docs/src/components/examples/Widgets.tsx` + +**Step 1: Write the failing tests** + +Add docs Playwright tests for: + +- home page English → Chinese switch and Chinese → English switch +- `ShowMore` basic expand/collapse in English and Chinese +- `ShowMore` live demo `custom buttons` flow in English and Chinese +- `ShowMore` dialog demo open/close in English and Chinese +- `ShowMore` tooltip demo visible content in English and Chinese +- existing `preserveMarkup` assertions stay intact and remain strict + +**Step 2: Run only the new docs tests to verify RED** + +Run a focused command such as: + +```bash +pnpm test:e2e:docs --grep "ShowMore|language switch|tooltip|dialog" +``` + +Expected: FAIL for missing selectors or missing coverage wiring, not because of syntax mistakes. + +**Step 3: Add the minimal support needed** + +Only if needed: + +- add stable `data-testid` hooks to the specific docs examples +- add tiny test helpers inside `e2e/docs/docs-pages.spec.ts` +- do not change component behavior unless a genuine bug is revealed + +**Step 4: Re-run the focused docs tests to verify GREEN** + +Run the same focused docs command. + +Expected: PASS. + +--- + +### Task 3: Add failing docs E2E for `MiddleTruncate` and `Truncate` localized paths + +**Files:** +- Modify: `e2e/docs/docs-pages.spec.ts` +- Maybe modify: `docs/src/components/examples/middle-truncate/ControllableMiddleTruncate.tsx` +- Maybe modify: docs components under `docs/src/components/examples/**` + +**Step 1: Write the failing tests** + +Add docs Playwright coverage for: + +- `MiddleTruncate` live demo in English and Chinese +- width slider changes visibly affect truncation +- end-position changes preserve the expected tail behavior +- rich-text toggle keeps the demo functional +- `Truncate` English and Chinese docs routes load the expected localized content and `preserveMarkup` example text + +**Step 2: Run the focused docs tests to verify RED** + +Run: + +```bash +pnpm test:e2e:docs --grep "MiddleTruncate|Truncate" +``` + +Expected: FAIL because the new tests are not yet supported. + +**Step 3: Implement the minimal support** + +- add only the selectors needed for stable assertions +- keep assertions behavioral rather than DOM-shape-dependent +- preserve strict locale-sensitive checks + +**Step 4: Re-run the focused docs tests to verify GREEN** + +Run the same `--grep` command. + +Expected: PASS. + +--- + +### Task 4: Extend the local harness for stronger bilingual core-path coverage + +**Files:** +- Modify: `e2e/app/App.tsx` +- Modify: `e2e/tests/components.spec.ts` + +**Step 1: Write the failing tests** + +Add harness Playwright tests for: + +- English and Chinese `ShowMore` state toggles +- bilingual `preserveMarkup` collapsed-height comparisons where applicable +- bilingual `MiddleTruncate` tail preservation +- width recalculation remaining strict after resizing + +**Step 2: Run the focused harness tests to verify RED** + +Run: + +```bash +pnpm test:e2e --grep "Chinese|English|preserveMarkup|MiddleTruncate|resize" +``` + +Expected: FAIL because the harness does not yet expose every required bilingual assertion surface. + +**Step 3: Add the minimal harness surface** + +- add localized fixture content to `e2e/app/App.tsx` +- add `data-testid` hooks only where role/text selectors are not robust enough +- avoid duplicating docs-only behavior such as full page navigation + +**Step 4: Re-run the focused harness tests to verify GREEN** + +Run the same command. + +Expected: PASS. + +--- + +### Task 5: Extend smoke coverage for packed-package user paths + +**Files:** +- Modify: `smoke/consumer/src/App.tsx` +- Modify: `smoke/tests/package-smoke.spec.ts` + +**Step 1: Write the failing smoke tests** + +Add consumer-path Playwright coverage for: + +- packed package imports still succeed +- English and Chinese truncate examples render and truncate +- `ShowMore` interaction still works with explicit state changes +- `MiddleTruncate` preserves the expected suffix + +**Step 2: Run focused smoke tests to verify RED** + +Run: + +```bash +pnpm test:smoke --grep "packed package|Chinese|ShowMore|MiddleTruncate" +``` + +Expected: FAIL because the consumer app does not yet expose all required localized scenarios. + +**Step 3: Add the minimal consumer fixtures** + +- extend `smoke/consumer/src/App.tsx` with localized fixture examples +- keep the consumer app intentionally small and realistic +- do not recreate the full docs demo shell inside smoke + +**Step 4: Re-run focused smoke tests to verify GREEN** + +Run the same `--grep` command. + +Expected: PASS. + +--- + +### Task 6: Run full verification before claiming completion + +**Files:** +- Verify only + +**Step 1: Run docs E2E** + +```bash +pnpm test:e2e:docs +``` + +Expected: PASS. + +**Step 2: Run harness E2E** + +```bash +pnpm test:e2e +``` + +Expected: PASS. + +**Step 3: Run smoke E2E** + +```bash +pnpm test:smoke +``` + +Expected: PASS. + +**Step 4: Review the final diff** + +Run: + +```bash +git status --short +git diff --stat +``` + +Expected: only docs plans, tests, and minimal test-surface changes are present. diff --git a/docs/src/components/examples/middle-truncate/ControllableMiddleTruncate.tsx b/docs/src/components/examples/middle-truncate/ControllableMiddleTruncate.tsx index 3909199..8a2dad1 100644 --- a/docs/src/components/examples/middle-truncate/ControllableMiddleTruncate.tsx +++ b/docs/src/components/examples/middle-truncate/ControllableMiddleTruncate.tsx @@ -13,6 +13,7 @@ export const ControllableMiddleTruncate: React.FC<{ lang: Languages }> = ({ lang }) => { const { isZh } = useLang(lang) + const testIdPrefix = `docs-middle-truncate-demo-${lang}` const [width, setWidth] = useState(DEFAULT_WIDTH_VALUE) const [end, setEnd] = useState(DEFAULT_END_VALUE) @@ -21,7 +22,7 @@ export const ControllableMiddleTruncate: React.FC<{ const { refreshKey } = useRefreshKey([width, end]) return ( - <> +
- - + + - +
) } diff --git a/docs/src/components/examples/show-more/BasicShowMore.tsx b/docs/src/components/examples/show-more/BasicShowMore.tsx index cac144e..ddc2c97 100644 --- a/docs/src/components/examples/show-more/BasicShowMore.tsx +++ b/docs/src/components/examples/show-more/BasicShowMore.tsx @@ -9,7 +9,7 @@ export const BasicShowMore: React.FC<{ const { isZh } = useLang(lang) return ( - + diff --git a/docs/src/components/examples/show-more/CustomButtonShowMore.tsx b/docs/src/components/examples/show-more/CustomButtonShowMore.tsx index 399715d..c6d5314 100644 --- a/docs/src/components/examples/show-more/CustomButtonShowMore.tsx +++ b/docs/src/components/examples/show-more/CustomButtonShowMore.tsx @@ -11,8 +11,9 @@ import { type Languages, useLang } from '@/i18n' const IconButton: React.FC<{ type: 'more' | 'less' + isZh: boolean onClick: ShowMoreToggleLinesFn -}> = ({ type, onClick }) => { +}> = ({ type, isZh, onClick }) => { const Icon = useMemo(() => { if (type === 'less') { return ChevronUp @@ -24,6 +25,14 @@ const IconButton: React.FC<{ return type === 'more' ? : null }, [type]) + const label = useMemo(() => { + if (isZh) { + return type === 'more' ? '展开' : '收起' + } + + return type === 'more' ? 'Show More' : 'Show Less' + }, [isZh, type]) + return ( <> {prefix} @@ -33,6 +42,8 @@ const IconButton: React.FC<{ variant="outline" size="icon-sm" onClick={onClick} + aria-label={label} + title={label} > @@ -52,13 +63,13 @@ export const CustomButtonShowMore: React.FC<{ } return ( - + } - less={} + more={} + less={} > diff --git a/docs/src/components/examples/show-more/DialogShowMore.tsx b/docs/src/components/examples/show-more/DialogShowMore.tsx index 342dee4..2d27994 100644 --- a/docs/src/components/examples/show-more/DialogShowMore.tsx +++ b/docs/src/components/examples/show-more/DialogShowMore.tsx @@ -13,7 +13,14 @@ import { } from '@/components/ui' import { type Languages, getTranslation, useLang } from '@/i18n' -const ExpandButton: React.FC = () => { +const ExpandButton: React.FC<{ + isZh: boolean + testId: string +}> = ({ isZh, testId }) => { + const label = useMemo(() => { + return isZh ? '展开完整内容' : 'Expand full content' + }, [isZh]) + return ( @@ -23,6 +30,9 @@ const ExpandButton: React.FC = () => { className="ml-2 cursor-pointer" variant="outline" size="icon-sm" + data-testid={testId} + aria-label={label} + title={label} > @@ -35,6 +45,7 @@ export const DialogShowMore: React.FC<{ lang: Languages }> = ({ lang }) => { const { isZh } = useLang(lang) + const testIdPrefix = `docs-dialog-show-more-${lang}` const dialogTitle = useMemo(() => { return getTranslation(lang, 'example.dialogTitle') @@ -46,13 +57,16 @@ export const DialogShowMore: React.FC<{ return ( - - }> + + } + > {content} - + {dialogTitle} {content} diff --git a/docs/src/components/examples/show-more/TooltipShowMore.tsx b/docs/src/components/examples/show-more/TooltipShowMore.tsx index cc6d61e..b655029 100644 --- a/docs/src/components/examples/show-more/TooltipShowMore.tsx +++ b/docs/src/components/examples/show-more/TooltipShowMore.tsx @@ -20,18 +20,22 @@ export const TooltipShowMore: React.FC<{ return }, [isZh]) + const testIdPrefix = useMemo(() => { + return `docs-tooltip-show-more-${lang}` + }, [lang]) + return ( - + {content} - +
{content}
diff --git a/e2e/app/App.tsx b/e2e/app/App.tsx index 7602d47..ffb1b08 100644 --- a/e2e/app/App.tsx +++ b/e2e/app/App.tsx @@ -5,7 +5,10 @@ const LONG_TEXT = 'This sentence keeps going so the component must clamp it safely. This extra copy ensures the browser truncates it in a narrow box.' const SHOW_MORE_TEXT = 'ShowMore should reveal the hidden text after interaction and allow collapsing again without leaving the browser page.' +const ZH_SHOW_MORE_TEXT = + '显示更多组件在中文内容下也应该能正确展开和收起,并且不应该因为没有空格分词而出现异常的裁剪结果。为了稳定触发折叠状态,这里补充一段更长的中文文本。' const FILE_NAME = 'Quarterly-operating-report-final-reviewed-version-2026.pdf' +const ZH_FILE_NAME = '客户合同归档最终审核版本-2026.pdf' const RESIZE_TEXT = 'Resizing the container should force truncation to recalculate and produce a shorter visible result in the narrow state.' const INLINE_CHINESE_RICH_TEXT = ( @@ -81,6 +84,9 @@ export const App: React.FC = () => { const [showMoreState, setShowMoreState] = useState<'collapsed' | 'expanded'>( 'collapsed', ) + const [zhShowMoreState, setZhShowMoreState] = useState< + 'collapsed' | 'expanded' + >('collapsed') const [resizeWidth, setResizeWidth] = useState(240) useEffect(() => { @@ -139,6 +145,26 @@ export const App: React.FC = () => { +
+

Chinese ShowMore

+
+
{zhShowMoreState}
+
+ + setZhShowMoreState(expanded ? 'expanded' : 'collapsed') + } + > + {ZH_SHOW_MORE_TEXT} + +
+
+
+

MiddleTruncate

{
+
+

Chinese MiddleTruncate

+
+ + {ZH_FILE_NAME} + +
+
+

Resize

diff --git a/e2e/docs/docs-pages.spec.ts b/e2e/docs/docs-pages.spec.ts index af5e9f3..7aaaa62 100644 --- a/e2e/docs/docs-pages.spec.ts +++ b/e2e/docs/docs-pages.spec.ts @@ -3,7 +3,12 @@ import { expect, test } from '@playwright/test' const setRangeValue = async (page, testId: string, value: number) => { await page.getByTestId(testId).evaluate((node, nextValue) => { const input = node as HTMLInputElement - input.value = String(nextValue) + const descriptor = Object.getOwnPropertyDescriptor( + HTMLInputElement.prototype, + 'value', + ) + + descriptor?.set?.call(input, String(nextValue)) input.dispatchEvent(new Event('input', { bubbles: true })) input.dispatchEvent(new Event('change', { bubbles: true })) }, value) @@ -17,12 +22,173 @@ const setCheckbox = async (page, testId: string, checked: boolean) => { } } +const getVisibleByTestId = (page, testId: string) => { + return page.locator(`[data-testid="${testId}"]:visible`).first() +} + const getContentHeight = (page, testId: string) => { - return page.getByTestId(testId).evaluate((node) => { + return getVisibleByTestId(page, testId).evaluate((node) => { return node.getBoundingClientRect().height }) } +const hoverVisibleTooltip = async (page, triggerTestId: string) => { + await page.getByTestId(triggerTestId).hover() + + const visibleTooltip = page.getByTestId(`${triggerTestId}-content`) + await visibleTooltip.waitFor({ state: 'visible' }) + + return visibleTooltip +} + +const docsLocales = [ + { + lang: 'en', + prefix: '', + showMoreTitle: 'ShowMore', + middleTitle: 'MiddleTruncate', + truncateTitle: 'Truncate', + moreLabel: /(?:Expand|Show More)/, + lessLabel: /(?:Collapse|Show Less)/, + dialogButtonName: 'Expand full content', + dialogTitle: 'Here is the full content', + tooltipText: /Hover the mouse over the text to view the full text\./, + preserveMarkupText: + /If you need the collapsed state to keep rendered inline markup/, + }, + { + lang: 'zh', + prefix: '/zh', + showMoreTitle: 'ShowMore', + middleTitle: 'MiddleTruncate', + truncateTitle: 'Truncate', + moreLabel: /(?:展开|Expand|Show More)/, + lessLabel: /(?:收起|Collapse|Show Less)/, + dialogButtonName: '展开完整内容', + dialogTitle: '这里是完整的内容', + tooltipText: /将鼠标悬停在这段文字上面以查看全文。/, + preserveMarkupText: /如果希望折叠态尽量保留已经渲染出来的内联富文本结构/, + }, +] as const + +test('docs home switches between English and Chinese', async ({ page }) => { + await page.goto('/') + await expect( + page.getByRole('heading', { name: 'React Truncate' }), + ).toBeVisible() + + const languageSelect = page.locator('starlight-lang-select select') + + await languageSelect.selectOption('/zh/') + await expect(page).toHaveURL(/\/zh\/$/) + + await languageSelect.selectOption('/') + await expect(page).toHaveURL(/\/$/) +}) + +for (const locale of docsLocales) { + test(`docs show-more basic demo toggles in ${locale.lang}`, async ({ + page, + }) => { + await page.goto(`${locale.prefix}/reference/show-more/`) + await expect( + page.getByRole('heading', { name: locale.showMoreTitle }), + ).toBeVisible() + + const basicDemo = page.getByTestId(`docs-basic-show-more-${locale.lang}`) + + await basicDemo.getByRole('link', { name: locale.moreLabel }).click() + const collapseLink = basicDemo.getByRole('link', { name: locale.lessLabel }) + + await expect(collapseLink).toBeVisible() + await collapseLink.click() + await expect( + basicDemo.getByRole('link', { name: locale.moreLabel }), + ).toBeVisible() + }) + + test(`docs show-more custom button demo toggles in ${locale.lang}`, async ({ + page, + }) => { + await page.goto(`${locale.prefix}/reference/show-more/`) + + const customDemo = page.getByTestId(`docs-custom-show-more-${locale.lang}`) + + await customDemo.getByRole('button', { name: locale.moreLabel }).click() + await expect( + customDemo.getByRole('button', { name: locale.lessLabel }), + ).toBeVisible() + }) + + test(`docs show-more dialog demo opens in ${locale.lang}`, async ({ + page, + }) => { + await page.goto(`${locale.prefix}/reference/show-more/`) + + await page.getByRole('button', { name: locale.dialogButtonName }).click() + + const dialog = page.getByTestId( + `docs-dialog-show-more-${locale.lang}-content`, + ) + await expect(dialog).toBeVisible() + await expect(dialog).toContainText(locale.dialogTitle) + + await page.keyboard.press('Escape') + await expect(dialog).toBeHidden() + }) + + test(`docs show-more tooltip demo reveals full text in ${locale.lang}`, async ({ + page, + }) => { + await page.goto(`${locale.prefix}/reference/show-more/`) + await expect(page.getByText(locale.tooltipText)).toBeVisible() + + const tooltip = await hoverVisibleTooltip( + page, + `docs-tooltip-show-more-${locale.lang}`, + ) + await expect(tooltip).toContainText(/Lorem ipsum|从前有座山/) + }) + + test(`docs middle-truncate live demo responds to controls in ${locale.lang}`, async ({ + page, + }) => { + await page.goto(`${locale.prefix}/reference/middle-truncate/`) + await expect( + page.getByRole('heading', { name: locale.middleTitle }), + ).toBeVisible() + + const testIdPrefix = `docs-middle-truncate-demo-${locale.lang}` + const content = getVisibleByTestId(page, `${testIdPrefix}-content`) + const container = page.getByTestId(`${testIdPrefix}-container`) + + const initialText = await content.innerText() + expect(initialText).toContain('…') + expect(initialText.endsWith('…')).toBeFalsy() + + await setRangeValue(page, `${testIdPrefix}-width`, 60) + await setRangeValue(page, `${testIdPrefix}-end`, 12) + + await expect(page.getByTestId(`${testIdPrefix}-width`)).toHaveValue('60') + await expect(page.getByTestId(`${testIdPrefix}-end`)).toHaveValue('12') + await expect(container).toHaveAttribute('style', /width: 60%/) + + const adjustedText = await content.innerText() + expect(adjustedText).toContain('…') + expect(adjustedText.endsWith('…')).toBeFalsy() + }) + + test(`docs truncate page exposes localized preserveMarkup guidance in ${locale.lang}`, async ({ + page, + }) => { + await page.goto(`${locale.prefix}/reference/truncate/`) + await expect( + page.getByRole('heading', { name: locale.truncateTitle }), + ).toBeVisible() + await expect(page.getByText(locale.preserveMarkupText)).toBeVisible() + }) +} + test('docs show-more demo preserves inline markup without extra lines in English', async ({ page, }) => { @@ -39,9 +205,9 @@ test('docs show-more demo preserves inline markup without extra lines in English 'docs-show-more-demo-en-content', ) await expect( - page - .getByTestId('docs-show-more-demo-en-content') - .locator('[data-testid="docs-inline-rich-link-en"]'), + getVisibleByTestId(page, 'docs-show-more-demo-en-content').locator( + '[data-testid="docs-inline-rich-link-en"]', + ), ).toHaveCount(0) await setCheckbox(page, 'docs-show-more-demo-en-preserve-markup', true) @@ -54,14 +220,14 @@ test('docs show-more demo preserves inline markup without extra lines in English expect(markupHeight).toBeLessThanOrEqual(plainHeight) await expect( - page - .getByTestId('docs-show-more-demo-en-content') - .locator('[data-testid="docs-inline-rich-link-en"]'), + getVisibleByTestId(page, 'docs-show-more-demo-en-content').locator( + '[data-testid="docs-inline-rich-link-en"]', + ), ).toHaveCount(1) await expect( - page - .getByTestId('docs-show-more-demo-en-content') - .locator('[data-testid="docs-inline-rich-accent-en"]'), + getVisibleByTestId(page, 'docs-show-more-demo-en-content').locator( + '[data-testid="docs-inline-rich-accent-en"]', + ), ).toHaveCount(1) const demo = page.getByTestId('docs-show-more-demo-en') @@ -86,9 +252,9 @@ test('docs show-more demo preserves inline markup without extra lines in Chinese 'docs-show-more-demo-zh-content', ) await expect( - page - .getByTestId('docs-show-more-demo-zh-content') - .locator('[data-testid="docs-inline-rich-link-zh"]'), + getVisibleByTestId(page, 'docs-show-more-demo-zh-content').locator( + '[data-testid="docs-inline-rich-link-zh"]', + ), ).toHaveCount(0) await setCheckbox(page, 'docs-show-more-demo-zh-preserve-markup', true) @@ -100,6 +266,17 @@ test('docs show-more demo preserves inline markup without extra lines in Chinese expect(markupHeight).toBeLessThanOrEqual(plainHeight) + await expect( + getVisibleByTestId(page, 'docs-show-more-demo-zh-content').locator( + '[data-testid="docs-inline-rich-link-zh"]', + ), + ).toHaveCount(1) + await expect( + getVisibleByTestId(page, 'docs-show-more-demo-zh-content').locator( + '[data-testid="docs-inline-rich-accent-zh"]', + ), + ).toHaveCount(1) + const demo = page.getByTestId('docs-show-more-demo-zh') await demo.getByRole('link', { name: /(?:展开|Expand)/ }).click() diff --git a/e2e/global-setup.mjs b/e2e/global-setup.mjs index a90a2bf..8fb093f 100644 --- a/e2e/global-setup.mjs +++ b/e2e/global-setup.mjs @@ -3,7 +3,7 @@ import { readFileSync, rmSync, writeFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' -const PORT = 4173 +const PORT = 4176 const PID_FILE = '/tmp/react-truncate-e2e-vite.pid' const __dirname = path.dirname(fileURLToPath(import.meta.url)) diff --git a/e2e/tests/components.spec.ts b/e2e/tests/components.spec.ts index c693053..126a1c2 100644 --- a/e2e/tests/components.spec.ts +++ b/e2e/tests/components.spec.ts @@ -30,13 +30,31 @@ test('show-more expands and collapses', async ({ page }) => { showMoreExample.getByRole('link', { name: 'Collapse' }), ).toBeVisible() - const expandedText = await page.getByTestId('show-more-example').innerText() + const expandedText = await showMoreExample.innerText() expect(expandedText.length).toBeGreaterThan(collapsedText.length) await showMoreExample.getByRole('link', { name: 'Collapse' }).click() await expect(page.getByTestId('show-more-state')).toHaveText('collapsed') }) +test('zh show-more expands and collapses', async ({ page }) => { + await page.goto('/') + + await expect(page.getByTestId('zh-show-more-state')).toHaveText('collapsed') + const showMoreExample = page.getByTestId('zh-show-more-example') + const collapsedText = await showMoreExample.innerText() + + await showMoreExample.getByRole('link', { name: '展开' }).click() + + await expect(page.getByTestId('zh-show-more-state')).toHaveText('expanded') + await expect( + showMoreExample.getByRole('link', { name: '收起' }), + ).toBeVisible() + + const expandedText = await showMoreExample.innerText() + expect(expandedText.length).toBeGreaterThan(collapsedText.length) +}) + test('middle-truncate preserves suffix', async ({ page }) => { await page.goto('/') @@ -49,6 +67,18 @@ test('middle-truncate preserves suffix', async ({ page }) => { expect(text.endsWith('.pdf')).toBeTruthy() }) +test('zh middle-truncate preserves suffix', async ({ page }) => { + await page.goto('/') + + const middleExample = page.getByTestId('zh-middle-example') + await expect(middleExample).toContainText('.pdf') + + const text = await middleExample.innerText() + + expect(text).toContain('…') + expect(text.endsWith('.pdf')).toBeTruthy() +}) + test('truncate recalculates after resize controls change width', async ({ page, }) => { diff --git a/playwright.config.ts b/playwright.config.ts index ce2588b..639b154 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ globalSetup: './e2e/global-setup.mjs', globalTeardown: './e2e/global-teardown.mjs', use: { - baseURL: 'http://127.0.0.1:4173', + baseURL: 'http://127.0.0.1:4176', trace: 'on-first-retry', }, }) diff --git a/smoke/consumer/src/App.tsx b/smoke/consumer/src/App.tsx index 90f0e49..99370d4 100644 --- a/smoke/consumer/src/App.tsx +++ b/smoke/consumer/src/App.tsx @@ -5,7 +5,10 @@ const TRUNCATE_TEXT = 'The packed package should still truncate text correctly when consumed by another app.' const SHOW_MORE_TEXT = 'The packed package should also keep interactive expand and collapse behavior in a consumer project.' +const ZH_SHOW_MORE_TEXT = + '打包后的组件在消费者项目中也应该能正确处理中文内容的展开与收起,并且在没有空格分词的情况下保持稳定的裁剪行为。' const MIDDLE_TEXT = 'customer-contract-archive-reference-2026.pdf' +const ZH_MIDDLE_TEXT = '客户合同归档最终审核版本-2026.pdf' const imported = Boolean(Truncate && ShowMore && MiddleTruncate) @@ -14,6 +17,9 @@ export const App: React.FC = () => { const [showMoreState, setShowMoreState] = useState<'collapsed' | 'expanded'>( 'collapsed', ) + const [zhShowMoreState, setZhShowMoreState] = useState< + 'collapsed' | 'expanded' + >('collapsed') useEffect(() => { setReady(true) @@ -56,12 +62,40 @@ export const App: React.FC = () => {
+
+
{zhShowMoreState}
+
+ + setZhShowMoreState(expanded ? 'expanded' : 'collapsed') + } + > + {ZH_SHOW_MORE_TEXT} + +
+
+
{MIDDLE_TEXT}
+ +
+ + {ZH_MIDDLE_TEXT} + +
) } diff --git a/smoke/tests/package-smoke.spec.ts b/smoke/tests/package-smoke.spec.ts index d85ed45..4b3de52 100644 --- a/smoke/tests/package-smoke.spec.ts +++ b/smoke/tests/package-smoke.spec.ts @@ -20,3 +20,48 @@ test('packed package show-more interaction works', async ({ page }) => { await expect(page.getByTestId('smoke-show-more-state')).toHaveText('expanded') await expect(page.getByRole('link', { name: 'Collapse' })).toBeVisible() }) + +test('packed package zh show-more interaction works', async ({ page }) => { + await page.goto('/') + + await expect(page.getByTestId('smoke-zh-show-more-state')).toHaveText( + 'collapsed', + ) + + const example = page.getByTestId('smoke-zh-show-more-example') + const collapsedText = await example.innerText() + + await example.getByRole('link', { name: '展开' }).click() + + await expect(page.getByTestId('smoke-zh-show-more-state')).toHaveText( + 'expanded', + ) + await expect(example.getByRole('link', { name: '收起' })).toBeVisible() + + const expandedText = await example.innerText() + expect(expandedText.length).toBeGreaterThan(collapsedText.length) +}) + +test('packed package middle-truncate preserves suffix', async ({ page }) => { + await page.goto('/') + + const middleExample = page.getByTestId('smoke-middle-example') + await expect(middleExample).toContainText('.pdf') + + const text = await middleExample.innerText() + + expect(text).toContain('…') + expect(text.endsWith('.pdf')).toBeTruthy() +}) + +test('packed package zh middle-truncate preserves suffix', async ({ page }) => { + await page.goto('/') + + const middleExample = page.getByTestId('smoke-zh-middle-example') + await expect(middleExample).toContainText('.pdf') + + const text = await middleExample.innerText() + + expect(text).toContain('…') + expect(text.endsWith('.pdf')).toBeTruthy() +})