feat(W2): 栏目树修订-虚拟根(学校名/子站名)+工具栏收敛4按钮+高度链修复,T-W2-01..10 绿 44/44
This commit is contained in:
@@ -14,10 +14,11 @@ import { createApp, defineComponent, h, nextTick, reactive } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import ViewUIPlus from "view-ui-plus";
|
||||
|
||||
const { pageMock, routerPush, openMock } = vi.hoisted(() => ({
|
||||
const { pageMock, routerPush, openMock, saveMock } = vi.hoisted(() => ({
|
||||
pageMock: vi.fn(),
|
||||
routerPush: vi.fn(),
|
||||
openMock: vi.fn(),
|
||||
saveMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/api/module/cms", () => ({
|
||||
@@ -33,7 +34,10 @@ vi.mock("@/api/module/cms", () => ({
|
||||
},
|
||||
categoryApi: {
|
||||
tree: () => ({ send: () => Promise.resolve([]) }),
|
||||
save: () => ({ send: () => Promise.resolve({}) }),
|
||||
save: (payload: any) => {
|
||||
saveMock(payload);
|
||||
return { send: () => Promise.resolve({}) };
|
||||
},
|
||||
delete: () => ({ send: () => Promise.resolve({}) }),
|
||||
},
|
||||
uploadUrl: () => "/api/attachment/upload",
|
||||
@@ -45,16 +49,19 @@ vi.mock("vue-router", () => ({
|
||||
useRoute: () => ({ params: {}, query: {} }),
|
||||
}));
|
||||
|
||||
/** 树面板桩:把 emit 挂到全局,测试用它模拟"选中节点" */
|
||||
/** 树面板桩:把 emit 挂到全局,测试用它模拟"选中节点";捕获 props 供 treeApi 断言 */
|
||||
vi.mock("@/components/CategoryTreePanel.vue", () => ({
|
||||
default: defineComponent({
|
||||
props: ["curTreeNode", "treeApi", "defaultSearchVisible", "showCloseButton"],
|
||||
emits: ["update:curTreeNode", "on-change", "on-tree-data"],
|
||||
setup(_, { emit }) {
|
||||
setup(props, { emit, expose }) {
|
||||
(globalThis as any).__treeEmit = (node: any) => {
|
||||
emit("update:curTreeNode", node);
|
||||
emit("on-change", node);
|
||||
};
|
||||
(globalThis as any).__treeProps = props;
|
||||
// CategoryManage 会经模板 ref 调这些方法(真实组件的 exposed API)
|
||||
expose({ loadTree: () => {}, refreshTree: () => {}, reloadTree: () => {} });
|
||||
return () => h("div", { class: "tree-stub" }, "tree-stub");
|
||||
},
|
||||
}),
|
||||
@@ -72,14 +79,18 @@ vi.mock("@/components/MyConfirm", () => ({ default: vi.fn() }));
|
||||
|
||||
import ArticleListPanel from "@/components/article/ArticleListPanel.vue";
|
||||
import CategoryManage from "@/views/cms/CategoryManage.vue";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { useCmsStore } from "@/store";
|
||||
|
||||
let cleanups: (() => void)[] = [];
|
||||
let currentStore: ReturnType<typeof useCmsStore> | null = null;
|
||||
|
||||
/** 挂载:真实 pinia(预置学校/站点上下文)+ 全局 view-ui-plus */
|
||||
function mountApp(root: Component) {
|
||||
const pinia = createPinia();
|
||||
const store = useCmsStore(pinia);
|
||||
currentStore = store;
|
||||
store.user = { superAdmin: true, sites: [] } as any;
|
||||
store.currentSchool = { schId: 1, schName: "演示学校", mainSiteId: 1, mainSiteCode: "demo1", mainSiteName: "演示主站" } as any;
|
||||
store.currentSite = { id: 1, code: "demo1", name: "演示站" } as any;
|
||||
@@ -106,6 +117,8 @@ async function flush(frames = 5) {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(globalThis as any).__treeEmit = undefined;
|
||||
(globalThis as any).__treeProps = undefined;
|
||||
currentStore = null;
|
||||
window.open = openMock as any;
|
||||
});
|
||||
|
||||
@@ -207,3 +220,81 @@ describe("categoryWorkbench - CategoryManage 右侧类型化面板", () => {
|
||||
expect(el.textContent).toContain("选择左侧栏目");
|
||||
});
|
||||
});
|
||||
|
||||
describe("W2 栏目树修订(T-W2-06..10)", () => {
|
||||
const selectRoot = async () => {
|
||||
(globalThis as any).__treeEmit({ id: 0, name: "演示学校", virtual: true, children: [] });
|
||||
await flush();
|
||||
};
|
||||
|
||||
it("T-W2-06: 工具栏收敛——搜索/收起展开按钮移除(仅剩 4 个操作)", async () => {
|
||||
const el = mountApp(defineComponent({ render: () => h(CategoryManage) }));
|
||||
await flush();
|
||||
expect(el.querySelector(".search-node-li")).toBeNull();
|
||||
expect(el.querySelectorAll(".opera-ul li")).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("T-W2-07: 树桩收到带根数据——treeApi 返回首节点 id=0、name=学校名", async () => {
|
||||
mountApp(defineComponent({ render: () => h(CategoryManage) }));
|
||||
await flush();
|
||||
const props = (globalThis as any).__treeProps;
|
||||
expect(props?.treeApi).toBeTypeOf("function");
|
||||
const wrapped = await props.treeApi();
|
||||
expect(wrapped[0]).toMatchObject({ id: 0, name: "演示学校", virtual: true });
|
||||
expect(wrapped[0].children).toEqual([]);
|
||||
});
|
||||
|
||||
it("T-W2-08: 根上新增=顶级栏目(保存 payload parentId===0)", async () => {
|
||||
const el = mountApp(defineComponent({ render: () => h(CategoryManage) }));
|
||||
await flush();
|
||||
await selectRoot();
|
||||
const addLi = el.querySelector(".add-node-li");
|
||||
addLi!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await flush();
|
||||
|
||||
// Modal 经 transfer 渲染到 body(不在挂载根内);首个 input 是禁用的"上级栏目"展示框,须按 placeholder 定位名称框
|
||||
const input = document.querySelector('.ivu-modal input[placeholder="栏目名称"]') as HTMLInputElement;
|
||||
expect(input).toBeTruthy();
|
||||
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!;
|
||||
setter.call(input, "测试顶级栏目");
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
await flush();
|
||||
|
||||
const saveBtn = [...document.querySelectorAll(".ivu-modal button")].find(b => b.textContent?.includes("保存"));
|
||||
saveBtn!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await flush();
|
||||
|
||||
expect(saveMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveMock.mock.calls[0][0]).toMatchObject({ parentId: 0, name: "测试顶级栏目" });
|
||||
});
|
||||
|
||||
it("T-W2-09: 根上编辑/删除被拒——仅警告、零请求", async () => {
|
||||
const el = mountApp(defineComponent({ render: () => h(CategoryManage) }));
|
||||
await flush();
|
||||
await selectRoot();
|
||||
const warnSpy = vi.spyOn(Message, "warning");
|
||||
|
||||
const lis = [...el.querySelectorAll(".opera-ul li")];
|
||||
// 工具栏顺序:新增/编辑/排序/删除(搜索与收起展开已移除)
|
||||
lis[1].dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await flush();
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
expect(saveMock).not.toHaveBeenCalled();
|
||||
|
||||
lis[3].dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await flush();
|
||||
expect(MyConfirm).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("T-W2-10: 站点切换根 label 跟随——切到子站后根 name=子站名", async () => {
|
||||
mountApp(defineComponent({ render: () => h(CategoryManage) }));
|
||||
await flush();
|
||||
const before = await (globalThis as any).__treeProps.treeApi();
|
||||
expect(before[0].name).toBe("演示学校");
|
||||
|
||||
currentStore!.setCurrentSite({ id: 2, code: "jdxy", name: "机械学院" } as any);
|
||||
await flush();
|
||||
const wrapped = await (globalThis as any).__treeProps.treeApi();
|
||||
expect(wrapped[0].name).toBe("机械学院");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user