Skip to content

Commit ed56bac

Browse files
committed
perf(test): eliminate vi.resetModules in insertContent and cleanUpParagraph tests
- cleanUpParagraphWithAnnotation: replace per-test vi.resetModules + vi.doMock + dynamic import with a single hoisted vi.mock and a controllable mock variable. Tests drop from 58s to 6ms. - insertContent: move helpers/DOCX loading to beforeAll, remove per-test vi.resetModules. Tests drop from 90s to 4.67s.
1 parent 183c1d8 commit ed56bac

2 files changed

Lines changed: 53 additions & 79 deletions

File tree

packages/super-editor/src/core/commands/insertContent.test.js

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect, vi, beforeEach } from 'vitest';
1+
import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest';
22
import { insertContent } from './insertContent.js';
33
import * as contentProcessor from '../helpers/contentProcessor.js';
44

@@ -161,11 +161,14 @@ describe('insertContent', () => {
161161

162162
// Integration-style tests that use a real Editor instance to
163163
// insert markdown/HTML lists and verify exported OOXML has list numbering.
164+
//
165+
// These tests need the REAL contentProcessor (not the mock from the unit tests
166+
// above). We use a separate vi.mock-free import path by dynamically importing
167+
// the real insertContent function.
164168
describe('insertContent (integration) list export', () => {
165-
// Cache loaded DOCX data and helpers to avoid repeated file loading
166-
let cachedDocxData = null;
167169
let helpers = null;
168170
let exportHelpers = null;
171+
let cachedDocxData = null;
169172

170173
const getListParagraphs = (result) => {
171174
const body = result.elements?.find((el) => el.name === 'w:body');
@@ -185,22 +188,16 @@ describe('insertContent (integration) list export', () => {
185188
return { numId, ilvl };
186189
};
187190

188-
const setupEditor = async () => {
189-
// Use real content processor for these tests
191+
// Load helpers and DOCX data once for all integration tests
192+
beforeAll(async () => {
190193
vi.resetModules();
191194
vi.doUnmock('../helpers/contentProcessor.js');
195+
helpers = await import('../../tests/helpers/helpers.js');
196+
cachedDocxData = await helpers.loadTestDataForEditorTests('blank-doc.docx');
197+
exportHelpers = await import('../../tests/export/export-helpers/index.js');
198+
});
192199

193-
// Cache helpers and DOCX data on first call
194-
if (!helpers) {
195-
helpers = await import('../../tests/helpers/helpers.js');
196-
}
197-
if (!cachedDocxData) {
198-
cachedDocxData = await helpers.loadTestDataForEditorTests('blank-doc.docx');
199-
}
200-
if (!exportHelpers) {
201-
exportHelpers = await import('../../tests/export/export-helpers/index.js');
202-
}
203-
200+
const setupEditor = () => {
204201
const { docx, media, mediaFiles, fonts } = cachedDocxData;
205202
const { editor } = helpers.initTestEditor({ content: docx, media, mediaFiles, fonts, mode: 'docx' });
206203
return editor;
@@ -212,7 +209,7 @@ describe('insertContent (integration) list export', () => {
212209
};
213210

214211
it('exports ordered list from markdown with numId/ilvl', async () => {
215-
const editor = await setupEditor();
212+
const editor = setupEditor();
216213
editor.commands.insertContent('1. One\n2. Two', { contentType: 'markdown' });
217214
await Promise.resolve();
218215

@@ -230,7 +227,7 @@ describe('insertContent (integration) list export', () => {
230227
});
231228

232229
it('exports unordered list from markdown with numId/ilvl', async () => {
233-
const editor = await setupEditor();
230+
const editor = setupEditor();
234231
editor.commands.insertContent('- Alpha\n- Beta', { contentType: 'markdown' });
235232
await Promise.resolve();
236233

@@ -248,7 +245,7 @@ describe('insertContent (integration) list export', () => {
248245
});
249246

250247
it('exports ordered list from HTML with numId/ilvl', async () => {
251-
const editor = await setupEditor();
248+
const editor = setupEditor();
252249
editor.commands.insertContent('<ol><li>First</li><li>Second</li></ol>', { contentType: 'html' });
253250
await Promise.resolve();
254251

@@ -262,7 +259,7 @@ describe('insertContent (integration) list export', () => {
262259
});
263260

264261
it('inserts markdown heading + bold text without creating a table', async () => {
265-
const editor = await setupEditor();
262+
const editor = setupEditor();
266263

267264
editor.commands.insertContent('# Hello\n\nSome **bold** text', { contentType: 'markdown' });
268265
await Promise.resolve();
@@ -280,7 +277,7 @@ describe('insertContent (integration) list export', () => {
280277
});
281278

282279
it('exports unordered list from HTML with numId/ilvl', async () => {
283-
const editor = await setupEditor();
280+
const editor = setupEditor();
284281
editor.commands.insertContent('<ul><li>Apple</li><li>Banana</li></ul>', { contentType: 'html' });
285282
await Promise.resolve();
286283

@@ -294,7 +291,7 @@ describe('insertContent (integration) list export', () => {
294291
});
295292

296293
it('defaults imported HTML tables to 100% width', async () => {
297-
const editor = await setupEditor();
294+
const editor = setupEditor();
298295
editor.commands.insertContent(
299296
'<table><tbody><tr><td>Query</td><td>Assessment</td></tr><tr><td>A</td><td>B</td></tr></tbody></table>',
300297
{ contentType: 'html' },
@@ -310,7 +307,7 @@ describe('insertContent (integration) list export', () => {
310307
});
311308

312309
it('defaults imported markdown tables to 100% width', async () => {
313-
const editor = await setupEditor();
310+
const editor = setupEditor();
314311
editor.commands.insertContent('| Query | Assessment |\n| --- | --- |\n| A | B |', { contentType: 'markdown' });
315312
await Promise.resolve();
316313

@@ -323,7 +320,7 @@ describe('insertContent (integration) list export', () => {
323320
});
324321

325322
it('does not inject inline cell borders on imported HTML table headers', async () => {
326-
const editor = await setupEditor();
323+
const editor = setupEditor();
327324
editor.commands.insertContent(
328325
'<table><thead><tr><th>Search Query</th><th>Findings / Assessment</th></tr></thead><tbody><tr><td>A</td><td>B</td></tr></tbody></table>',
329326
{ contentType: 'html' },
@@ -358,17 +355,14 @@ describe.skipIf(!process.env.CI)('insertContent (integration) horizontal rule',
358355
let helpers = null;
359356
let cachedDocxData = null;
360357

361-
const setupEditor = async () => {
358+
beforeAll(async () => {
362359
vi.resetModules();
363360
vi.doUnmock('../helpers/contentProcessor.js');
361+
helpers = await import('../../tests/helpers/helpers.js');
362+
cachedDocxData = await helpers.loadTestDataForEditorTests('blank-doc.docx');
363+
});
364364

365-
if (!helpers) {
366-
helpers = await import('../../tests/helpers/helpers.js');
367-
}
368-
if (!cachedDocxData) {
369-
cachedDocxData = await helpers.loadTestDataForEditorTests('blank-doc.docx');
370-
}
371-
365+
const setupEditor = () => {
372366
const { docx, media, mediaFiles, fonts } = cachedDocxData;
373367
const { editor } = helpers.initTestEditor({ content: docx, media, mediaFiles, fonts, mode: 'docx' });
374368
return editor;
@@ -391,7 +385,7 @@ describe.skipIf(!process.env.CI)('insertContent (integration) horizontal rule',
391385
};
392386

393387
it('insertContent with contentType html creates a horizontal rule', async () => {
394-
const editor = await setupEditor();
388+
const editor = setupEditor();
395389
expect(countHorizontalRules(editor)).toBe(0);
396390

397391
editor.commands.insertContent('<hr>', { contentType: 'html' });
@@ -400,7 +394,7 @@ describe.skipIf(!process.env.CI)('insertContent (integration) horizontal rule',
400394
});
401395

402396
it('insertContent with contentType markdown creates a horizontal rule', async () => {
403-
const editor = await setupEditor();
397+
const editor = setupEditor();
404398
expect(countHorizontalRules(editor)).toBe(0);
405399

406400
editor.commands.insertContent('---', { contentType: 'markdown' });
@@ -409,7 +403,7 @@ describe.skipIf(!process.env.CI)('insertContent (integration) horizontal rule',
409403
});
410404

411405
it('insertContent with bare <hr> (no contentType) creates a horizontal rule', async () => {
412-
const editor = await setupEditor();
406+
const editor = setupEditor();
413407
expect(countHorizontalRules(editor)).toBe(0);
414408

415409
editor.commands.insertContent('<hr>');

packages/super-editor/src/extensions/field-annotation/cleanup-commands/cleanUpParagraphWithAnnotation.test.js

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ if (!Object.getOwnPropertyDescriptor(PMNode.prototype, 'children')) {
1212
});
1313
}
1414

15+
// Controllable mock — set mockAnnotations before each test to control return value
16+
let mockAnnotations = [];
17+
18+
vi.mock('../fieldAnnotationHelpers/index.js', () => ({
19+
findFieldAnnotationsByFieldId: vi.fn(() => mockAnnotations),
20+
}));
21+
22+
// Import AFTER vi.mock so the command picks up the hoisted mock
23+
const { cleanUpParagraphWithAnnotations } = await import('./index.js');
24+
1525
// Local helpers
1626
const schema = basic;
1727
const p = schema.nodes.paragraph;
@@ -24,27 +34,19 @@ const makeAnnotationAt = (state, pos) => {
2434
};
2535
const hardBreak = schema.nodes.hard_break;
2636
const textContent = (docNode) => docNode.textContent;
27-
const mockHelperPath = '../fieldAnnotationHelpers/index.js';
2837

29-
describe('cleanUpParagraphWithAnnotations - test range error crash', () => {
30-
beforeEach(() => {
31-
vi.resetModules();
32-
});
38+
beforeEach(() => {
39+
mockAnnotations = [];
40+
});
3341

42+
describe('cleanUpParagraphWithAnnotations - test range error crash', () => {
3443
/** Test to fix error in position out of range in original */
35-
it('throws RangeError "Position … out of range" on single-paragraph doc', async () => {
44+
it('throws RangeError "Position … out of range" on single-paragraph doc', () => {
3645
const doc = schema.node('doc', null, [p.createAndFill(null, [text('A')])]);
3746
const state = makeState(doc);
3847
const tr = state.tr;
3948

40-
const ann = makeAnnotationAt(state, 1);
41-
42-
vi.doMock('../fieldAnnotationHelpers/index.js', () => ({
43-
findFieldAnnotationsByFieldId: vi.fn(() => [ann]),
44-
}));
45-
46-
// Import AFTER mocking so the command picks up the mocked helper
47-
const { cleanUpParagraphWithAnnotations } = await import('./index.js');
49+
mockAnnotations = [makeAnnotationAt(state, 1)];
4850

4951
const cmd = cleanUpParagraphWithAnnotations(['field-x']);
5052
const run = () => cmd({ dispatch: () => {}, tr, state });
@@ -54,11 +56,7 @@ describe('cleanUpParagraphWithAnnotations - test range error crash', () => {
5456
});
5557

5658
describe('cleanUpParagraphWithAnnotations – original behavior', () => {
57-
beforeEach(() => {
58-
vi.resetModules();
59-
});
60-
61-
it('deletes a single-child paragraph (non-last) annotated node', async () => {
59+
it('deletes a single-child paragraph (non-last) annotated node', () => {
6260
// doc: [ p("REMOVE_ME"), p("keep this") ]
6361
const doc = schema.node('doc', null, [
6462
p.createAndFill(null, [text('REMOVE_ME')]),
@@ -68,13 +66,8 @@ describe('cleanUpParagraphWithAnnotations – original behavior', () => {
6866
const tr = state.tr;
6967

7068
// Annotate inside the first paragraph
71-
const ann = makeAnnotationAt(state, 1);
69+
mockAnnotations = [makeAnnotationAt(state, 1)];
7270

73-
vi.doMock(mockHelperPath, () => ({
74-
findFieldAnnotationsByFieldId: vi.fn(() => [ann]),
75-
}));
76-
77-
const { cleanUpParagraphWithAnnotations } = await import('./index.js');
7871
const cmd = cleanUpParagraphWithAnnotations(['field-x']);
7972

8073
// no-op dispatch is fine; we inspect tr afterwards
@@ -89,20 +82,15 @@ describe('cleanUpParagraphWithAnnotations – original behavior', () => {
8982
expect(textContent(after)).not.toMatch(/REMOVE_ME/);
9083
});
9184

92-
it('no-ops when parent has >= 2 inline children (e.g., text + hardBreak + text)', async () => {
85+
it('no-ops when parent has >= 2 inline children (e.g., text + hardBreak + text)', () => {
9386
// p("A", <br/>, "B") -> childCount >= 2 -> guard should fail -> no deletion
9487
const para = p.createAndFill(null, [text('A'), hardBreak.create(), text('B')]);
9588
const doc = schema.node('doc', null, [para]);
9689
const state = makeState(doc);
9790
const tr = state.tr;
9891

99-
const ann = makeAnnotationAt(state, 1);
100-
101-
vi.doMock(mockHelperPath, () => ({
102-
findFieldAnnotationsByFieldId: vi.fn(() => [ann]),
103-
}));
92+
mockAnnotations = [makeAnnotationAt(state, 1)];
10493

105-
const { cleanUpParagraphWithAnnotations } = await import('./index.js');
10694
const cmd = cleanUpParagraphWithAnnotations(['field-x']);
10795
const run = () => cmd({ dispatch: () => {}, tr, state });
10896

@@ -111,21 +99,16 @@ describe('cleanUpParagraphWithAnnotations – original behavior', () => {
11199
expect(textContent(tr.doc)).toContain('AB');
112100
});
113101

114-
it('no-ops when the annotation node does not equal the current node at mapped position', async () => {
102+
it('no-ops when the annotation node does not equal the current node at mapped position', () => {
115103
// doc: [ p("X"), p("Y") ]
116104
const doc = schema.node('doc', null, [p.createAndFill(null, [text('X')]), p.createAndFill(null, [text('Y')])]);
117105
const state = makeState(doc);
118106
const tr = state.tr;
119107

120108
// Build an "annotation" object whose node DOES NOT equal the current node at pos 1
121109
// We fake it by using a different node instance/type (paragraph node) so node.eq(currentNode) is false.
122-
const badAnn = { pos: 1, node: state.doc.child(0) /* paragraph, not text node */ };
123-
124-
vi.doMock(mockHelperPath, () => ({
125-
findFieldAnnotationsByFieldId: vi.fn(() => [badAnn]),
126-
}));
110+
mockAnnotations = [{ pos: 1, node: state.doc.child(0) /* paragraph, not text node */ }];
127111

128-
const { cleanUpParagraphWithAnnotations } = await import('./index.js');
129112
const cmd = cleanUpParagraphWithAnnotations(['field-x']);
130113
const run = () => cmd({ dispatch: () => {}, tr, state });
131114

@@ -134,7 +117,7 @@ describe('cleanUpParagraphWithAnnotations – original behavior', () => {
134117
expect(textContent(tr.doc)).toMatch(/^XY$/);
135118
});
136119

137-
it('handles multiple annotations by queuing and deleting them in descending order', async () => {
120+
it('handles multiple annotations by queuing and deleting them in descending order', () => {
138121
// doc: [ p("FIRST"), p("MID"), p("SECOND") ]
139122
// Annotate inside FIRST and SECOND (both single-child paragraphs).
140123
const first = p.createAndFill(null, [text('FIRST')]);
@@ -154,11 +137,8 @@ describe('cleanUpParagraphWithAnnotations – original behavior', () => {
154137
const ann1 = makeAnnotationAt(state, 1);
155138
const ann2 = makeAnnotationAt(state, secondTextPos);
156139

157-
vi.doMock(mockHelperPath, () => ({
158-
findFieldAnnotationsByFieldId: vi.fn(() => [ann1, ann2]),
159-
}));
140+
mockAnnotations = [ann1, ann2];
160141

161-
const { cleanUpParagraphWithAnnotations } = await import('./index.js');
162142
const cmd = cleanUpParagraphWithAnnotations(['field-x']);
163143
const run = () => cmd({ dispatch: () => {}, tr, state });
164144

0 commit comments

Comments
 (0)