diff --git a/src/components/RdmAuditWorkspace.test.tsx b/src/components/RdmAuditWorkspace.test.tsx new file mode 100644 index 0000000..d11ead2 --- /dev/null +++ b/src/components/RdmAuditWorkspace.test.tsx @@ -0,0 +1,84 @@ +import { describe, expect, it, vi } from 'vitest' +import { renderToStaticMarkup } from 'react-dom/server' +import { startLatestFetch, TreeNodeRow } from './RdmAuditWorkspace' + +function deferred() { + let resolve!: (v: T) => void + let reject!: (e: unknown) => void + const promise = new Promise((res, rej) => { resolve = res; reject = rej }) + return { promise, resolve, reject } +} + +const flush = () => new Promise(r => setTimeout(r, 0)) + +describe('startLatestFetch(历史请求单一所有者)', () => { + it('较新的请求已发起时丢弃过期响应', async () => { + const ref = { current: 0 } + const onData = vi.fn() + const onError = vi.fn() + const older = deferred() + const newer = deferred() + startLatestFetch(ref, () => older.promise, onData, onError) + startLatestFetch(ref, () => newer.promise, onData, onError) + newer.resolve('newer') + await flush() + older.resolve('older') + await flush() + expect(onData.mock.calls).toEqual([['newer']]) + expect(onError).not.toHaveBeenCalled() + }) + + it('过期请求的失败不写回错误', async () => { + const ref = { current: 0 } + const onError = vi.fn() + const older = deferred() + startLatestFetch(ref, () => older.promise, vi.fn(), onError) + startLatestFetch(ref, () => new Promise(() => {}), vi.fn(), onError) + older.reject(new Error('stale')) + await flush() + expect(onError).not.toHaveBeenCalled() + }) + + it('清理(卸载或参数变化)后在途响应被丢弃', async () => { + const ref = { current: 0 } + const onData = vi.fn() + const pending = deferred() + const cleanup = startLatestFetch(ref, () => pending.promise, onData, vi.fn()) + cleanup() + pending.resolve('late') + await flush() + expect(onData).not.toHaveBeenCalled() + }) + + it('只推进自己的 ref,不影响其他 seq 所有者', () => { + const historyRef = { current: 0 } + const eventsRef = { current: 7 } + startLatestFetch(historyRef, () => new Promise(() => {}), vi.fn(), vi.fn()) + expect(historyRef.current).toBe(1) + expect(eventsRef.current).toBe(7) + }) +}) + +describe('TreeNodeRow 无障碍属性', () => { + const noop = () => {} + const render = (item: { kind: string; id: string; title: string; hasChildren?: boolean }, children = {}) => + renderToStaticMarkup() + + it('可展开行:折叠时 aria-expanded=false,箭头 aria-hidden', () => { + const html = render({ kind: 'spine', id: 's1', title: '骨架' }) + expect(html).toContain('aria-expanded="false"') + expect(html).toMatch(/