diff --git a/.babelrc.js b/.babelrc.js index e257fc61..c2a4e9d0 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -4,7 +4,13 @@ module.exports = { presets: [ [ - "@babel/preset-env" + "@babel/preset-env", + { + targets: { + esmodules: true, + }, + }, ], + '@babel/preset-typescript' ], } diff --git a/README.md b/README.md index 9a963053..9f55ea0b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ I ([@ckalika](https://github.com/ckalika)) spoke with [@rt2zz](https://github.co 5. Improve testing and automation - [x] Move to GitHub Actions - - [ ] Move from Ava to Jest + - [x] Move from Ava to Jest There's a lot to do here, so I'll ask your patience and understanding as I work through it. If you have ideas for how to improve the library, the documentation, or the community, I'd love to hear them, and if you're submitting pull requests (or have submitted some previously), please reach out and help me understand what you're aiming to do with it. diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..00767440 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + testTimeout: 10000, +} diff --git a/package.json b/package.json index b59b0556..08b1b2cb 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "README.md" ], "scripts": { - "ava": "ava", "build": "npm run build:commonjs && npm run build:es && npm run build:umd", "build:commonjs": "tsc --module commonjs --outDir lib", "build:es": "tsc --module es2015 --outDir es", @@ -24,7 +23,7 @@ "prepare": "npm run build", "precommit": "lint-staged", "stats:size": "node ./scripts/size-estimator.js", - "test": "ava", + "test": "jest", "version": "npm run clean && npm run build && npm run stats:size | tail -1 >> LIBSIZE.md && git add LIBSIZE.md" }, "lint-staged": { @@ -36,33 +35,23 @@ "author": "", "license": "MIT", "homepage": "https://github.com/rt2zz/redux-persist#readme", - "ava": { - "files": [ - "tests/**/*.spec.ts" - ], - "extensions": [ - "ts" - ], - "require": [ - "ts-node/register" - ] - }, "devDependencies": { "@babel/core": "^7.15.0", "@babel/preset-env": "^7.15.0", + "@babel/preset-typescript": "^7.16.7", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^20.0.0", "@rollup/plugin-node-resolve": "^13.0.4", "@rollup/plugin-typescript": "^8.2.5", + "@types/jest": "^27.4.0", "@types/react": "^17.0.16", "@types/redux-mock-store": "^1.0.3", - "@types/sinon": "^10.0.2", "@typescript-eslint/eslint-plugin": "^4.29.0", "@typescript-eslint/parser": "^4.29.0", - "ava": "^3.15.0", "eslint": "^7.32.0", "eslint-plugin-import": "^2.23.4", "husky": "^7.0.1", + "jest": "^27.4.7", "lint-staged": "^11.1.2", "prettier": "^2.3.2", "redux": "^4.1.1", diff --git a/tests/complete.spec.ts b/tests/complete.spec.ts index 5f4ca85b..7a724e90 100644 --- a/tests/complete.spec.ts +++ b/tests/complete.spec.ts @@ -1,4 +1,3 @@ -import test from 'ava' import { combineReducers, createStore } from 'redux' import persistReducer from '../src/persistReducer' @@ -15,43 +14,43 @@ const config = { timeout: 5, } -test('multiple persistReducers work together', t => { - return new Promise((resolve) => { +test('multiple persistReducers work together', async () => { + return new Promise((resolve) => { const r1 = persistReducer(config, reducer) const r2 = persistReducer(config, reducer) const rootReducer = combineReducers({ r1, r2 }) const store = createStore(rootReducer) const persistor = persistStore(store, {}, () => { - t.is(persistor.getState().bootstrapped, true) - resolve() + expect(persistor.getState().bootstrapped).toBe(true) + resolve() }) }) }) -test('persistStore timeout 0 never bootstraps', t => { - return new Promise((resolve, reject) => { +test('persistStore timeout 0 never bootstraps', async () => { + return new Promise((resolve, reject) => { const r1 = persistReducer({...config, storage: brokenStorage, timeout: 0}, reducer) const rootReducer = combineReducers({ r1 }) const store = createStore(rootReducer) const persistor = persistStore(store, undefined, () => { console.log('resolve') - reject() + reject() }) setTimeout(() => { - t.is(persistor.getState().bootstrapped, false) + expect(persistor.getState().bootstrapped).toBe(false) resolve() }, 10) }) }) -test('persistStore timeout forces bootstrap', t => { - return new Promise((resolve, reject) => { +test('persistStore timeout forces bootstrap', async () => { + return new Promise((resolve, reject) => { const r1 = persistReducer({...config, storage: brokenStorage}, reducer) const rootReducer = combineReducers({ r1 }) const store = createStore(rootReducer) const persistor = persistStore(store, undefined, () => { - t.is(persistor.getState().bootstrapped, true) + expect(persistor.getState().bootstrapped).toBe(true) resolve() }) setTimeout(() => { diff --git a/tests/createPersistor.spec.ts b/tests/createPersistor.spec.ts index d805b302..b1c1f288 100644 --- a/tests/createPersistor.spec.ts +++ b/tests/createPersistor.spec.ts @@ -1,5 +1,3 @@ -import test from 'ava' -import sinon from 'sinon' import createMemoryStorage from './utils/createMemoryStorage' import createPersistoid from '../src/createPersistoid' const memoryStorage = createMemoryStorage() @@ -11,49 +9,48 @@ const config = { debug: true } -let spy: sinon.SinonSpy; -let clock: sinon.SinonFakeTimers; +let spy: jest.SpyInstance; +let clock: typeof jest; -test.beforeEach(() => { - spy = sinon.spy(memoryStorage, 'setItem') - clock = sinon.useFakeTimers() +beforeEach(() => { + spy = jest.spyOn(memoryStorage, 'setItem') + clock = jest.useFakeTimers() }); -test.afterEach(() => { - spy.restore() - clock.restore() +afterEach(() => { + spy.mockRestore() + clock.useRealTimers() }); -// @NOTE these tests broke when updating sinon -test.skip('it updates changed state', t => { +test('it updates changed state', () => { const { update } = createPersistoid(config) update({ a: 1 }) - clock.tick(1); + clock.runAllTimers() update({ a: 2 }) - clock.tick(1); - t.true(spy.calledTwice); - t.true(spy.withArgs('persist:persist-reducer-test', '{"a":"1"}').calledOnce); - t.true(spy.withArgs('persist:persist-reducer-test', '{"a":"2"}').calledOnce); + clock.runAllTimers() + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenNthCalledWith(1, 'persist:persist-reducer-test', '{"a":"1"}'); + expect(spy).toHaveBeenNthCalledWith(2, 'persist:persist-reducer-test', '{"a":"2"}'); }) -test.skip('it does not update unchanged state', t => { +test('it does not update unchanged state', () => { const { update } = createPersistoid(config) update({ a: undefined, b: 1 }) - clock.tick(1); + clock.runAllTimers() // This update should not cause a write. update({ a: undefined, b: 1 }) - clock.tick(1); - t.true(spy.calledOnce); - t.true(spy.withArgs('persist:persist-reducer-test', '{"b":"1"}').calledOnce); + clock.runAllTimers() + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('persist:persist-reducer-test', '{"b":"1"}'); }) -test.skip('it updates removed keys', t => { +test('it updates removed keys', () => { const { update } = createPersistoid(config) update({ a: undefined, b: 1 }) - clock.tick(1); + clock.runAllTimers(); update({ a: undefined, b: undefined }) - clock.tick(1); - t.true(spy.calledTwice); - t.true(spy.withArgs('persist:persist-reducer-test', '{"b":"1"}').calledOnce); - t.true(spy.withArgs('persist:persist-reducer-test', '{}').calledOnce); + clock.runAllTimers(); + expect(spy).toHaveBeenCalledTimes(2) + expect(spy).toHaveBeenNthCalledWith(1,'persist:persist-reducer-test', '{"b":"1"}') + expect(spy).toHaveBeenNthCalledWith(2,'persist:persist-reducer-test', '{}') }) diff --git a/tests/flush.spec.ts b/tests/flush.spec.ts index 607c2226..10eae1c6 100644 --- a/tests/flush.spec.ts +++ b/tests/flush.spec.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import test from 'ava' import { createStore } from 'redux' import getStoredState from '../src/getStoredState' @@ -35,7 +33,7 @@ const config = { throttle: 1000, } -test('state before flush is not updated, after flush is', t => { +test('state before flush is not updated, after flush is', () => { return new Promise((resolve) => { const rootReducer = persistReducer(config, reducer) const store = createStore(rootReducer) @@ -43,10 +41,10 @@ test('state before flush is not updated, after flush is', t => { store.dispatch({ type: INCREMENT }) const state = store.getState() const storedPreFlush = await getStoredState(config) - t.not(storedPreFlush && storedPreFlush.c, state.c) + expect(storedPreFlush && storedPreFlush.c).not.toBe(state.c) await persistor.flush() const storedPostFlush = await getStoredState(config) - resolve(t.is(storedPostFlush && storedPostFlush.c, state.c)) + resolve(expect(storedPostFlush && storedPostFlush.c).toBe(state.c)) }) }) }) diff --git a/tests/persistCombineReducers.spec.ts b/tests/persistCombineReducers.spec.ts index caa99968..b292cf4d 100644 --- a/tests/persistCombineReducers.spec.ts +++ b/tests/persistCombineReducers.spec.ts @@ -1,23 +1,21 @@ import persistCombineReducers from '../src/persistCombineReducers' import createMemoryStorage from './utils/createMemoryStorage' -import test from 'ava' - const config = { key: 'TestConfig', storage: createMemoryStorage() } -test('persistCombineReducers returns a function', t => { +test('persistCombineReducers returns a function', () => { const reducer = persistCombineReducers(config, { foo: () => ({}) }) - t.is(typeof reducer, 'function') + expect(typeof reducer).toBe('function'); }) /* test.skip('persistCombineReducers merges two levels deep of state', t => { - + }) */ diff --git a/tests/persistReducer.spec.ts b/tests/persistReducer.spec.ts index 330a8829..708d6e25 100644 --- a/tests/persistReducer.spec.ts +++ b/tests/persistReducer.spec.ts @@ -1,6 +1,3 @@ -import test from 'ava' -import sinon from 'sinon' - import persistReducer from '../src/persistReducer' import createMemoryStorage from './utils/createMemoryStorage' import { PERSIST } from '../src/constants' @@ -13,27 +10,27 @@ const config = { storage: createMemoryStorage() } -test('persistedReducer does not automatically set _persist state', t => { +test('persistedReducer does not automatically set _persist state', () => { const persistedReducer = persistReducer(config, reducer) const state = persistedReducer({}, {type: "UNDEFINED"}) console.log('state', state) - t.is(undefined, state._persist) + expect(state._persist).toBeUndefined() }) -test('persistedReducer does returns versioned, rehydrate tracked _persist state upon PERSIST', t => { +test('persistedReducer does returns versioned, rehydrate tracked _persist state upon PERSIST', () => { const persistedReducer = persistReducer(config, reducer) - const register = sinon.spy() - const rehydrate = sinon.spy() + const register = jest.fn() + const rehydrate = jest.fn() const state = persistedReducer({}, { type: PERSIST, register, rehydrate }) - t.deepEqual({ version: 1, rehydrated: false}, state._persist) + expect(state._persist).toEqual({ version: 1, rehydrated: false}) }) -test('persistedReducer calls register and rehydrate after PERSIST', async (t) => { +test('persistedReducer calls register and rehydrate after PERSIST', async () => { const persistedReducer = persistReducer(config, reducer) - const register = sinon.spy() - const rehydrate = sinon.spy() + const register = jest.fn() + const rehydrate = jest.fn() persistedReducer({}, { type: PERSIST, register, rehydrate }) await sleep(5000) - t.is(register.callCount, 1) - t.is(rehydrate.callCount, 1) + expect(register).toHaveBeenCalledTimes(1) + expect(rehydrate).toHaveBeenCalledTimes(1) }) diff --git a/tests/persistStore.spec.ts b/tests/persistStore.spec.ts index 8421a004..24f668e8 100644 --- a/tests/persistStore.spec.ts +++ b/tests/persistStore.spec.ts @@ -1,6 +1,3 @@ -import test from 'ava' -import sinon from 'sinon' - import configureStore from 'redux-mock-store' import persistStore from '../src/persistStore' @@ -9,34 +6,34 @@ import find from './utils/find' const mockStore = configureStore([]) -test('persistStore dispatches PERSIST action', t => { +test('persistStore dispatches PERSIST action', () => { const store = mockStore() persistStore(store) const actions = store.getActions() const persistAction = find(actions, { type: PERSIST }) - t.truthy(persistAction) + expect(persistAction).toBeTruthy() }) -test('register method adds a key to the registry', t => { +test('register method adds a key to the registry', () => { const store = mockStore() const persistor = persistStore(store) const actions = store.getActions() const persistAction = find(actions, { type: PERSIST }) persistAction.register('canary') - t.deepEqual(persistor.getState().registry, ['canary']) + expect(persistor.getState().registry).toEqual(['canary']) }) -test('rehydrate method fires with the expected shape', t => { +test('rehydrate method fires with the expected shape', () => { const store = mockStore() persistStore(store) const actions = store.getActions() const persistAction = find(actions, { type: PERSIST }) persistAction.rehydrate('canary', { foo: 'bar' }, null) const rehydrateAction = find(actions, { type: REHYDRATE }) - t.deepEqual(rehydrateAction, { type: REHYDRATE, key: 'canary', payload: { foo: 'bar' }, err: null }) + expect(rehydrateAction).toEqual({ type: REHYDRATE, key: 'canary', payload: { foo: 'bar' }, err: null }) }) -test('rehydrate method removes provided key from registry', t => { +test('rehydrate method removes provided key from registry', () => { const store = mockStore() const persistor = persistStore(store) const actions = store.getActions() @@ -44,14 +41,14 @@ test('rehydrate method removes provided key from registry', t => { // register canary persistAction.register('canary') - t.deepEqual(persistor.getState().registry, ['canary']) + expect(persistor.getState().registry).toEqual(['canary']) // rehydrate canary persistAction.rehydrate('canary', { foo: 'bar' }, null) - t.deepEqual(persistor.getState().registry, []) + expect(persistor.getState().registry).toEqual([]) }) -test('rehydrate method removes exactly one of provided key from registry', t => { +test('rehydrate method removes exactly one of provided key from registry', () => { const store = mockStore() const persistor = persistStore(store) const actions = store.getActions() @@ -60,55 +57,55 @@ test('rehydrate method removes exactly one of provided key from registry', t => // register canary twice persistAction.register('canary') persistAction.register('canary') - t.deepEqual(persistor.getState().registry, ['canary', 'canary']) + expect(persistor.getState().registry).toEqual(['canary', 'canary']) // rehydrate canary persistAction.rehydrate('canary', { foo: 'bar' }, null) - t.deepEqual(persistor.getState().registry, ['canary']) + expect(persistor.getState().registry).toEqual(['canary']) }) -test('once registry is cleared for first time, persistor is flagged as bootstrapped', t => { +test('once registry is cleared for first time, persistor is flagged as bootstrapped', () => { const store = mockStore() const persistor = persistStore(store) const actions = store.getActions() const persistAction = find(actions, { type: PERSIST }) persistAction.register('canary') - t.false(persistor.getState().bootstrapped) + expect(persistor.getState().bootstrapped).toBe(false) persistAction.rehydrate('canary', { foo: 'bar' }, null) - t.true(persistor.getState().bootstrapped) + expect(persistor.getState().bootstrapped).toBe(true) }) -test('once persistor is flagged as bootstrapped, further registry changes do not affect this value', t => { +test('once persistor is flagged as bootstrapped, further registry changes do not affect this value', () => { const store = mockStore() const persistor = persistStore(store) const actions = store.getActions() const persistAction = find(actions, { type: PERSIST }) persistAction.register('canary') - t.false(persistor.getState().bootstrapped) + expect(persistor.getState().bootstrapped).toBe(false) persistAction.rehydrate('canary', { foo: 'bar' }, null) - t.true(persistor.getState().bootstrapped) + expect(persistor.getState().bootstrapped).toBe(true) // add canary back, registry is updated but bootstrapped remains true persistAction.register('canary') - t.deepEqual(persistor.getState().registry, ['canary']) - t.true(persistor.getState().bootstrapped) + expect(persistor.getState().registry).toEqual(['canary']) + expect(persistor.getState().bootstrapped).toBe(true) }) -test('persistStore calls bootstrapped callback (at most once) if provided', t => { +test('persistStore calls bootstrapped callback (at most once) if provided', () => { const store = mockStore() - const bootstrappedCb = sinon.spy() + const bootstrappedCb = jest.fn() persistStore(store, {}, bootstrappedCb) const actions = store.getActions() const persistAction = find(actions, { type: PERSIST }) - + persistAction.register('canary') persistAction.rehydrate('canary', { foo: 'bar' }, null) - t.is(bootstrappedCb.callCount, 1) + expect(bootstrappedCb).toHaveBeenCalledTimes(1) // further rehydrates do not trigger the cb persistAction.register('canary') persistAction.rehydrate('canary', { foo: 'bar' }, null) - t.is(bootstrappedCb.callCount, 1) + expect(bootstrappedCb).toHaveBeenCalledTimes(1) })