A test file under packages/client now says which face it covers:
`*.client.spec.{ts,tsx}` and its `*.client.{ts,tsx}` helpers belong to the
Client aggregate, `*.host.spec.ts` to the host aggregate. The carrier's four
node-half specs take the Host suffix.
The two suffixes are mutually exclusive, so each aggregate excludes the
other's and both keep one broad test glob: `exclude` wins over `include`, and
`packages/client/**` no longer has to be excluded wholesale from the host
program with per-file `files` entries carved back out of it. A Host-face spec
that reaches only Host source therefore needs no cross-face project
reference, which the split-project rule rejects.
vitest still discovers every file through `**/*.spec.{ts,tsx}`.
51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { cleanup, fireEvent, render } from '@testing-library/react'
|
|
import { ImageLightbox } from '../src/ImageLightbox.tsx'
|
|
|
|
afterEach(cleanup)
|
|
|
|
const labels = { dialog: '原图预览', close: '关闭原图预览' }
|
|
|
|
describe('ImageLightbox', () => {
|
|
it('focuses its close control, closes by button and Escape, and restores focus', () => {
|
|
const opener = document.createElement('button')
|
|
document.body.appendChild(opener)
|
|
opener.focus()
|
|
const onClose = vi.fn()
|
|
const view = render(<ImageLightbox src="blob:original" alt="原图" labels={labels} onClose={onClose} />)
|
|
const close = view.getByRole('button', { name: '关闭原图预览' })
|
|
expect(document.activeElement).toBe(close)
|
|
fireEvent.keyDown(window, { key: 'a' })
|
|
expect(onClose).not.toHaveBeenCalled()
|
|
fireEvent.keyDown(window, { key: 'Escape' })
|
|
fireEvent.click(close)
|
|
expect(onClose).toHaveBeenCalledTimes(2)
|
|
view.unmount()
|
|
expect(document.activeElement).toBe(opener)
|
|
opener.remove()
|
|
})
|
|
|
|
it('tolerates a focus owner it cannot restore (no active element at mount)', () => {
|
|
// jsdom always reports body as the fallback active element; stub the
|
|
// element-less state a detached focus can leave.
|
|
Object.defineProperty(document, 'activeElement', { configurable: true, get: () => null })
|
|
try {
|
|
const view = render(<ImageLightbox src="blob:original" alt="原图" labels={labels} onClose={vi.fn()} />)
|
|
view.unmount()
|
|
} finally {
|
|
delete (document as { activeElement?: unknown }).activeElement
|
|
}
|
|
})
|
|
|
|
it('closes on a backdrop press but not on a press over the image', () => {
|
|
const onClose = vi.fn()
|
|
const view = render(<ImageLightbox src="blob:original" alt="原图" labels={labels} onClose={onClose} />)
|
|
fireEvent.mouseDown(view.getByRole('img'))
|
|
expect(onClose).not.toHaveBeenCalled()
|
|
fireEvent.mouseDown(view.getByRole('dialog', { name: '原图预览' }))
|
|
expect(onClose).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|