// @vitest-environment jsdom // privateLibraryPane.test.js —— 我的资料面板(T1 骨架,对齐 A1 交付) // 对应用例:TC-PRIV-L3-04 ~ TC-PRIV-L3-07(docs/team/TEST_CASES_PRIVATE.md) // UI_SPEC §9.2:标题「📁 我的资料」+ 计数;列表项 name/size/time;删除 confirm → removeLibrary → 刷新; // 空态「尚无上传资料。在对话框拖拽 PDF / DOCX / TXT 上传。」;加载失败显示重试按钮。 // 修正(A1 交付对齐): // - 可变状态用 vi.hoisted(vi.mock 工厂被提升,引用顶层 let 会 TDZ); // - A1 onDelete 内部 await setTimeout(0)(宏任务)后才刷新——用例必须等待宏任务 // (vitest 默认未启用 fake timers,setTimeout 真实执行,flushPromises 不排干它; // 用小轮询或 vi.useFakeTimers+advanceTimersByTimeAsync;本文件用真实定时器轮询辅助函数)。 import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { setActivePinia, createPinia } from 'pinia'; const h = vi.hoisted(() => ({ listImpl: () => ({ ok: true, items: [] }), removeImpl: () => ({ ok: true }), })); vi.mock('../../src/api/docgen.js', () => ({ listLibrary: vi.fn(async () => h.listImpl()), removeLibrary: vi.fn(async (id) => h.removeImpl(id)), })); import PrivateLibraryPane from '../../src/components/bench/PrivateLibraryPane.vue'; const ROW_A = { id: 'a', name: '水稻资料.pdf', category: '', tags: '', description: '', as_template: false, active: true, created_at: '2026-09-01T00:00:00Z' }; const ROW_B = { id: 'b', name: '小麦试验.docx', category: '', tags: '', description: '', as_template: false, active: true, created_at: '2026-09-02T00:00:00Z' }; /** 等待宏任务(A1 onDelete 的 setTimeout(0) 刷新点) */ async function waitMacroTask() { await new Promise((resolve) => setTimeout(resolve, 0)); await flushPromises(); } describe('PrivateLibraryPane(FR-PRIV-006)', () => { beforeEach(() => { setActivePinia(createPinia()); h.listImpl = () => ({ ok: true, items: [] }); h.removeImpl = () => ({ ok: true }); }); it('TC-PRIV-L3-04 列表加载:标题 + 计数 + 条目(名称)', async () => { h.listImpl = () => ({ ok: true, items: [ROW_A, ROW_B] }); const wrapper = mount(PrivateLibraryPane); await flushPromises(); expect(wrapper.text()).toContain('我的资料'); expect(wrapper.text()).toContain('2'); expect(wrapper.text()).toContain('水稻资料.pdf'); expect(wrapper.text()).toContain('小麦试验.docx'); }); it('TC-PRIV-L3-05 空态文案', async () => { const wrapper = mount(PrivateLibraryPane); await flushPromises(); expect(wrapper.text()).toContain('尚无上传资料'); expect(wrapper.text()).toContain('PDF / DOCX / TXT'); }); it('TC-PRIV-L3-06 删除:confirm 二次确认 → removeLibrary → 列表刷新', async () => { vi.spyOn(window, 'confirm').mockReturnValue(true); h.listImpl = () => ({ ok: true, items: [ROW_A, ROW_B] }); h.removeImpl = vi.fn(async () => ({ ok: true })); const wrapper = mount(PrivateLibraryPane); await flushPromises(); const deleteBtn = wrapper.find('button[data-action="delete"]'); expect(deleteBtn.exists()).toBe(true); await deleteBtn.trigger('click'); expect(window.confirm).toHaveBeenCalled(); // 删除后刷新:下一次 listLibrary 返回单条(A1 在宏任务后刷新) h.listImpl = () => ({ ok: true, items: [ROW_B] }); await waitMacroTask(); expect(wrapper.text()).not.toContain('水稻资料.pdf'); expect(wrapper.text()).toContain('小麦试验.docx'); }); it('TC-PRIV-L3-07 加载失败显示重试按钮,重试后恢复', async () => { let failFirst = true; h.listImpl = () => { if (failFirst) throw new Error('network'); return { ok: true, items: [ROW_A] }; }; const wrapper = mount(PrivateLibraryPane); await flushPromises(); const retryBtn = wrapper.find('button'); expect(retryBtn.exists()).toBe(true); failFirst = false; await retryBtn.trigger('click'); await flushPromises(); expect(wrapper.text()).toContain('水稻资料.pdf'); }); });