From cb6633349ff8c9e0958689f3c49a8aacfc092bb6 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Wed, 15 May 2024 15:24:27 -0400 Subject: [PATCH] test(jest): add Jest to CI matrix (#372) --- .github/workflows/release.yml | 8 ++++++-- jest.config.js | 23 +++++++++++++++++++++++ package.json | 4 ++++ src/__tests__/_jest-setup.js | 9 +++++++++ src/__tests__/_jest-vitest-alias.js | 25 +++++++++++++++++++++++++ src/__tests__/cleanup.test.js | 2 +- src/__tests__/mount.test.js | 4 ++-- src/__tests__/rerender.test.js | 2 +- 8 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 jest.config.js create mode 100644 src/__tests__/_jest-setup.js create mode 100644 src/__tests__/_jest-vitest-alias.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be47107..27af7a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: matrix: node: ['16', '18', '20'] svelte: ['3', '4'] - test-runner: ['vitest:jsdom', 'vitest:happy-dom'] + test-runner: ['vitest:jsdom', 'vitest:happy-dom', 'jest'] experimental: [false] include: - node: '20' @@ -36,6 +36,10 @@ jobs: svelte: 'next' test-runner: 'vitest:happy-dom' experimental: true + - node: '20' + svelte: 'next' + test-runner: 'jest' + experimental: true steps: - name: ⬇️ Checkout repo @@ -55,7 +59,7 @@ jobs: run: npm run test:${{ matrix.test-runner }} - name: ▶️ Run type-checks - if: ${{ matrix.node == '20' && matrix.svelte == '4' }} + if: ${{ matrix.node == '20' && matrix.svelte == '4' && matrix.test-runner == 'vitest:jsdom' }} run: npm run types - name: ⬆️ Upload coverage report diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..d6b1fde --- /dev/null +++ b/jest.config.js @@ -0,0 +1,23 @@ +import { VERSION as SVELTE_VERSION } from 'svelte/compiler' + +const IS_SVELTE_5 = SVELTE_VERSION >= '5' + +export default { + testMatch: ['/src/__tests__/**/*.test.js'], + transform: { + '^.+\\.svelte$': 'svelte-jester', + }, + moduleFileExtensions: ['js', 'svelte'], + extensionsToTreatAsEsm: ['.svelte'], + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/__tests__/_jest-setup.js'], + injectGlobals: false, + moduleNameMapper: { + '^vitest$': '/src/__tests__/_jest-vitest-alias.js', + '^@testing-library/svelte$': IS_SVELTE_5 + ? '/src/svelte5-index.js' + : '/src/index.js', + }, + resetMocks: true, + restoreMocks: true, +} diff --git a/package.json b/package.json index 795e117..b67db89 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "test:watch": "vitest", "test:vitest:jsdom": "vitest run --coverage --environment jsdom", "test:vitest:happy-dom": "vitest run --coverage --environment happy-dom", + "test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage", "types": "svelte-check", "validate": "npm-run-all test:vitest:* types", "contributors:add": "all-contributors add", @@ -89,6 +90,7 @@ "@testing-library/dom": "^10.0.0" }, "devDependencies": { + "@jest/globals": "^29.7.0", "@sveltejs/vite-plugin-svelte": "^3.0.2", "@testing-library/jest-dom": "^6.3.0", "@testing-library/user-event": "^14.5.2", @@ -109,6 +111,8 @@ "eslint-plugin-vitest-globals": "1.5.0", "expect-type": "^0.19.0", "happy-dom": "^14.7.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "jsdom": "^24.0.0", "npm-run-all": "^4.1.5", "prettier": "3.2.5", diff --git a/src/__tests__/_jest-setup.js b/src/__tests__/_jest-setup.js new file mode 100644 index 0000000..d1c255c --- /dev/null +++ b/src/__tests__/_jest-setup.js @@ -0,0 +1,9 @@ +import '@testing-library/jest-dom/jest-globals' + +import { afterEach } from '@jest/globals' +import { act, cleanup } from '@testing-library/svelte' + +afterEach(async () => { + await act() + cleanup() +}) diff --git a/src/__tests__/_jest-vitest-alias.js b/src/__tests__/_jest-vitest-alias.js new file mode 100644 index 0000000..6628c80 --- /dev/null +++ b/src/__tests__/_jest-vitest-alias.js @@ -0,0 +1,25 @@ +import { describe, jest, test } from '@jest/globals' + +export { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, + jest as vi, +} from '@jest/globals' + +// Add support for describe.skipIf and test.skipIf +describe.skipIf = (condition) => (condition ? describe.skip : describe) +test.skipIf = (condition) => (condition ? test.skip : test) + +// Add support for `stubGlobal` +jest.stubGlobal = (property, stub) => { + if (typeof stub === 'function') { + jest.spyOn(globalThis, property).mockImplementation(stub) + } else { + jest.replaceProperty(globalThis, property, stub) + } +} diff --git a/src/__tests__/cleanup.test.js b/src/__tests__/cleanup.test.js index 7131624..d0ae026 100644 --- a/src/__tests__/cleanup.test.js +++ b/src/__tests__/cleanup.test.js @@ -19,7 +19,7 @@ describe('cleanup', () => { renderSubject() cleanup() - expect(onDestroyed).toHaveBeenCalledOnce() + expect(onDestroyed).toHaveBeenCalledTimes(1) }) test('cleanup handles unexpected errors during mount', () => { diff --git a/src/__tests__/mount.test.js b/src/__tests__/mount.test.js index 48d985f..e25c429 100644 --- a/src/__tests__/mount.test.js +++ b/src/__tests__/mount.test.js @@ -15,7 +15,7 @@ describe('mount and destroy', () => { expect(content).toBeInTheDocument() await act() - expect(onMounted).toHaveBeenCalledOnce() + expect(onMounted).toHaveBeenCalledTimes(1) }) test('component is destroyed', async () => { @@ -28,6 +28,6 @@ describe('mount and destroy', () => { expect(content).not.toBeInTheDocument() await act() - expect(onDestroyed).toHaveBeenCalledOnce() + expect(onDestroyed).toHaveBeenCalledTimes(1) }) }) diff --git a/src/__tests__/rerender.test.js b/src/__tests__/rerender.test.js index ca4b8e8..21a782c 100644 --- a/src/__tests__/rerender.test.js +++ b/src/__tests__/rerender.test.js @@ -23,7 +23,7 @@ describe('rerender', () => { await rerender({ props: { name: 'Dolly' } }) expect(element).toHaveTextContent('Hello Dolly!') - expect(console.warn).toHaveBeenCalledOnce() + expect(console.warn).toHaveBeenCalledTimes(1) expect(console.warn).toHaveBeenCalledWith( expect.stringMatching(/deprecated/iu) )