mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-15 04:54:12 +00:00
276 lines
8.8 KiB
TypeScript
276 lines
8.8 KiB
TypeScript
/*
|
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
*
|
|
* Copyright (C) 2025 Tencent.
|
|
*/
|
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
import { defineComponent, h, nextTick } from 'vue';
|
|
import { mount } from '@vue/test-utils';
|
|
|
|
import CodeEditor from '@editor/layouts/CodeEditor.vue';
|
|
|
|
const {
|
|
vsEditorInstance,
|
|
vsDiffEditorInstance,
|
|
monacoInstance,
|
|
blurHandlers,
|
|
contentChangeHandlers,
|
|
diffContentChangeHandlers,
|
|
} = vi.hoisted(() => ({
|
|
vsEditorInstance: {
|
|
getValue: vi.fn(() => 'editor-value'),
|
|
setValue: vi.fn(),
|
|
getPosition: vi.fn(() => ({ lineNumber: 1, column: 1 })),
|
|
setPosition: vi.fn(),
|
|
focus: vi.fn(),
|
|
layout: vi.fn(),
|
|
setScrollTop: vi.fn(),
|
|
revealLine: vi.fn(),
|
|
dispose: vi.fn(),
|
|
getOptions: vi.fn(() => ({ get: vi.fn(() => 20) })),
|
|
onDidChangeModelContent: vi.fn(),
|
|
onDidBlurEditorWidget: vi.fn(),
|
|
updateOptions: vi.fn(),
|
|
} as any,
|
|
vsDiffEditorInstance: {
|
|
getModifiedEditor: vi.fn(),
|
|
getPosition: vi.fn(() => null),
|
|
setPosition: vi.fn(),
|
|
setModel: vi.fn(),
|
|
focus: vi.fn(),
|
|
layout: vi.fn(),
|
|
dispose: vi.fn(),
|
|
updateOptions: vi.fn(),
|
|
} as any,
|
|
monacoInstance: {
|
|
editor: {
|
|
createModel: vi.fn(),
|
|
EditorOption: { scrollBeyondLastLine: 1, padding: 2, lineHeight: 3 },
|
|
},
|
|
} as any,
|
|
blurHandlers: [] as any[],
|
|
contentChangeHandlers: [] as any[],
|
|
diffContentChangeHandlers: [] as any[],
|
|
}));
|
|
|
|
vi.mock('@editor/utils/monaco-editor', () => ({
|
|
default: vi.fn(async () => monacoInstance),
|
|
}));
|
|
|
|
vi.mock('@editor/utils/config', () => ({
|
|
getEditorConfig: vi.fn((k: string) => {
|
|
if (k === 'parseDSL') return (s: string) => JSON.parse(s);
|
|
if (k === 'customCreateMonacoEditor') {
|
|
return (_m: any, _el: any, _opts: any) => vsEditorInstance;
|
|
}
|
|
if (k === 'customCreateMonacoDiffEditor') {
|
|
return (_m: any, _el: any, _opts: any) => vsDiffEditorInstance;
|
|
}
|
|
return undefined;
|
|
}),
|
|
}));
|
|
|
|
vi.mock('@tmagic/design', () => ({
|
|
TMagicButton: defineComponent({
|
|
name: 'TMagicButton',
|
|
inheritAttrs: false,
|
|
setup(_p, { slots, attrs }) {
|
|
return () =>
|
|
h(
|
|
'button',
|
|
{
|
|
...attrs,
|
|
class: ['fake-btn', (attrs as any).class].filter(Boolean).join(' '),
|
|
},
|
|
slots.default?.(),
|
|
);
|
|
},
|
|
}),
|
|
}));
|
|
|
|
vi.mock('@editor/components/Icon.vue', () => ({
|
|
default: defineComponent({
|
|
name: 'IconStub',
|
|
props: ['icon'],
|
|
setup() {
|
|
return () => h('i', { class: 'fake-icon' });
|
|
},
|
|
}),
|
|
}));
|
|
|
|
class FakeResizeObserver {
|
|
observe() {}
|
|
disconnect() {}
|
|
}
|
|
(globalThis as any).ResizeObserver = FakeResizeObserver;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
blurHandlers.length = 0;
|
|
contentChangeHandlers.length = 0;
|
|
diffContentChangeHandlers.length = 0;
|
|
vsEditorInstance.onDidChangeModelContent.mockImplementation((cb: any) => {
|
|
contentChangeHandlers.push(cb);
|
|
});
|
|
vsEditorInstance.onDidBlurEditorWidget.mockImplementation((cb: any) => {
|
|
blurHandlers.push(cb);
|
|
});
|
|
const modifiedEditor = {
|
|
getValue: vi.fn(() => 'modified-value'),
|
|
onDidChangeModelContent: vi.fn((cb: any) => diffContentChangeHandlers.push(cb)),
|
|
};
|
|
vsDiffEditorInstance.getModifiedEditor.mockReturnValue(modifiedEditor);
|
|
});
|
|
|
|
const flush = async () => {
|
|
await nextTick();
|
|
await new Promise((r) => setTimeout(r, 50));
|
|
await nextTick();
|
|
};
|
|
|
|
describe('CodeEditor', () => {
|
|
test('挂载时初始化 monaco 编辑器', async () => {
|
|
const wrapper = mount(CodeEditor, { props: { initValues: 'abc' } as any, attachTo: document.body });
|
|
await flush();
|
|
expect(wrapper.find('.fake-btn').exists()).toBe(true);
|
|
expect(wrapper.emitted('initd')).toBeTruthy();
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('disabledFullScreen 时不显示按钮', async () => {
|
|
const wrapper = mount(CodeEditor, {
|
|
props: { initValues: 'abc', disabledFullScreen: true } as any,
|
|
attachTo: document.body,
|
|
});
|
|
await flush();
|
|
expect(wrapper.find('.magic-code-editor-full-screen-icon').exists()).toBe(false);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('点击全屏按钮切换 fullScreen', async () => {
|
|
const wrapper = mount(CodeEditor, { props: { initValues: 'abc' } as any, attachTo: document.body });
|
|
await flush();
|
|
await wrapper.find('.fake-btn').trigger('click');
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
expect(vsEditorInstance.layout).toHaveBeenCalled();
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('blur 自动保存触发 save 事件', async () => {
|
|
const wrapper = mount(CodeEditor, { props: { initValues: 'abc', autoSave: true } as any, attachTo: document.body });
|
|
await flush();
|
|
vsEditorInstance.getValue.mockReturnValue('new-value');
|
|
blurHandlers.forEach((cb) => cb());
|
|
expect(wrapper.emitted('save')?.[0]?.[0]).toBe('new-value');
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('parse: true 时解析后再 emit save', async () => {
|
|
const wrapper = mount(CodeEditor, {
|
|
props: { initValues: '{}', autoSave: true, parse: true, language: 'json' } as any,
|
|
attachTo: document.body,
|
|
});
|
|
await flush();
|
|
vsEditorInstance.getValue.mockReturnValue('{"foo":1}');
|
|
blurHandlers.forEach((cb) => cb());
|
|
expect(wrapper.emitted('save')?.[0]?.[0]).toEqual({ foo: 1 });
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('Ctrl+S 触发 save', async () => {
|
|
const wrapper = mount(CodeEditor, { props: { initValues: 'abc' } as any, attachTo: document.body });
|
|
await flush();
|
|
const editorEl = wrapper.find('.magic-code-editor-content').element as HTMLDivElement;
|
|
vsEditorInstance.getValue.mockReturnValue('save-content');
|
|
const event = new KeyboardEvent('keydown', { keyCode: 83, ctrlKey: true } as any);
|
|
editorEl.dispatchEvent(event);
|
|
expect(wrapper.emitted('save')?.[0]?.[0]).toBe('save-content');
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('diff 模式下创建 diff 编辑器', async () => {
|
|
const wrapper = mount(CodeEditor, {
|
|
props: { type: 'diff', initValues: 'a', modifiedValues: 'b' } as any,
|
|
attachTo: document.body,
|
|
});
|
|
await flush();
|
|
expect(vsDiffEditorInstance.setModel).toHaveBeenCalled();
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('autosize 时根据内容计算高度', async () => {
|
|
const wrapper = mount(CodeEditor, {
|
|
props: { initValues: 'a\nb\nc', autosize: { minRows: 1, maxRows: 10 } } as any,
|
|
attachTo: document.body,
|
|
});
|
|
await flush();
|
|
contentChangeHandlers.forEach((cb) => cb());
|
|
await flush();
|
|
expect(true).toBe(true);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('options 变化时调用 updateOptions', async () => {
|
|
const wrapper = mount(CodeEditor, {
|
|
props: { initValues: 'abc', options: { tabSize: 2 } } as any,
|
|
attachTo: document.body,
|
|
});
|
|
await flush();
|
|
await wrapper.setProps({ options: { tabSize: 4 } } as any);
|
|
await flush();
|
|
expect(vsEditorInstance.updateOptions).toHaveBeenCalled();
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('initValues 改变时调用 setEditorValue', async () => {
|
|
const wrapper = mount(CodeEditor, { props: { initValues: 'abc' } as any, attachTo: document.body });
|
|
await flush();
|
|
await wrapper.setProps({ initValues: 'xyz' } as any);
|
|
await flush();
|
|
expect(vsEditorInstance.setValue).toHaveBeenCalledWith('xyz');
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('expose getEditor / focus / setEditorValue', async () => {
|
|
vsEditorInstance.getValue.mockReturnValue('editor-value');
|
|
const wrapper = mount(CodeEditor, { props: { initValues: 'abc' } as any, attachTo: document.body });
|
|
await flush();
|
|
expect((wrapper.vm as any).getEditor()).toBe(vsEditorInstance);
|
|
expect((wrapper.vm as any).getVsEditor()).toBe(vsEditorInstance);
|
|
(wrapper.vm as any).focus();
|
|
expect(vsEditorInstance.focus).toHaveBeenCalled();
|
|
expect((wrapper.vm as any).getEditorValue()).toBe('editor-value');
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('卸载时 dispose', async () => {
|
|
const wrapper = mount(CodeEditor, { props: { initValues: 'abc' } as any, attachTo: document.body });
|
|
await flush();
|
|
wrapper.unmount();
|
|
expect(vsEditorInstance.dispose).toHaveBeenCalled();
|
|
});
|
|
|
|
test('toString 处理 javascript 对象', async () => {
|
|
const wrapper = mount(CodeEditor, {
|
|
props: { initValues: { a: 1 }, language: 'javascript' } as any,
|
|
attachTo: document.body,
|
|
});
|
|
await flush();
|
|
expect(vsEditorInstance.setValue).toHaveBeenCalled();
|
|
const callArg = vsEditorInstance.setValue.mock.calls[0][0];
|
|
expect(callArg).toMatch(/^\(/);
|
|
wrapper.unmount();
|
|
});
|
|
|
|
test('toString 处理 json 对象', async () => {
|
|
const wrapper = mount(CodeEditor, {
|
|
props: { initValues: { a: 1 }, language: 'json' } as any,
|
|
attachTo: document.body,
|
|
});
|
|
await flush();
|
|
const callArg = vsEditorInstance.setValue.mock.calls[0][0];
|
|
expect(JSON.parse(callArg)).toEqual({ a: 1 });
|
|
wrapper.unmount();
|
|
});
|
|
});
|