Merge remote-tracking branch 'origin/master' into feat/web-message-feedback-ui

This commit is contained in:
Chinesezjc
2026-08-12 16:39:15 +08:00
176 changed files with 3088 additions and 458 deletions

View File

@@ -143,8 +143,55 @@ it('accepts pasted images into the composer rail in order and removes them', asy
},
})
const toast = await screen.findByRole('alert')
expect(toast.textContent).toContain('Unsupported image format: text/plain')
expect(toast.textContent).toContain('Only PNG, JPG, WebP, and GIF images are supported')
await waitFor(() => {
expect(screen.queryByRole('alert')).toBeNull()
}, { timeout: 6_000 })
})
it('accepts a whole-page drop under the limits-labeled overlay and refuses an over-limit batch at intake', async () => {
mountAssembledApp()
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)
const textarea = await screen.findByPlaceholderText('Describe what you want to build', {}, { timeout: 10_000 })
// A file drag anywhere over the page raises the full-viewport overlay whose
// desc line carries the projected limits — copy that can only render after
// the imageLimits projection crossed the real fixture transport.
const image = new File([new Uint8Array([137, 80, 78, 71])], 'dropped.png', { type: 'image/png' })
const dataTransfer = { types: ['Files'], files: [image], dropEffect: 'none' }
fireEvent.dragEnter(document.body, { dataTransfer })
const overlay = await screen.findByRole('status')
expect(overlay.textContent).toContain('Drag images here to add them')
await waitFor(() => {
expect(overlay.textContent).toContain('Up to 20 images, 5MB each')
})
// Dropping on the transcript area (not the composer card) lands in the rail.
fireEvent.drop(document.body, { dataTransfer })
await waitFor(() => {
const rail = document.querySelector('[role="group"][aria-label="Pending images"]')
if (rail === null) throw new Error('attachment rail missing after page drop')
expect([...rail.querySelectorAll('img')].map(img => img.getAttribute('alt'))).toEqual(['dropped.png'])
}, { timeout: 5_000 })
expect(screen.queryByRole('status')).toBeNull()
// An intake that would exceed the projected per-message count is refused as
// a whole batch at add time: the banner names the limit and the rail keeps
// only the previously accepted thumbnail — no submit-time rollback.
const batch = Array.from({ length: 20 }, (_, i) =>
new File([new Uint8Array([137, 80, 78, 71])], `bulk-${String(i)}.png`, { type: 'image/png' }))
fireEvent.paste(textarea, {
clipboardData: {
items: batch.map(file => ({ kind: 'file', type: 'image/png', getAsFile: () => file })),
getData: () => '',
},
})
const banner = await screen.findByRole('alert')
expect(banner.textContent).toContain('A message can include up to 20 images')
const rail = document.querySelector('[role="group"][aria-label="Pending images"]')
expect([...(rail?.querySelectorAll('img') ?? [])]).toHaveLength(1)
})