Files
YschcmsFront/src/components/RichTextEditor.spec.ts
T
chenxing 6f1f666479
build / build (push) Successful in 4m47s
周末修订
2026-09-15 23:41:11 +08:00

146 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @vitest-environment jsdom
/**
* RichTextEditor 组件行为(审计批次 6:ArticleEdit 复用后,正文上传契约成为两个页面共用底座,
* 需要独立护栏)。覆盖:
* ① 上传 FormData 参数:siteId / scene / articleId 按传入拼装,未传的键不出现;
* ② 上传成功 → emit uploaded(附件 id) 且 insertFn 收到 (url, fileName, url);
* ③ 上传失败(code≠0)→ 不 emit uploaded;
* ④ 工具栏配置仍排除视频组与全屏(Notice 趟坑口径)。
*
* 无 @vue/test-utils 依赖:沿用项目既有 createApp 手写挂载 + vi.mock 桩范式。
*/
import { afterEach, describe, expect, it, vi } from "vitest";
import { createApp, defineComponent, h } from "vue";
const confState = vi.hoisted(() => ({
conf: { baseHost: "", ssoHost: "", serviceHost: "", isMock: false, isDebug: false },
}));
vi.mock("@/assets/config/host.config", () => ({ default: confState.conf }));
/** wangEditor 桩:把传进来的 defaultConfig 挂到全局,测试借此直接驱动 customUpload */
vi.mock("@wangeditor/editor-for-vue", () => ({
Toolbar: defineComponent({
props: ["editor", "defaultConfig", "mode"],
setup(props) {
(globalThis as any).__toolbarConfig = props.defaultConfig;
return () => h("div", { class: "toolbar-stub" });
},
}),
Editor: defineComponent({
props: ["modelValue", "defaultConfig", "mode"],
emits: ["onCreated", "update:modelValue"],
setup(props, { emit }) {
(globalThis as any).__editorConfig = props.defaultConfig;
(globalThis as any).__editorEmitUpdate = (v: string) => emit("update:modelValue", v);
(globalThis as any).__editorCreated = emit;
return () => h("div", { class: "editor-stub" });
},
}),
}));
import RichTextEditor from "@/components/RichTextEditor.vue";
let cleanups: (() => void)[] = [];
function mountEditor(props: Record<string, unknown>, onUploaded?: (id: number) => void) {
const el = document.createElement("div");
document.body.appendChild(el);
const app = createApp({
render: () => h(RichTextEditor, { modelValue: "", ...props, onUploaded }),
});
app.mount(el);
cleanups.push(() => {
app.unmount();
el.remove();
});
return el;
}
/** 直接调用组件内部构造的 customUpload(等同 wangEditor 选中图片后的回调) */
async function runUpload(file: File): Promise<{ inserted: string[] }> {
const inserted: string[] = [];
const cfg = (globalThis as any).__editorConfig;
await cfg.MENU_CONF.uploadImage.customUpload(file, (url: string) => inserted.push(url));
return { inserted };
}
afterEach(() => {
cleanups.forEach(fn => fn());
cleanups = [];
vi.unstubAllGlobals();
delete (globalThis as any).__editorConfig;
delete (globalThis as any).__toolbarConfig;
});
describe("RichTextEditor - 正文上传与事件契约", () => {
it("① 上传 FormData 带 siteId/scene/articleId(scene 透传契约)", async () => {
const seen: FormData[] = [];
vi.stubGlobal(
"fetch",
vi.fn(async (_url: string, init: any) => {
seen.push(init.body as FormData);
return { json: async () => ({ code: 0, data: { id: 77, url: "/cms/upload/a.png", fileName: "a.png" } }) };
})
);
mountEditor({ siteId: 5, scene: "svc_item_asset" }, () => {});
await runUpload(new File(["x"], "a.png", { type: "image/png" }));
expect(seen).toHaveLength(1);
expect(seen[0].get("file")).toBeInstanceOf(File);
expect(seen[0].get("siteId")).toBe("5");
expect(seen[0].get("scene")).toBe("svc_item_asset");
expect(seen[0].get("articleId")).toBeNull();
});
it("①b 文章页口径:传 articleId 时随之提交(编辑已有文章)", async () => {
const seen: FormData[] = [];
vi.stubGlobal(
"fetch",
vi.fn(async (_url: string, init: any) => {
seen.push(init.body as FormData);
return { json: async () => ({ code: 0, data: { id: 78, url: "/cms/upload/b.png", fileName: "b.png" } }) };
})
);
mountEditor({ siteId: 5, articleId: 12 }, () => {});
await runUpload(new File(["x"], "b.png", { type: "image/png" }));
expect(seen[0].get("siteId")).toBe("5");
expect(seen[0].get("articleId")).toBe("12");
expect(seen[0].get("scene")).toBeNull();
});
it("② 上传成功:emit uploaded(id) 且 insertFn 收到 (url, fileName, url)", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({
json: async () => ({ code: 0, data: { id: 99, url: "/cms/upload/c.png", fileName: "c.png" } }),
}))
);
const ids: number[] = [];
mountEditor({ siteId: 5, scene: "svc_item_asset" }, id => ids.push(id));
const { inserted } = await runUpload(new File(["x"], "c.png", { type: "image/png" }));
expect(ids).toEqual([99]);
expect(inserted).toEqual(["/cms/upload/c.png"]);
});
it("③ 上传失败(code≠0):不 emit uploaded、不插入图片", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => ({ json: async () => ({ code: 2, msg: "scene 非法" }) }))
);
const ids: number[] = [];
mountEditor({ siteId: 5, scene: "bad_scene" }, id => ids.push(id));
const { inserted } = await runUpload(new File(["x"], "d.png", { type: "image/png" }));
expect(ids).toEqual([]);
expect(inserted).toEqual([]);
});
it("④ 工具栏仍排除视频组与全屏(Notice 趟坑口径不回退)", () => {
mountEditor({ siteId: 5 }, () => {});
expect((globalThis as any).__toolbarConfig.excludeKeys).toEqual(["group-video", "fullScreen"]);
});
});