diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 4c46dcef9..74498780c 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -90,6 +90,9 @@ jobs: - name: Run unit tests run: pnpm test + - name: Run slow tests + run: pnpm test:slow + run-e2e-tests: runs-on: ubuntu-latest container: diff --git a/package.json b/package.json index 3b55aa3ba..4047a22cc 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "packageManager": "pnpm@10.25.0", "scripts": { "test": "vitest run", + "test:bench": "VITEST_BENCH=true vitest run", + "test:slow": "vitest run --root ./packages/super-editor src/tests/editor/node-import-timing.test.js", "test:debug": "pnpm --prefix packages/super-editor run test:debug", "test:inspect": "NODE_OPTIONS=\"--inspect-brk=9229\" vitest --pool threads --poolOptions.threads.singleThread", "test:all": "vitest run", @@ -73,6 +75,7 @@ "eslint-import-resolver-typescript": "catalog:", "eslint-plugin-import-x": "catalog:", "eslint-plugin-jsdoc": "catalog:", + "happy-dom": "catalog:", "husky": "catalog:", "jsdom": "catalog:", "lint-staged": "catalog:", diff --git a/packages/layout-engine/contracts/vitest.config.mjs b/packages/layout-engine/contracts/vitest.config.mjs index b9bb27496..29bb879cd 100644 --- a/packages/layout-engine/contracts/vitest.config.mjs +++ b/packages/layout-engine/contracts/vitest.config.mjs @@ -4,7 +4,8 @@ import baseConfig from '../../../vitest.baseConfig'; export default defineConfig({ ...baseConfig, test: { - environment: 'jsdom', + // Use happy-dom for faster tests (set VITEST_DOM=jsdom to use jsdom) + environment: process.env.VITEST_DOM || 'happy-dom', include: ['src/**/*.test.ts'] } }); diff --git a/packages/layout-engine/layout-bridge/vitest.config.ts b/packages/layout-engine/layout-bridge/vitest.config.ts index 3e6e378a3..01c02b92b 100644 --- a/packages/layout-engine/layout-bridge/vitest.config.ts +++ b/packages/layout-engine/layout-bridge/vitest.config.ts @@ -1,11 +1,14 @@ import { defineConfig } from 'vitest/config'; import baseConfig from '../../../vitest.baseConfig'; +const includeBench = process.env.VITEST_BENCH === 'true'; + export default defineConfig({ ...baseConfig, test: { environment: 'node', - include: ['test/**/*.test.ts'], + include: includeBench ? ['test/**/performance*.test.ts'] : ['test/**/*.test.ts'], + exclude: includeBench ? [] : ['test/**/performance*.test.ts'], globals: true, }, }); diff --git a/packages/layout-engine/measuring/dom/vitest.config.mjs b/packages/layout-engine/measuring/dom/vitest.config.mjs index 1a1b03105..eccf6e4da 100644 --- a/packages/layout-engine/measuring/dom/vitest.config.mjs +++ b/packages/layout-engine/measuring/dom/vitest.config.mjs @@ -4,7 +4,8 @@ import baseConfig from '../../../../vitest.baseConfig'; export default defineConfig({ ...baseConfig, test: { - environment: 'jsdom', + // Use happy-dom for faster tests (set VITEST_DOM=jsdom to use jsdom) + environment: process.env.VITEST_DOM || 'happy-dom', include: ['src/**/*.test.ts'], setupFiles: ['./vitest.setup.ts'], }, diff --git a/packages/layout-engine/painters/dom/vitest.config.mjs b/packages/layout-engine/painters/dom/vitest.config.mjs index 74d39459a..522217db4 100644 --- a/packages/layout-engine/painters/dom/vitest.config.mjs +++ b/packages/layout-engine/painters/dom/vitest.config.mjs @@ -4,7 +4,8 @@ import baseConfig from '../../../../vitest.baseConfig'; export default defineConfig({ ...baseConfig, test: { - environment: 'jsdom', + // Use happy-dom for faster tests (set VITEST_DOM=jsdom to use jsdom) + environment: process.env.VITEST_DOM || 'happy-dom', include: ['src/**/*.test.ts'], }, }); diff --git a/packages/layout-engine/pm-adapter/src/integration.test.ts b/packages/layout-engine/pm-adapter/src/integration.test.ts index 4fd038ca3..b130e95c3 100644 --- a/packages/layout-engine/pm-adapter/src/integration.test.ts +++ b/packages/layout-engine/pm-adapter/src/integration.test.ts @@ -547,7 +547,9 @@ describe('PM → FlowBlock → Measure integration', () => { const fragment = mount.querySelector('.superdoc-fragment') as HTMLElement; const shadingLayer = fragment.querySelector('.superdoc-paragraph-shading') as HTMLElement; expect(shadingLayer).toBeTruthy(); - expect(shadingLayer.style.backgroundColor).toBe('rgb(170, 187, 204)'); + // Accept both rgb and hex formats (jsdom uses rgb, happy-dom uses hex) + const bgColor = shadingLayer.style.backgroundColor.toLowerCase(); + expect(bgColor === 'rgb(170, 187, 204)' || bgColor === '#aabbcc').toBe(true); document.body.removeChild(mount); }); diff --git a/packages/layout-engine/pm-adapter/vitest.config.mjs b/packages/layout-engine/pm-adapter/vitest.config.mjs index de74cddeb..09c648b90 100644 --- a/packages/layout-engine/pm-adapter/vitest.config.mjs +++ b/packages/layout-engine/pm-adapter/vitest.config.mjs @@ -5,7 +5,8 @@ import baseConfig from '../../../vitest.baseConfig'; export default defineConfig({ ...baseConfig, test: { - environment: 'jsdom', + // Use happy-dom for faster tests (set VITEST_DOM=jsdom to use jsdom) + environment: process.env.VITEST_DOM || 'happy-dom', include: ['src/**/*.test.ts'], setupFiles: [resolve(__dirname, './vitest.setup.ts')], }, diff --git a/packages/layout-engine/tests/vitest.config.mjs b/packages/layout-engine/tests/vitest.config.mjs index e8fdfb817..30c9b0189 100644 --- a/packages/layout-engine/tests/vitest.config.mjs +++ b/packages/layout-engine/tests/vitest.config.mjs @@ -1,11 +1,17 @@ import { defineConfig } from 'vitest/config'; import baseConfig from '../../../vitest.baseConfig'; +const includeBench = process.env.VITEST_BENCH === 'true'; + export default defineConfig({ ...baseConfig, test: { - environment: 'jsdom', - include: ['src/**/*.test.ts', 'src/**/*.bench.ts'], + // Use happy-dom for faster tests (set VITEST_DOM=jsdom to use jsdom) + environment: process.env.VITEST_DOM || 'happy-dom', + include: includeBench + ? ['src/**/*.bench.ts'] + : ['src/**/*.test.ts'], + exclude: includeBench ? [] : ['src/**/*.bench.ts'], setupFiles: ['./vitest.setup.ts'], coverage: { enabled: false, diff --git a/packages/super-editor/src/core/commands/insertContent.test.js b/packages/super-editor/src/core/commands/insertContent.test.js index ed1eea88e..39c1a220d 100644 --- a/packages/super-editor/src/core/commands/insertContent.test.js +++ b/packages/super-editor/src/core/commands/insertContent.test.js @@ -166,6 +166,11 @@ describe('insertContent', () => { // Integration-style tests that use a real Editor instance to // insert markdown/HTML lists and verify exported OOXML has list numbering. describe('insertContent (integration) list export', () => { + // Cache loaded DOCX data and helpers to avoid repeated file loading + let cachedDocxData = null; + let helpers = null; + let exportHelpers = null; + const getListParagraphs = (result) => { const body = result.elements?.find((el) => el.name === 'w:body'); const paragraphs = (body?.elements || []).filter((el) => el.name === 'w:p'); @@ -189,16 +194,25 @@ describe('insertContent (integration) list export', () => { vi.resetModules(); vi.doUnmock('../helpers/contentProcessor.js'); - const { loadTestDataForEditorTests, initTestEditor } = await import('../../tests/helpers/helpers.js'); - const { docx, media, mediaFiles, fonts } = await loadTestDataForEditorTests('blank-doc.docx'); - const { editor } = initTestEditor({ content: docx, media, mediaFiles, fonts, mode: 'docx' }); + // Cache helpers and DOCX data on first call + if (!helpers) { + helpers = await import('../../tests/helpers/helpers.js'); + } + if (!cachedDocxData) { + cachedDocxData = await helpers.loadTestDataForEditorTests('blank-doc.docx'); + } + if (!exportHelpers) { + exportHelpers = await import('../../tests/export/export-helpers/index.js'); + } + + const { docx, media, mediaFiles, fonts } = cachedDocxData; + const { editor } = helpers.initTestEditor({ content: docx, media, mediaFiles, fonts, mode: 'docx' }); return editor; }; const exportFromEditorContent = async (editor) => { - const { getExportedResultWithDocContent } = await import('../../tests/export/export-helpers/index.js'); const content = editor.getJSON().content || []; - return await getExportedResultWithDocContent(content); + return await exportHelpers.getExportedResultWithDocContent(content); }; it('exports ordered list from markdown with numId/ilvl', async () => { diff --git a/packages/super-editor/src/extensions/table/table.test.js b/packages/super-editor/src/extensions/table/table.test.js index 165c37a90..ecbbe3e56 100644 --- a/packages/super-editor/src/extensions/table/table.test.js +++ b/packages/super-editor/src/extensions/table/table.test.js @@ -1,9 +1,13 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; import { EditorState } from 'prosemirror-state'; import { loadTestDataForEditorTests, initTestEditor } from '@tests/helpers/helpers.js'; import { createTable } from './tableHelpers/createTable.js'; import { promises as fs } from 'fs'; +// Cache DOCX data to avoid repeated file loading +let cachedBlankDoc = null; +let cachedBordersDoc = null; + /** * Find the first table position within the provided document. * @param {import('prosemirror-model').Node} doc @@ -29,8 +33,14 @@ describe('Table commands', async () => { let templateBlockAttrs; let table; + // Load DOCX data once before all tests + beforeAll(async () => { + cachedBlankDoc = await loadTestDataForEditorTests('blank-doc.docx'); + cachedBordersDoc = await loadTestDataForEditorTests('SD-978-remove-table-borders.docx'); + }); + const setupTestTable = async () => { - let { docx, media, mediaFiles, fonts } = await loadTestDataForEditorTests('blank-doc.docx'); + const { docx, media, mediaFiles, fonts } = cachedBlankDoc; ({ editor } = initTestEditor({ content: docx, media, mediaFiles, fonts })); ({ schema } = editor); @@ -600,7 +610,7 @@ describe('Table commands', async () => { describe('table imported from docx', async () => { beforeEach(async () => { - let { docx, media, mediaFiles, fonts } = await loadTestDataForEditorTests('SD-978-remove-table-borders.docx'); + const { docx, media, mediaFiles, fonts } = cachedBordersDoc; ({ editor } = initTestEditor({ content: docx, media, mediaFiles, fonts })); tablePos = findTablePos(editor.state.doc); diff --git a/packages/super-editor/vite.config.js b/packages/super-editor/vite.config.js index e0036dddb..f4a446c5e 100644 --- a/packages/super-editor/vite.config.js +++ b/packages/super-editor/vite.config.js @@ -33,6 +33,7 @@ export default defineConfig(({ mode }) => { exclude: [ ...configDefaults.exclude, '**/*.spec.js', + '**/node-import-timing.test.js', // Slow test, run separately with test:slow ], coverage: { provider: 'v8', diff --git a/packages/superdoc/vite.config.js b/packages/superdoc/vite.config.js index 3ad599ea7..70b78dcdc 100644 --- a/packages/superdoc/vite.config.js +++ b/packages/superdoc/vite.config.js @@ -94,7 +94,8 @@ export default defineConfig(({ mode, command}) => { test: { name: projectLabel, globals: true, - environment: 'jsdom', + // Use happy-dom for faster tests (set VITEST_DOM=jsdom to use jsdom) + environment: process.env.VITEST_DOM || 'happy-dom', retry: 2, testTimeout: 20000, hookTimeout: 10000, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bddb2e276..292b3b4c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,6 +313,9 @@ importers: eslint-plugin-jsdoc: specifier: 'catalog:' version: 54.7.0(eslint@9.39.1(jiti@2.6.1)) + happy-dom: + specifier: ^20.3.4 + version: 20.3.4 husky: specifier: 'catalog:' version: 9.1.7