2026-05-14 15:26:22 +08:00

169 lines
6.3 KiB
TypeScript

/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent.
*/
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
import codeBlockService from '@editor/services/codeBlock';
import storageService, { Protocol } from '@editor/services/storage';
import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
import { setEditorConfig } from '@editor/utils/config';
import { COPY_CODE_STORAGE_KEY } from '@editor/utils/editor';
vi.mock('@editor/services/editor', () => ({
default: {
getNodeById: vi.fn((id: string) => ({ id, code: 'code1' })),
},
}));
beforeAll(() => {
setEditorConfig({
// eslint-disable-next-line no-new-func
parseDSL: ((str: string) => new Function(`return ${str}`)()) as any,
} as any);
});
beforeEach(() => {
codeBlockService.resetState();
globalThis.localStorage.clear();
});
afterEach(() => {
vi.clearAllMocks();
});
describe('CodeBlockService - 基础', () => {
test('setCodeDsl / getCodeDsl 保存并触发事件', async () => {
const fn = vi.fn();
codeBlockService.on('code-dsl-change', fn);
await codeBlockService.setCodeDsl({ a: { name: 'a', content: 'x' } } as any);
expect(codeBlockService.getCodeDsl()).toEqual({ a: { name: 'a', content: 'x' } });
expect(fn).toHaveBeenCalled();
codeBlockService.off('code-dsl-change', fn);
});
test('getCodeContentById - 没有 dsl 或 id 返回 null', () => {
expect(codeBlockService.getCodeContentById('')).toBeNull();
expect(codeBlockService.getCodeContentById('any')).toBeNull();
});
test('getCodeContentById - 取得现有内容', async () => {
await codeBlockService.setCodeDsl({ x: { name: 'X' } } as any);
expect(codeBlockService.getCodeContentById('x')).toEqual({ name: 'X' });
expect(codeBlockService.getCodeContentById('y')).toBeNull();
});
test('setCodeDslByIdSync - 没有 dsl 时抛错', () => {
expect(() => codeBlockService.setCodeDslByIdSync('id', {} as any)).toThrow('dsl中没有codeBlocks');
});
test('setCodeDslByIdSync - 已存在且 force=false 时跳过', async () => {
await codeBlockService.setCodeDsl({ a: { name: 'A' } } as any);
codeBlockService.setCodeDslByIdSync('a', { name: 'NEW' } as any, false);
expect(codeBlockService.getCodeContentById('a')?.name).toBe('A');
});
test('setCodeDslByIdSync - content 为字符串时被 parseDSL 转换', async () => {
await codeBlockService.setCodeDsl({} as any);
codeBlockService.setCodeDslByIdSync('a', { name: 'A', content: '() => 42' } as any);
const item = codeBlockService.getCodeContentById('a') as any;
expect(typeof item.content).toBe('function');
expect(item.content()).toBe(42);
});
test('setCodeDslByIdSync - 触发 addOrUpdate 事件', async () => {
await codeBlockService.setCodeDsl({} as any);
const fn = vi.fn();
codeBlockService.on('addOrUpdate', fn);
codeBlockService.setCodeDslByIdSync('id', { name: 'a' } as any);
expect(fn).toHaveBeenCalledWith('id', expect.any(Object));
codeBlockService.off('addOrUpdate', fn);
});
test('getCodeDslByIds 仅返回指定 ids', async () => {
await codeBlockService.setCodeDsl({ a: { name: 'a' }, b: { name: 'b' } } as any);
expect(codeBlockService.getCodeDslByIds(['a'])).toEqual({ a: { name: 'a' } });
});
test('编辑状态 / combineIds / undeletableList', async () => {
expect(codeBlockService.getEditStatus()).toBe(true);
await codeBlockService.setEditStatus(false);
expect(codeBlockService.getEditStatus()).toBe(false);
await codeBlockService.setCombineIds(['a', 'b']);
expect(codeBlockService.getCombineIds()).toEqual(['a', 'b']);
await codeBlockService.setUndeleteableList(['x']);
expect(codeBlockService.getUndeletableList()).toEqual(['x']);
});
test('代码草稿 set/get/remove', () => {
codeBlockService.setCodeDraft('id', 'draft');
expect(globalThis.localStorage.getItem(`${CODE_DRAFT_STORAGE_KEY}_id`)).toBe('draft');
expect(codeBlockService.getCodeDraft('id')).toBe('draft');
codeBlockService.removeCodeDraft('id');
expect(codeBlockService.getCodeDraft('id')).toBeNull();
});
test('deleteCodeDslByIds 删除并触发 remove 事件', async () => {
await codeBlockService.setCodeDsl({ a: { name: 'a' }, b: { name: 'b' } } as any);
const fn = vi.fn();
codeBlockService.on('remove', fn);
await codeBlockService.deleteCodeDslByIds(['a', 'b']);
expect(codeBlockService.getCodeDsl()).toEqual({});
expect(fn).toHaveBeenCalledTimes(2);
codeBlockService.off('remove', fn);
});
test('deleteCodeDslByIds - dsl 为空时直接返回', async () => {
await expect(codeBlockService.deleteCodeDslByIds(['x'])).resolves.toBeUndefined();
});
test('paramsColConfig set/get', () => {
const cfg = { type: 'row', items: [] } as any;
codeBlockService.setParamsColConfig(cfg);
expect(codeBlockService.getParamsColConfig()).toEqual(cfg);
});
test('getUniqueId 在重复时再次取', async () => {
let count = 0;
const random = vi.spyOn(Math, 'random').mockImplementation(() => {
count += 1;
return count === 1 ? 0.111111111 : 0.222222222;
});
await codeBlockService.setCodeDsl({ code_1111: { name: 'x' } } as any);
const id = await codeBlockService.getUniqueId();
expect(id.startsWith('code_')).toBe(true);
expect(id).not.toBe('code_1111');
random.mockRestore();
});
test('paste - 不覆盖现有 id', async () => {
await codeBlockService.setCodeDsl({ a: { name: 'A' } } as any);
storageService.setItem(
COPY_CODE_STORAGE_KEY,
{ a: { name: 'OTHER' }, b: { name: 'B' } },
{ protocol: Protocol.OBJECT },
);
codeBlockService.paste();
expect(codeBlockService.getCodeContentById('a')?.name).toBe('A');
expect(codeBlockService.getCodeContentById('b')?.name).toBe('B');
});
test('copyWithRelated - 没有 collectorOptions 时仅写空对象', () => {
codeBlockService.copyWithRelated([]);
const stored = storageService.getItem(COPY_CODE_STORAGE_KEY, { protocol: Protocol.OBJECT });
expect(stored).toEqual({});
});
test('destroy 清空 listeners 与 plugin', () => {
const fn = vi.fn();
codeBlockService.on('addOrUpdate', fn);
codeBlockService.destroy();
codeBlockService.emit('addOrUpdate', 'a');
expect(fn).not.toHaveBeenCalled();
});
});