94 lines
4.2 KiB
JavaScript
94 lines
4.2 KiB
JavaScript
// @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');
|
||
});
|
||
});
|