test: Fix slow typescript editor tests by mocking typescript vfs (no-changelog) (#14631)

This commit is contained in:
Elias Meire
2025-04-15 16:38:31 +02:00
committed by GitHub
parent ed19f0f39b
commit 86de2db4f3

View File

@@ -1,6 +1,12 @@
import { type ChangeSet, EditorState } from '@codemirror/state';
import * as tsvfs from '@typescript/vfs';
import ts from 'typescript';
import { mock } from 'vitest-mock-extended';
import type { WorkerInitOptions } from '../types'; import type { WorkerInitOptions } from '../types';
import { worker } from './typescript.worker'; import { worker } from './typescript.worker';
import { type ChangeSet, EditorState } from '@codemirror/state';
vi.mock('@typescript/vfs');
vi.mock('typescript');
async function createWorker({ async function createWorker({
doc, doc,
@@ -16,6 +22,14 @@ function myFunction(){
return $input.all();`; return $input.all();`;
const state = EditorState.create({ doc: doc ?? defaultDoc }); const state = EditorState.create({ doc: doc ?? defaultDoc });
const mockEnv = mock<tsvfs.VirtualTypeScriptEnvironment>({
getSourceFile: vi.fn(() => mock<ts.SourceFile>({ getText: () => state.doc.toString() })),
languageService: mock<ts.LanguageService>(),
});
vi.spyOn(tsvfs, 'createDefaultMapFromCDN').mockResolvedValue(new Map());
vi.spyOn(tsvfs, 'createVirtualTypeScriptEnvironment').mockResolvedValue(mockEnv);
const tsWorker = worker.init( const tsWorker = worker.init(
{ {
allNodeNames: [], allNodeNames: [],
@@ -32,12 +46,32 @@ return $input.all();`;
params: { path: '', type: 'string', value: '' }, params: { path: '', type: 'string', value: '' },
}), }),
); );
return await tsWorker; return { tsWorker: await tsWorker, mockEnv };
} }
describe('Typescript Worker', () => { describe('Typescript Worker', () => {
beforeEach(() => {
vi.resetAllMocks();
});
it('should return diagnostics', async () => { it('should return diagnostics', async () => {
const tsWorker = await createWorker(); const { tsWorker, mockEnv } = await createWorker();
vi.mocked(mockEnv.languageService.getSemanticDiagnostics).mockReturnValue([
mock<ts.Diagnostic>({
start: 56,
length: 10,
code: 6133,
messageText: "'myFunction' is declared but its value is never read.",
}),
mock<ts.Diagnostic>({
start: 93,
length: 5,
code: 6133,
messageText: "'myObj' is declared but its value is never read.",
}),
]);
vi.mocked(mockEnv.languageService.getSyntacticDiagnostics).mockReturnValue([]);
expect(tsWorker.getDiagnostics()).toEqual([ expect(tsWorker.getDiagnostics()).toEqual([
{ {
@@ -55,10 +89,13 @@ describe('Typescript Worker', () => {
to: 52, to: 52,
}, },
]); ]);
expect(mockEnv.languageService.getSemanticDiagnostics).toHaveBeenCalledWith('id.js');
expect(mockEnv.languageService.getSyntacticDiagnostics).toHaveBeenCalledWith('id.js');
}); });
it('should accept updates from the client and buffer them', async () => { it('should accept updates from the client and buffer them', async () => {
const tsWorker = await createWorker(); const { tsWorker, mockEnv } = await createWorker();
// Add if statement and remove indentation // Add if statement and remove indentation
const changes = [ const changes = [
[75, [0, '', ''], 22], [75, [0, '', ''], 22],
@@ -68,49 +105,60 @@ describe('Typescript Worker', () => {
]; ];
vi.useFakeTimers({ toFake: ['setTimeout', 'queueMicrotask', 'nextTick'] }); vi.useFakeTimers({ toFake: ['setTimeout', 'queueMicrotask', 'nextTick'] });
vi.mocked(mockEnv.updateFile).mockReset();
for (const change of changes) { for (const change of changes) {
tsWorker.updateFile(change as unknown as ChangeSet); tsWorker.updateFile(change as unknown as ChangeSet);
} }
expect(tsWorker.getDiagnostics()).toHaveLength(2); expect(mockEnv.updateFile).not.toHaveBeenCalled();
vi.advanceTimersByTime(1000); vi.advanceTimersByTime(1000);
vi.runAllTicks(); vi.runAllTicks();
expect(tsWorker.getDiagnostics()).toHaveLength(3); expect(mockEnv.updateFile).toHaveBeenCalledTimes(1);
expect(tsWorker.getDiagnostics()).toEqual([ expect(mockEnv.updateFile).toHaveBeenCalledWith(
'id.js',
'\n\nif (true){\n const myObj = {test: "value"}\n}',
{ {
from: 10, length: 0,
markClass: 'cm-faded', start: 121,
message: "'myFunction' is declared but its value is never read.",
severity: 'warning',
to: 20,
}, },
{ );
from: 47,
markClass: 'cm-faded',
message: "'myObj' is declared but its value is never read.",
severity: 'warning',
to: 52,
},
{
from: 96,
markClass: 'cm-faded',
message: "'myObj' is declared but its value is never read.",
severity: 'warning',
to: 101,
},
]);
}); });
it('should return completions', async () => { it('should return completions', async () => {
const doc = 'return $input.'; const doc = 'return $input.';
const tsWorker = await createWorker({ doc }); const { tsWorker, mockEnv } = await createWorker({ doc });
vi.mocked(mockEnv.languageService.getCompletionsAtPosition).mockReturnValue(
mock<ts.CompletionInfo>({
isGlobalCompletion: false,
optionalReplacementSpan: '',
entries: [
mock<ts.CompletionEntry>({
name: 'all',
kind: ts.ScriptElementKind.functionElement,
sortText: '10',
}),
mock<ts.CompletionEntry>({
name: 'first',
kind: ts.ScriptElementKind.functionElement,
sortText: '10',
}),
],
}),
);
const completionResult = await tsWorker.getCompletionsAtPos(doc.length); const completionResult = await tsWorker.getCompletionsAtPos(doc.length);
assert(completionResult !== null); assert(completionResult !== null);
expect(completionResult).toEqual({
isGlobal: false,
result: expect.objectContaining({
from: 60,
}),
});
const completionLabels = completionResult.result.options.map((c) => c.label); const completionLabels = completionResult.result.options.map((c) => c.label);
expect(completionLabels).toContain('all()'); expect(completionLabels).toContain('all()');
expect(completionLabels).toContain('first()'); expect(completionLabels).toContain('first()');