Skip to content

Commit

Permalink
Feat: Migrate to Jest (#598)
Browse files Browse the repository at this point in the history
  • Loading branch information
sseppola authored Mar 17, 2023
1 parent 8b2ee5a commit d13dbe1
Show file tree
Hide file tree
Showing 31 changed files with 2,672 additions and 1,281 deletions.
10 changes: 3 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,10 @@ jobs:
- run: yarn build

- run:
name: Test
name: Run tests and collect coverage
command: |
mkdir -p ~/reports/ava
yarn test --tap | ./node_modules/.bin/tap-xunit > ~/reports/ava/report.xml
- run:
name: Line coverage
command: yarn c8 report --reporter=text-lcov | npx coveralls
mkdir -p ~/reports/jest
yarn test:coverage --coverageDirectory=~/reports/jest
- store_test_results:
path: ~/reports
13 changes: 13 additions & 0 deletions babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"env": {
"test": {
"plugins": [["@babel/transform-runtime"]],
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
}
}
}
18 changes: 18 additions & 0 deletions jest-coverage.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import config from './jest.config.mjs';

const coverageConfig = {
...config,
collectCoverage: true,
collectCoverageFrom: [
'./src/**/*',
'!./src/types/*',
'!./src/**/types.ts',
'!./src/**/index.ts',
'!./src/**/*.tspec.ts',
],
coverageThreshold: {
global: { statements: 95, branches: 95 },
},
};

export default coverageConfig;
9 changes: 9 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const config = {
testEnvironment: 'node',
transform: {
'\\.([jt]sx?)$': ['babel-jest'],
},
collectCoverage: false,
};

