Kaynağa Gözat

test(web): pin the assembled snapshot lane's locale through the browser

The assembled Web snapshot lane selected its locale with a `dsh.locale`
localStorage key. That key stopped selecting anything once the locale
preference moved to the Host settings document, so the image-display
scenario's Chinese expectations met the English default and failed.

Pin the navigator languages the boot env already documents, and state the
image-display expectations in the lane's English copy — the fixture session
title stays Chinese because it is fixture data, not product copy.
Yichen Jiang 1 ay önce
ebeveyn
işleme
76feeece55

+ 11 - 1
apps/web/tests/assembled-boot.ts

@@ -70,7 +70,12 @@ let unmount: (() => void) | undefined
 export function installAssembledBootEnv(): void {
   beforeEach(() => {
     localStorage.clear()
-    localStorage.setItem('dsh.locale', 'en')
+    // The locale service derives its provisional locale from the browser and
+    // takes an explicit choice only from Host settings, which this lane's
+    // fixture transport does not serve; pinning the navigator is what selects
+    // English here.
+    Object.defineProperty(navigator, 'languages', { value: ['en-US'], configurable: true })
+    Object.defineProperty(navigator, 'language', { value: 'en-US', configurable: true })
     document.title = 'DeepSeek Harness'
     vi.stubGlobal('ResizeObserver', ResizeObserverStub)
     vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) =>
@@ -88,6 +93,11 @@ export function installAssembledBootEnv(): void {
     document.head.querySelectorAll('style[data-plugin]').forEach((style) => { style.remove() })
     document.title = ''
     history.replaceState(null, '', '/')
+    // Deleting the own properties uncovers jsdom's own accessors again
+    // (Navigator declares both readonly, hence the erased receiver).
+    const ownNavigator = navigator as unknown as Record<string, unknown>
+    delete ownNavigator.languages
+    delete ownNavigator.language
     vi.unstubAllGlobals()
   })
 }

+ 8 - 10
apps/web/tests/image-display.snapshot.ts

@@ -14,7 +14,7 @@ installAssembledBootEnv()
 
 /** Open the fixture history session (the alpha log carrying the turn-72 image pair) and wait for its gallery. */
 async function openFixtureSession(): Promise<void> {
-  const tree = await screen.findByRole('tree', { name: '会话' }, { timeout: 10_000 })
+  const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 })
   const group = (await within(tree).findAllByText('fixture'))
     .map(el => el.closest<HTMLElement>('[role="treeitem"]'))
     .find(el => el?.getAttribute('aria-expanded') !== null)
@@ -33,7 +33,6 @@ async function openFixtureSession(): Promise<void> {
 }
 
 it('renders the history image pair through the authorized attachment route and opens the lightbox', async () => {
-  localStorage.setItem('dsh.locale', 'zh')
   mountAssembledApp()
   await openFixtureSession()
 
@@ -73,24 +72,23 @@ it('renders the history image pair through the authorized attachment route and o
   fireEvent.doubleClick(frame)
   const lightbox = await screen.findByRole('dialog')
   expect(within(lightbox).getByRole('img').getAttribute('src')?.split(':')[0]).toBe('blob')
-  fireEvent.click(within(lightbox).getByRole('button', { name: /关闭/ }))
+  fireEvent.click(within(lightbox).getByRole('button', { name: /Close/ }))
   await waitFor(() => {
     expect(screen.queryByRole('dialog')).toBeNull()
   })
 })
 
 it('accepts pasted images into the composer rail in order and removes them', async () => {
-  localStorage.setItem('dsh.locale', 'zh')
   mountAssembledApp()
 
-  const tree = await screen.findByRole('tree', { name: '会话' }, { timeout: 10_000 })
-  const start = tree.querySelector<HTMLButtonElement>('button[aria-label="在“fixture”中新建会话"]')
+  const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 })
+  const start = tree.querySelector<HTMLButtonElement>('button[aria-label="New session in fixture"]')
   if (start === null) throw new Error('fixture Workspace new-session action missing')
   fireEvent.click(start)
 
   // Image-only send arming is pinned at package level (input-bar.spec.tsx);
   // this assembled lane pins the intake chain over the built graph.
-  const textarea = await screen.findByPlaceholderText('描述你想要构建的内容', {}, { timeout: 10_000 })
+  const textarea = await screen.findByPlaceholderText('Describe what you want to build', {}, { timeout: 10_000 })
   const image = new File([new Uint8Array([137, 80, 78, 71])], 'pasted.png', { type: 'image/png' })
   fireEvent.paste(textarea, {
     clipboardData: {
@@ -102,7 +100,7 @@ it('accepts pasted images into the composer rail in order and removes them', asy
   // The rail is an accessible group holding the draft thumbnail (queried via
   // DOM: jsdom's a11y-visibility computation hides the composer subtree).
   const rail = await waitFor(() => {
-    const el = document.querySelector('[role="group"][aria-label="待发送图片"]')
+    const el = document.querySelector('[role="group"][aria-label="Pending images"]')
     if (el === null) throw new Error('attachment rail missing')
     return el
   }, { timeout: 5_000 })
@@ -129,10 +127,10 @@ it('accepts pasted images into the composer rail in order and removes them', asy
       .toEqual(['pasted.png', 'second.png'])
   })
 
-  const remove = [...rail.querySelectorAll('button[aria-label^="移除图片"]')]
+  const remove = [...rail.querySelectorAll('button[aria-label^="Remove image"]')]
   if (remove.length !== 2) throw new Error('remove buttons missing')
   for (const button of remove) fireEvent.click(button)
   await waitFor(() => {
-    expect(document.querySelector('[role="group"][aria-label="待发送图片"]')).toBeNull()
+    expect(document.querySelector('[role="group"][aria-label="Pending images"]')).toBeNull()
   })
 })