feat(web): whole-page image drop, projected intake limits, and thumbnail tiling
Second alignment step for #2248: document-level drag intake behind the new DropOverlay atom, lightbox close icon and shared dialog mask, DeepSeek Chat thumbnail rules (single 240px long edge with ratio clamp, 64px tiles, merged consecutive assistant images), image limits raised to 20/10MiB/100MiB and published to clients as the imageLimits projection, whole-batch intake pre-check with product-copy banners, and attachment-error reasons mapped to localized copy.
This commit is contained in:
@@ -29,7 +29,7 @@ const attachment = {
|
||||
describe('MessageImage', () => {
|
||||
it('loads a session-authorized URL, bounds the thumbnail, and clicks into the original', async () => {
|
||||
const load = vi.fn().mockResolvedValue('blob:history')
|
||||
const view = render(<MessageImage attachment={attachment} load={load} labels={labels} />)
|
||||
const view = render(<MessageImage attachment={attachment} load={load} variant="single" labels={labels} />)
|
||||
const frame = view.getByRole('button', { name: 'history.png,点击查看原图' })
|
||||
expect(frame.getAttribute('style')).toContain('width: 240px')
|
||||
expect(frame.getAttribute('style')).toContain('height: 120px')
|
||||
@@ -44,7 +44,7 @@ describe('MessageImage', () => {
|
||||
|
||||
it('ignores a click while the thumbnail is still loading', () => {
|
||||
const load = vi.fn(() => new Promise<string>(() => {}))
|
||||
const view = render(<MessageImage attachment={attachment} load={load} labels={labels} />)
|
||||
const view = render(<MessageImage attachment={attachment} load={load} variant="single" labels={labels} />)
|
||||
const frame = view.getByRole('button', { name: 'history.png,点击查看原图' })
|
||||
expect(view.getByText('图片加载中…')).toBeTruthy()
|
||||
fireEvent.click(frame)
|
||||
@@ -54,7 +54,7 @@ describe('MessageImage', () => {
|
||||
it('falls back to the image label for an unnamed attachment', async () => {
|
||||
const { name: _named, ...unnamed } = attachment
|
||||
const load = vi.fn().mockResolvedValue('blob:unnamed')
|
||||
const view = render(<MessageImage attachment={unnamed} load={load} labels={labels} />)
|
||||
const view = render(<MessageImage attachment={unnamed} load={load} variant="single" labels={labels} />)
|
||||
await waitFor(() => { expect(view.getByAltText('图片')).toBeTruthy() })
|
||||
expect(view.getByRole('button', { name: '图片,点击查看原图' })).toBeTruthy()
|
||||
})
|
||||
@@ -64,7 +64,7 @@ describe('MessageImage', () => {
|
||||
.mockRejectedValueOnce(new Error('offline'))
|
||||
.mockRejectedValueOnce(new Error('still offline'))
|
||||
.mockResolvedValueOnce('blob:retry')
|
||||
const view = render(<MessageImage attachment={attachment} load={load} labels={labels} />)
|
||||
const view = render(<MessageImage attachment={attachment} load={load} variant="single" labels={labels} />)
|
||||
const retry = await view.findByRole('button', { name: '图片加载失败,点击重试' })
|
||||
fireEvent.click(retry)
|
||||
const retryAgain = await view.findByRole('button', { name: '图片加载失败,点击重试' })
|
||||
@@ -73,16 +73,52 @@ describe('MessageImage', () => {
|
||||
expect(load).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('clamps extreme aspect ratios and anchors the crop toward the informative edge', async () => {
|
||||
const load = vi.fn().mockResolvedValue('blob:ratio')
|
||||
const tall = render(
|
||||
<MessageImage attachment={{ ...attachment, width: 100, height: 2000 }} load={load} variant="single" labels={labels} />,
|
||||
)
|
||||
const tallFrame = tall.getByRole('button', { name: 'history.png,点击查看原图' })
|
||||
expect(tallFrame.getAttribute('style')).toContain('width: 60px')
|
||||
expect(tallFrame.getAttribute('style')).toContain('height: 240px')
|
||||
await waitFor(() => { expect(tall.getByAltText('history.png')).toBeTruthy() })
|
||||
expect(tall.getByAltText('history.png').style.objectPosition).toBe('center top')
|
||||
tall.unmount()
|
||||
const wide = render(
|
||||
<MessageImage attachment={{ ...attachment, width: 4000, height: 100 }} load={load} variant="single" labels={labels} />,
|
||||
)
|
||||
const wideFrame = wide.getByRole('button', { name: 'history.png,点击查看原图' })
|
||||
expect(wideFrame.getAttribute('style')).toContain('width: 240px')
|
||||
expect(wideFrame.getAttribute('style')).toContain('height: 60px')
|
||||
await waitFor(() => { expect(wide.getByAltText('history.png')).toBeTruthy() })
|
||||
expect(wide.getByAltText('history.png').style.objectPosition).toBe('left center')
|
||||
wide.unmount()
|
||||
const small = render(
|
||||
<MessageImage attachment={{ ...attachment, width: 100, height: 100 }} load={load} variant="single" labels={labels} />,
|
||||
)
|
||||
const smallFrame = small.getByRole('button', { name: 'history.png,点击查看原图' })
|
||||
expect(smallFrame.getAttribute('style')).toContain('width: 100px')
|
||||
expect(smallFrame.getAttribute('style')).toContain('height: 100px')
|
||||
})
|
||||
|
||||
it('renders a tile at the fixed square without inline sizing', () => {
|
||||
const load = vi.fn(() => new Promise<string>(() => {}))
|
||||
const view = render(<MessageImage attachment={attachment} load={load} variant="tile" labels={labels} />)
|
||||
const frame = view.getByRole('button', { name: 'history.png,点击查看原图' })
|
||||
expect(frame.getAttribute('data-variant')).toBe('tile')
|
||||
expect(frame.getAttribute('style')).toBeNull()
|
||||
})
|
||||
|
||||
it('ignores a load settling after unmount', async () => {
|
||||
let resolve: ((url: string) => void) | undefined
|
||||
const load = vi.fn(() => new Promise<string>((r) => { resolve = r }))
|
||||
const view = render(<MessageImage attachment={attachment} load={load} labels={labels} />)
|
||||
const view = render(<MessageImage attachment={attachment} load={load} variant="single" labels={labels} />)
|
||||
view.unmount()
|
||||
resolve?.('blob:late')
|
||||
await Promise.resolve()
|
||||
let reject: ((error: Error) => void) | undefined
|
||||
const failing = vi.fn(() => new Promise<string>((_r, rej) => { reject = rej }))
|
||||
const second = render(<MessageImage attachment={attachment} load={failing} labels={labels} />)
|
||||
const second = render(<MessageImage attachment={attachment} load={failing} variant="single" labels={labels} />)
|
||||
second.unmount()
|
||||
reject?.(new Error('late failure'))
|
||||
await Promise.resolve()
|
||||
@@ -100,4 +136,15 @@ describe('ImageGallery', () => {
|
||||
expect(view.container.querySelector('[data-align="end"]')).not.toBeNull()
|
||||
await waitFor(() => { expect(view.getAllByAltText('history.png')).toHaveLength(2) })
|
||||
})
|
||||
|
||||
it('renders a lone image large and several images as square tiles', () => {
|
||||
const load = vi.fn(() => new Promise<string>(() => {}))
|
||||
const lone = render(<ImageGallery images={[{ attachment }]} load={load} align="start" labels={labels} />)
|
||||
expect(lone.container.querySelectorAll('[data-variant="single"]')).toHaveLength(1)
|
||||
lone.unmount()
|
||||
const several = render(
|
||||
<ImageGallery images={[{ attachment }, { attachment }, { attachment }]} load={load} align="end" labels={labels} />,
|
||||
)
|
||||
expect(several.container.querySelectorAll('[data-variant="tile"]')).toHaveLength(3)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user