export default config;
32 changes: 12 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@
"./dist"
],
"scripts": {
"pretest": "yarn tsc",
"test": "yarn c8 ava",
"test:watch": "yarn ava --watch",
"check-types": "yarn tsc --noEmit",
"test": "yarn test:unit-test",
"test:unit-test": "yarn jest",
"test:coverage": "yarn jest --config jest-coverage.config.mjs --rootDir .",
"test:watch": "yarn test:unit-test --watch",
"lint": "yarn eslint --ext .ts src -c .eslintrc.js",
"build": "rimraf dist && yarn tsc",
"watch": "yarn tsc -w",
Expand All @@ -53,16 +55,18 @@
"postpublish": "pinst --enable"
},
"devDependencies": {
"@ava/typescript": "3.0.1",
"@babel/plugin-transform-runtime": "7.18.0",
"@babel/preset-env": "7.16.5",
"@babel/preset-react": "7.16.5",
"@babel/preset-typescript": "7.16.5",
"@commitlint/cli": "17.1.2",
"@commitlint/config-conventional": "17.1.0",
"@types/jest": "28.1.8",
"@types/react": "18.0.20",
"@types/react-test-renderer": "18.0.0",
"@types/sinon": "10.0.13",
"@typescript-eslint/eslint-plugin": "5.38.0",
"@typescript-eslint/parser": "5.38.0",
"ava": "4.3.3",
"c8": "7.12.0",
"babel-jest": "28.1.3",
"conditional-type-checks": "1.0.6",
"eslint": "8.24.0",
"eslint-config-prettier": "8.5.0",
Expand All @@ -72,16 +76,15 @@
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-sort-class-members": "1.15.2",
"husky": "8.0.1",
"jest": "28.1.3",
"pinst": "3.0.0",
"prettier": "2.7.1",
"react": "18.2.0",
"react-test-renderer": "18.2.0",
"rimraf": "3.0.2",
"rxjs": "7.5.7",
"rxjs-marbles": "7.0.1",
"sinon": "14.0.0",
"standard-version": "9.5.0",
"tap-xunit": "2.4.1",
"typescript": "4.9.3"
},
"dependencies": {
Expand All @@ -92,17 +95,6 @@
"react": "^18.2.0 || ^17.0.2",
"rxjs": "^7.5.6"
},
"ava": {
"files": [
"src/**/*.spec.ts"
],
"typescript": {
"rewritePaths": {
"src/": "dist/"
},
"compile": false
}
},
"prettier": {
"singleQuote": true,
"printWidth": 80,
Expand Down
27 changes: 16 additions & 11 deletions src/action$.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import test from 'ava';
import { incrementMocks } from './internal/testing/mock';
import { action$, dispatchAction } from './action$';
import { take } from 'rxjs/operators';
Expand All @@ -7,30 +6,36 @@ import { ActionWithoutPayload } from './types/Action';

const actions = incrementMocks.marbles.actions;

test('dispatchAction makes action$ emit', (t) => {
let cleanupFns: VoidFunction[] = [];
afterEach(() => {
cleanupFns.forEach((cleanupFn) => cleanupFn());
cleanupFns = [];
});

test('dispatchAction makes action$ emit', () => {
let lastAction: ActionWithoutPayload | undefined;
const sub = action$.pipe(take(1)).subscribe((a) => (lastAction = a));
t.teardown(() => sub.unsubscribe());
cleanupFns.push(() => sub.unsubscribe());

dispatchAction(actions[1]);
t.is(lastAction, actions[1]);
expect(lastAction).toBe(actions[1]);
});

test('dispatchAction does not remove existing namespace', (t) => {
test('dispatchAction does not remove existing namespace', () => {
let lastAction: ActionWithoutPayload | undefined;
const sub = action$.pipe(take(1)).subscribe((a) => (lastAction = a));
t.teardown(() => sub.unsubscribe());
cleanupFns.push(() => sub.unsubscribe());

dispatchAction(actions.n);
t.is(lastAction, actions.n);
expect(lastAction).toBe(actions.n);
});

test('dispatchAction replaces namespace', (t) => {
test('dispatchAction replaces namespace', () => {
let lastAction;
const sub = action$.pipe(take(1)).subscribe((a) => (lastAction = a));
t.teardown(() => sub.unsubscribe());
cleanupFns.push(() => sub.unsubscribe());

dispatchAction(actions.n, 'foo');
t.notDeepEqual(lastAction, actions.n);
t.deepEqual(lastAction, _namespaceAction('foo', actions.n));
expect(lastAction).not.toEqual(actions.n);
expect(lastAction).toEqual(_namespaceAction('foo', actions.n));
});
83 changes: 34 additions & 49 deletions src/actionCreator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import test from 'ava';
import {
actionCreator,
isActionOfType,
Expand All @@ -17,52 +16,40 @@ const unionAction = actionCreator<Payload | AlternativePayload>('[test] union');
const union1 = unionAction({ num: 4 });
const union2 = unionAction({ text: 'hi' });

test('actionCreator should create action creators and append the type', (t) => {
t.is(action.type, myAction.type);
t.deepEqual(action.payload, { num: 3 });
test('actionCreator should create action creators and append the type', () => {
expect(action.type).toBe(myAction.type);
expect(action.payload).toEqual({ num: 3 });
});
test('actionCreator should protect the type field', (t) => {
t.throws(
() => {
(myAction as any).type = 'lol';
},
{ instanceOf: TypeError }
);
test('actionCreator should protect the type field', () => {
expect(() => {
(myAction as any).type = 'lol';
}).toThrow(TypeError);
});
test('actionCreator should create action objects with the payload', (t) => {
t.deepEqual(action.payload, { num: 3 });
t.deepEqual(union1.payload, { num: 4 });
t.deepEqual(union2.payload, { text: 'hi' });
test('actionCreator should create action objects with the payload', () => {
expect(action.payload).toEqual({ num: 3 });
expect(union1.payload).toEqual({ num: 4 });
expect(union2.payload).toEqual({ text: 'hi' });
});
test('actionCreator should create action objects with protected type', (t) => {
t.throws(
() => {
action.type = 'mock';
},
{ instanceOf: TypeError }
);
test('actionCreator should create action objects with protected type', () => {
expect(() => {
action.type = 'mock';
}).toThrow(TypeError);
});
test('actionCreator should create action objects with protected meta', (t) => {
t.throws(
() => {
action.meta = {};
},
{ instanceOf: TypeError }
);
test('actionCreator should create action objects with protected meta', () => {
expect(() => {
action.meta = {};
}).toThrow(TypeError);
});
test('actionCreator should create action objects with protected namespace', (t) => {
t.throws(
() => {
action.meta.namespace = 'shim';
},
{ instanceOf: TypeError }
);
test('actionCreator should create action objects with protected namespace', () => {
expect(() => {
action.meta.namespace = 'shim';
}).toThrow(TypeError);
});

const actionCreatorWithPayload = actionCreator<string>('[test] with payload');
const actionCreatorWithoutPayload = actionCreator('[test] no payload');

test('isValidRxBeachAction - invalid actions', (t) => {
test('isValidRxBeachAction - invalid actions', () => {
const invalidActions = [
undefined,
null,
Expand All @@ -81,29 +68,27 @@ test('isValidRxBeachAction - invalid actions', (t) => {
];

for (const invalidAction of invalidActions) {
t.is(isValidRxBeachAction(invalidAction), false);
expect(isValidRxBeachAction(invalidAction)).toBe(false);
}
});

test('isValidRxBeachAction - valid actions', (t) => {
test('isValidRxBeachAction - valid actions', () => {
const validActions = [
{ type: '[test] action type', meta: {}, payload: undefined },
actionCreatorWithPayload('asd'),
actionCreatorWithoutPayload(),
];

for (const validAction of validActions) {
t.is(isValidRxBeachAction(validAction), true);
expect(isValidRxBeachAction(validAction)).toBe(true);
}
});

test('isActionOfType', (t) => {
t.is(
isActionOfType(actionCreatorWithPayload, actionCreatorWithPayload('asd')),
true
);
t.is(
isActionOfType(actionCreatorWithoutPayload, actionCreatorWithoutPayload()),
true
);
test('isActionOfType', () => {
expect(
isActionOfType(actionCreatorWithPayload, actionCreatorWithPayload('asd'))
).toBe(true);
expect(
isActionOfType(actionCreatorWithoutPayload, actionCreatorWithoutPayload())
).toBe(true);
});
31 changes: 2 additions & 29 deletions src/actionCreator.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,5 @@
import { Action, VoidPayload } from './types/Action';
import {
ActionCreator,
ActionCreatorWithPayload,
ActionCreatorWithoutPayload,
} from './types/ActionCreator';

type ActionName = `[${string}] ${string}`;

interface ActionCreatorFunc {
/**
* Create an action creator without a payload
*
* @param type A name for debugging purposes
* @returns An action creator function that creates complete action objects with
* a type unique to this action creator
*/
(type: ActionName): ActionCreatorWithoutPayload;
/**
* Create an action creator with a given payload type
*
* @param type A name for debugging purposes
* @template `Payload` - The payload type for the action
* @returns An action creator function that accepts a payload as input, and
* returns a complete action object with that payload and a type unique
* to this action creator
*/
<Payload>(type: ActionName): ActionCreatorWithPayload<Payload>;
}
import type { Action, VoidPayload } from './types/Action';
import type { ActionCreator, ActionCreatorFunc } from './types/ActionCreator';

/**
* Untyped `actionCreator`
Expand Down
3 changes: 1 addition & 2 deletions src/derivedStream.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import test from 'ava';
import { derivedStream } from './derivedStream';
import { marbles } from 'rxjs-marbles/ava';
import { marbles } from 'rxjs-marbles/jest';

test(
'derivedStream emits on emit from either source',
Expand Down
11 changes: 5 additions & 6 deletions src/internal/defaultErrorSubject.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import untypedTest from 'ava';
import { defaultErrorSubject } from './defaultErrorSubject';
import { stubRethrowErrorGlobally } from './testing/utils';

const test = stubRethrowErrorGlobally(untypedTest);
beforeAll(() => {
jest.useFakeTimers();
});

test('defaultErrorSubject rethrows errors globally', (t) => {
test('defaultErrorSubject rethrows errors globally', () => {
const error = new Error('Hello errors');
defaultErrorSubject.next(error);

t.context.rethrowErrorGlobally.calledOnceWithExactly(error);
t.pass();
expect(jest.runAllTimers).toThrow(error);
});
2 changes: 1 addition & 1 deletion src/internal/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export {
RoutineFunc,
VoidPayload,
UnknownAction,
ActionCreatorCommon,
UnknownActionCreator,
UnknownActionCreatorWithPayload,
} from './types';
export { RoutineFunc } from './routineFunc';
export { coldMergeOperators } from '../operators/mergeOperators';
export { defaultErrorSubject } from './defaultErrorSubject';
export { rethrowErrorGlobally } from './rethrowErrorGlobally';
Loading

0 comments on commit d13dbe1

Please sign in to comment.