From 4a35430f28b30e59d114d8de54ed8c9e6614edee Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 12 Feb 2023 21:25:51 -0500 Subject: [PATCH] Switch tests to Vitest --- .github/workflows/test.yaml | 13 ++---- jest.config.cjs | 19 -------- test/applyMiddleware.spec.ts | 41 ++++++++--------- test/combineReducers.spec.ts | 9 ++-- test/createStore.spec.ts | 79 +++++++++++++++++++------------- test/tsconfig.json | 1 + test/typescript/tsconfig.json | 1 + test/utils/isPlainObject.spec.ts | 1 - test/utils/warning.spec.ts | 3 +- tsconfig.json | 1 + vitest.config.ts | 17 +++++++ 11 files changed, 98 insertions(+), 87 deletions(-) delete mode 100644 jest.config.cjs create mode 100644 vitest.config.ts diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bbf8bcb84a..7700ea1759 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,7 +6,7 @@ jobs: name: Check for changes runs-on: ubuntu-latest outputs: - toolkit: ${{ steps.filter.outputs.toolkit }} + src: ${{ steps.filter.outputs.src }} steps: - uses: actions/checkout@v2 - uses: dorny/paths-filter@v2 @@ -15,10 +15,11 @@ jobs: filters: | src: - 'src/**' + - 'test/**' build: needs: changes - #if: ${{ needs.changes.outputs.src == 'true' }} + if: ${{ needs.changes.outputs.src == 'true' }} name: Lint, Test, Build & Pack on Node ${{ matrix.node }} @@ -82,13 +83,7 @@ jobs: - name: Install build artifact run: yarn add ./package.tgz - - run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.json ./jest.config.cjs ./test/tsconfig.json ./test/typescript/tsconfig.json - - - run: cat ./tsconfig.json - - run: cat ./test/tsconfig.json - - run: cat ./test/typescript/tsconfig.json - - - run: ls -lah ./node_modules/redux + - run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.json ./vitest.config.ts ./test/tsconfig.json ./test/typescript/tsconfig.json - name: Run tests, against dist run: yarn test diff --git a/jest.config.cjs b/jest.config.cjs deleted file mode 100644 index 75ea0a03ac..0000000000 --- a/jest.config.cjs +++ /dev/null @@ -1,19 +0,0 @@ -/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testRegex: '(/test/.*\\.spec\\.ts)$', - coverageProvider: 'v8', - moduleNameMapper: { - '^redux$': '/src/index.ts' // @remap-prod-remove-line - '^@internal/(.*)$': '/src/$1' - }, - transform: { - '\\.ts$': [ - 'ts-jest', - { - tsconfig: './test/tsconfig.json' - } - ] - } -} diff --git a/test/applyMiddleware.spec.ts b/test/applyMiddleware.spec.ts index 6dec6e739d..b5437c97ba 100644 --- a/test/applyMiddleware.spec.ts +++ b/test/applyMiddleware.spec.ts @@ -8,6 +8,7 @@ import { Store, Dispatch } from 'redux' +import { vi } from 'vitest' import * as reducers from './helpers/reducers' import { addTodo, addTodoAsync, addTodoIfEmpty } from './helpers/actionCreators' import { thunk } from './helpers/middleware' @@ -34,7 +35,7 @@ describe('applyMiddleware', () => { } } - const spy = jest.fn() + const spy = vi.fn() const store = applyMiddleware(test(spy), thunk)(createStore)(reducers.todos) store.dispatch(addTodo('Use Redux')) @@ -59,7 +60,7 @@ describe('applyMiddleware', () => { } } - const spy = jest.fn() + const spy = vi.fn() const store = applyMiddleware(test(spy), thunk)(createStore)(reducers.todos) // the typing for redux-thunk is super complex, so we will use an as unknown hack @@ -71,7 +72,7 @@ describe('applyMiddleware', () => { }) }) - it('works with thunk middleware', done => { + it('works with thunk middleware', async () => { const store = applyMiddleware(thunk)(createStore)(reducers.todos) store.dispatch(addTodoIfEmpty('Hello') as any) @@ -106,27 +107,25 @@ describe('applyMiddleware', () => { const dispatchedValue = store.dispatch( addTodoAsync('Maybe') as any ) as unknown as Promise - dispatchedValue.then(() => { - expect(store.getState()).toEqual([ - { - id: 1, - text: 'Hello' - }, - { - id: 2, - text: 'World' - }, - { - id: 3, - text: 'Maybe' - } - ]) - done() - }) + await dispatchedValue + expect(store.getState()).toEqual([ + { + id: 1, + text: 'Hello' + }, + { + id: 2, + text: 'World' + }, + { + id: 3, + text: 'Maybe' + } + ]) }) it('passes through all arguments of dispatch calls from within middleware', () => { - const spy = jest.fn() + const spy = vi.fn() const testCallArgs = ['test'] interface MultiDispatch { diff --git a/test/combineReducers.spec.ts b/test/combineReducers.spec.ts index e05382324f..53dc6fb56a 100644 --- a/test/combineReducers.spec.ts +++ b/test/combineReducers.spec.ts @@ -6,6 +6,7 @@ import { AnyAction, __DO_NOT_USE__ActionTypes as ActionTypes } from 'redux' +import { vi } from 'vitest' describe('Utils', () => { describe('combineReducers', () => { @@ -39,7 +40,7 @@ describe('Utils', () => { it('warns if a reducer prop is undefined', () => { const preSpy = console.error - const spy = jest.fn() + const spy = vi.fn() console.error = spy let isNotDefined: any @@ -199,7 +200,7 @@ describe('Utils', () => { it('warns if no reducers are passed to combineReducers', () => { const preSpy = console.error - const spy = jest.fn() + const spy = vi.fn() console.error = spy const reducer = combineReducers({}) @@ -214,7 +215,7 @@ describe('Utils', () => { it('warns if input state does not match reducer shape', () => { const preSpy = console.error - const spy = jest.fn() + const spy = vi.fn() const nullAction = undefined as unknown as AnyAction console.error = spy @@ -287,7 +288,7 @@ describe('Utils', () => { it('only warns for unexpected keys once', () => { const preSpy = console.error - const spy = jest.fn() + const spy = vi.fn() console.error = spy const nullAction = { type: '' } diff --git a/test/createStore.spec.ts b/test/createStore.spec.ts index 4ab698db56..ec708b8d76 100644 --- a/test/createStore.spec.ts +++ b/test/createStore.spec.ts @@ -5,6 +5,7 @@ import { Action, Store } from 'redux' +import { vi } from 'vitest' import { addTodo, dispatchInMiddle, @@ -205,8 +206,8 @@ describe('createStore', () => { it('supports multiple subscriptions', () => { const store = createStore(reducers.todos) - const listenerA = jest.fn() - const listenerB = jest.fn() + const listenerA = vi.fn() + const listenerB = vi.fn() let unsubscribeA = store.subscribe(listenerA) store.dispatch(unknownAction()) @@ -252,8 +253,8 @@ describe('createStore', () => { it('only removes listener once when unsubscribe is called', () => { const store = createStore(reducers.todos) - const listenerA = jest.fn() - const listenerB = jest.fn() + const listenerA = vi.fn() + const listenerB = vi.fn() const unsubscribeA = store.subscribe(listenerA) store.subscribe(listenerB) @@ -268,7 +269,7 @@ describe('createStore', () => { it('only removes relevant listener when unsubscribe is called', () => { const store = createStore(reducers.todos) - const listener = jest.fn() + const listener = vi.fn() store.subscribe(listener) const unsubscribeSecond = store.subscribe(listener) @@ -282,9 +283,9 @@ describe('createStore', () => { it('supports removing a subscription within a subscription', () => { const store = createStore(reducers.todos) - const listenerA = jest.fn() - const listenerB = jest.fn() - const listenerC = jest.fn() + const listenerA = vi.fn() + const listenerB = vi.fn() + const listenerC = vi.fn() store.subscribe(listenerA) const unSubB = store.subscribe(() => { @@ -308,9 +309,9 @@ describe('createStore', () => { const doUnsubscribeAll = () => unsubscribeHandles.forEach(unsubscribe => unsubscribe()) - const listener1 = jest.fn() - const listener2 = jest.fn() - const listener3 = jest.fn() + const listener1 = vi.fn() + const listener2 = vi.fn() + const listener3 = vi.fn() unsubscribeHandles.push(store.subscribe(() => listener1())) unsubscribeHandles.push( @@ -335,9 +336,9 @@ describe('createStore', () => { it('notifies only subscribers active at the moment of current dispatch', () => { const store = createStore(reducers.todos) - const listener1 = jest.fn() - const listener2 = jest.fn() - const listener3 = jest.fn() + const listener1 = vi.fn() + const listener2 = vi.fn() + const listener3 = vi.fn() let listener3Added = false const maybeAddThirdListener = () => { @@ -367,10 +368,10 @@ describe('createStore', () => { it('uses the last snapshot of subscribers during nested dispatch', () => { const store = createStore(reducers.todos) - const listener1 = jest.fn() - const listener2 = jest.fn() - const listener3 = jest.fn() - const listener4 = jest.fn() + const listener1 = vi.fn() + const listener2 = vi.fn() + const listener3 = vi.fn() + const listener4 = vi.fn() let unsubscribe4: any const unsubscribe1 = store.subscribe(() => { @@ -406,27 +407,41 @@ describe('createStore', () => { expect(listener4.mock.calls.length).toBe(1) }) - it('provides an up-to-date state when a subscriber is notified', done => { + it('provides an up-to-date state when a subscriber is notified', async () => { const store = createStore(reducers.todos) + + let resolve: (value: unknown) => void = () => {} + let promise = new Promise(_resolve => { + resolve = _resolve + }) store.subscribe(() => { - expect(store.getState()).toEqual([ - { - id: 1, - text: 'Hello' - } - ]) - done() + resolve(store.getState()) }) store.dispatch(addTodo('Hello')) + const state = await promise + + expect(state).toEqual([ + { + id: 1, + text: 'Hello' + } + ]) }) - it('does not leak private listeners array', done => { + it('does not leak private listeners array', async () => { const store = createStore(reducers.todos) + let resolve: (value: unknown) => void = () => {} + let promise = new Promise(_resolve => { + resolve = _resolve + }) + store.subscribe(function (this: any) { - expect(this).toBe(undefined) - done() + resolve(this) }) store.dispatch(addTodo('Hello')) + const result = await promise + + expect(result).toBe(undefined) }) it('only accepts plain object actions', () => { @@ -575,7 +590,7 @@ describe('createStore', () => { const vanillaStore = vanillaCreateStore(...args) return { ...vanillaStore, - dispatch: jest.fn(vanillaStore.dispatch) + dispatch: vi.fn(vanillaStore.dispatch) } } @@ -601,7 +616,7 @@ describe('createStore', () => { const vanillaStore = vanillaCreateStore(...args) return { ...vanillaStore, - dispatch: jest.fn(vanillaStore.dispatch) + dispatch: vi.fn(vanillaStore.dispatch) } } @@ -827,7 +842,7 @@ describe('createStore', () => { it('does not log an error if parts of the current state will be ignored by a nextReducer using combineReducers', () => { const originalConsoleError = console.error - console.error = jest.fn() + console.error = vi.fn() const store = createStore( combineReducers<{ x?: number; y: { z: number; w?: number } }>({ diff --git a/test/tsconfig.json b/test/tsconfig.json index 4b307b4845..1a3c22c7e1 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -14,6 +14,7 @@ "skipLibCheck": true, "noImplicitReturns": false, "noUnusedLocals": false, + "types": ["vitest/globals"], "paths": { "redux": ["../src/index.ts"], // @remap-prod-remove-line "@internal/*": ["../src/*"] diff --git a/test/typescript/tsconfig.json b/test/typescript/tsconfig.json index 1e665a92b3..dd2a6c5f1d 100644 --- a/test/typescript/tsconfig.json +++ b/test/typescript/tsconfig.json @@ -15,6 +15,7 @@ "noImplicitReturns": false, "noUnusedLocals": false, "noUnusedParameters": false, + "types": ["vitest/globals"], "paths": { "redux": ["../../src/index.ts"], // @remap-prod-remove-line "@internal/*": ["../../src/*"] diff --git a/test/utils/isPlainObject.spec.ts b/test/utils/isPlainObject.spec.ts index 17f22127f2..62fe216152 100644 --- a/test/utils/isPlainObject.spec.ts +++ b/test/utils/isPlainObject.spec.ts @@ -1,4 +1,3 @@ -import { expect } from 'expect' import isPlainObject from '@internal/utils/isPlainObject' import vm from 'vm' diff --git a/test/utils/warning.spec.ts b/test/utils/warning.spec.ts index 2a366678af..59ca8a175b 100644 --- a/test/utils/warning.spec.ts +++ b/test/utils/warning.spec.ts @@ -1,11 +1,12 @@ /* eslint-disable no-console */ +import { vi } from 'vitest' import warning from '@internal/utils/warning' describe('Utils', () => { describe('warning', () => { it('calls console.error when available', () => { const preSpy = console.error - const spy = jest.fn() + const spy = vi.fn() console.error = spy try { warning('Test') diff --git a/tsconfig.json b/tsconfig.json index 74f97aad21..4c60e03f99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "noUnusedLocals": true, "noUnusedParameters": true, "baseUrl": "./", + "types": ["vitest/globals"], "paths": { "redux": ["src/index.ts"], // @remap-prod-remove-line "@internal/*": ["src/*"] diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000000..748ef7f31f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + include: ['./test/**/*.(spec|test).[jt]s?(x)'], + alias: { + redux: './src/index.ts', // @remap-prod-remove-line + + // this mapping is disabled as we want `dist` imports in the tests only to be used for "type-only" imports which don't play a role for jest + '@internal/': './src/' + }, + deps: { + interopDefault: true + } + } +})