Skip to content

Commit

Permalink
Add more testing (#3)
Browse files Browse the repository at this point in the history
* Add tests for state container and utils.

* Add staat tests

* Start component test

* Add react tests.

* Update version.

* Undo version changes
  • Loading branch information
ericmackrodt authored Jan 29, 2019
1 parent 6df8954 commit 289ef9e
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 84 deletions.
34 changes: 13 additions & 21 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
module.exports = {
"collectCoverageFrom": [
"packages/**/*.(ts|tsx)",
"!packages/**/*.d.(ts|tsx)",
"!<rootDir>/node_modules/",
"!<rootDir>/**/build/**/*.*",
"!<rootDir>/packages/example/**/*.*",
collectCoverageFrom: [
'packages/**/*.(ts|tsx)',
'!packages/**/*.d.(ts|tsx)',
'!packages/**/index.(ts|tsx)',
'!<rootDir>/node_modules/',
'!<rootDir>/**/build/**/*.*',
'!<rootDir>/packages/example/**/*.*',
],
"roots": [
"<rootDir>/packages"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
roots: ['<rootDir>/packages'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
"testRegex": "(/__tests__/.*\.test)\.tsx?$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
}
testRegex: '(/__tests__/.*.test).tsx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
90 changes: 90 additions & 0 deletions packages/core/src/__tests__/staat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import staat from '../staat';
import { Staat } from '../types';

type TestState = {
count: number;
};

const state: TestState = {
count: 0,
};

const transformers = {
add(currentState: TestState, value: number) {
return { ...currentState, count: currentState.count + value };
},
subtract(currentState: TestState, value: number) {
return { ...currentState, count: currentState.count - value };
},
};

describe('staat', () => {
let sut: Staat<TestState, typeof transformers>;
beforeEach(() => {
sut = staat(transformers, state);
});

it('sets up all members', () => {
expect(sut).toHaveProperty('currentState');
expect(sut.currentState).toBe(state);
expect(typeof sut.subscribe).toBe('function');
expect(typeof sut.unsubscribe).toBe('function');
expect(typeof sut.add).toBe('function');
expect(typeof sut.subtract).toBe('function');
});

it('sets up deep transformers', () => {
const sut2 = staat({ deep: transformers }, state);
expect(sut2).toHaveProperty('deep');
expect(typeof sut2.deep.add).toBe('function');
expect(typeof sut2.deep.subtract).toBe('function');
});

it('creates external trasformers signature and calls the declaration', async () => {
let newState = await sut.add(100);
expect(sut.currentState).toEqual({ count: 100 });
expect(newState).toEqual({ count: 100 });
expect(sut.currentState).not.toBe(state);
newState = await sut.subtract(50);
expect(sut.currentState).toEqual({ count: 50 });
expect(newState).toEqual({ count: 50 });
expect(sut.currentState).not.toBe(state);
});

it('fires subscription when calling transformer', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
await sut.add(1);
expect(subscription).toHaveBeenCalled();
});

it('unsubscribes from state', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
await sut.add(1);
sut.unsubscribe(subscription);
await sut.add(1);
expect(subscription).toHaveBeenCalledTimes(1);
});

it('calls asynchronous transformers', async () => {
const sut2 = staat(
{
multiply(currentState: TestState, value: number) {
return Promise.resolve({
...currentState,
count: currentState.count * value,
});
},
},
{
count: 2,
},
);

const newState = await sut2.multiply(10);

expect(sut2.currentState).toEqual({ count: 20 });
expect(newState).toEqual({ count: 20 });
});
});
57 changes: 55 additions & 2 deletions packages/core/src/__tests__/state-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
import { StateContainer } from '../state-container';

type TestState = {
count: number;
};

const state: TestState = {
count: 0,
};

describe('StateContainer', () => {
it('should test', () => {
expect(true).toBe(true);
let sut: StateContainer<TestState>;
beforeEach(() => {
sut = new StateContainer(state);
});

it('returns initial state', () => {
expect(sut.getState()).toBe(state);
});

it('sets new state', async () => {
const newState = { ...state, count: 1 };
await sut.setState(newState);
expect(sut.getState()).toEqual(newState);
expect(sut).not.toBe(state);
});

it('fires subscription when setting state', async () => {
const subscriptionMock = jest.fn();
sut.subscribe(subscriptionMock);
const newState = { ...state, count: 1 };
await sut.setState(newState);
expect(subscriptionMock).toHaveBeenCalled();
});

it('fires multiple subscriptions when setting state', async () => {
const subscriptionMock1 = jest.fn();
const subscriptionMock2 = jest.fn();
sut.subscribe(subscriptionMock1);
sut.subscribe(subscriptionMock2);
const newState = { ...state, count: 1 };
await sut.setState(newState);
expect(subscriptionMock1).toHaveBeenCalled();
expect(subscriptionMock2).toHaveBeenCalled();
});

it('does not fire multiple subscriptions when unsubscribed', async () => {
const subscriptionMock1 = jest.fn();
const subscriptionMock2 = jest.fn();
sut.subscribe(subscriptionMock1);
sut.subscribe(subscriptionMock2);
const newState = { ...state, count: 1 };
sut.unsubscribe(subscriptionMock1);
await sut.setState(newState);
expect(subscriptionMock1).not.toHaveBeenCalled();
expect(subscriptionMock2).toHaveBeenCalled();
});
});
23 changes: 22 additions & 1 deletion packages/core/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { setScope } from '../utils';
import { setScope, isTransformer } from '../utils';

describe('utils', () => {
test('isTransformer', () => {
expect(isTransformer({})).toBe(false);
expect(isTransformer(jest.fn())).toBe(true);
});

describe('setScope', () => {
it('should build correct object', () => {
const state = {
Expand Down Expand Up @@ -37,5 +42,21 @@ describe('utils', () => {
});
expect(result).not.toBe(state);
});

it('should throw error if property does not exist', () => {
const state = {
level1: {
level2: {
name: 'Test',
},
},
};

expect(() =>
setScope(state, { name: 'Another' }, ['non_existant']),
).toThrowError(
'The property [non_existant] in path is invalid or undefined',
);
});
});
});
3 changes: 1 addition & 2 deletions packages/core/src/state-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export class StateContainer<T> {

public setState(state: T) {
return Promise.resolve().then(() => {
const current: T = JSON.parse(JSON.stringify(state));
this.state = current;
this.state = state;
return this.fireSubscriptions().then(() => this.state);
});
}
Expand Down
6 changes: 4 additions & 2 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
"devDependencies": {
"@types/react": "^16.7.17",
"cross-env": "^5.2.0",
"jest-dom": "^3.0.0",
"react-testing-library": "^5.4.4",
"ts-loader": "5.3.2",
"webpack": "^4.28.1",
"webpack-cli": "^3.1.2",
"ts-loader": "5.3.2"
"webpack-cli": "^3.1.2"
},
"peerDependencies": {
"staat": "1.1.1-beta.4"
Expand Down
93 changes: 91 additions & 2 deletions packages/react/src/__tests__/react.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,94 @@
import React from 'react';
import { reactStaat } from '../react';
import staat from 'staat';
import { ReactStaat } from '../types';
import { render, fireEvent } from 'react-testing-library';
/* tslint:disable no-submodule-imports */
import 'jest-dom/extend-expect';

type TestState = {
count: number;
};

const state: TestState = {
count: 0,
};

const transformers = {
add(currentState: TestState, value: number) {
return { ...currentState, count: currentState.count + value };
},
subtract(currentState: TestState, value: number) {
return { ...currentState, count: currentState.count - value };
},
};

type OwnProps = {};
type TransformerProps = {
add(value: number): TestState | Promise<TestState>;
};
type TestComponentProps = TestState & OwnProps & TransformerProps;

const TestComponent: React.StatelessComponent<TestComponentProps> = ({
count,
add,
}) => {
return (
<React.Fragment>
<div>Count: {count}</div>
<button onClick={() => add(10)}>Add</button>
</React.Fragment>
);
};

describe('React', () => {
it('should test', () => {
expect(true).toBe(true);
let sut: ReactStaat<TestState>;
let ConnectedComponent: React.ComponentType<OwnProps>;

beforeEach(() => {
const staatState = staat(transformers, state);
sut = reactStaat(staatState);

ConnectedComponent = sut.connect<OwnProps, TestState, TransformerProps>(
({ count }) => {
return {
count,
};
},
() => ({
add: staatState.add,
}),
)(TestComponent);
});

it('builds react-staat object', () => {
expect(typeof sut.Provider).toBe('function');
expect(typeof sut.connect).toBe('function');
});

it('should render component', () => {
const tree = (
<sut.Provider>
<ConnectedComponent />
</sut.Provider>
);
const { getByText } = render(tree);
expect(getByText(/^Count:/).textContent).toBe('Count: 0');
});

it('should update component', async () => {
const tree = (
<sut.Provider>
<ConnectedComponent />
</sut.Provider>
);
const { getByText, rerender } = render(tree);
await fireEvent.click(getByText('Add'));
await rerender(
<sut.Provider>
<ConnectedComponent />
</sut.Provider>,
);
expect(getByText(/^Count:/).textContent).toBe('Count: 10');
});
});
5 changes: 5 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { reactStaat } from './react';

export * from 'staat';

export default reactStaat;
6 changes: 1 addition & 5 deletions packages/react/src/index.tsx → packages/react/src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ import { ReactStaat } from './types';
import makeConnect from './connect';
import makeProvider from './provider';

export * from 'staat';

function reactStaat<TState, TTransformers>(
export function reactStaat<TState, TTransformers>(
staat: Staat<TState, TTransformers>,
): ReactStaat<TState> {
return {
Provider: makeProvider(staat),
connect: makeConnect<TState>(),
};
}

export default reactStaat;
8 changes: 4 additions & 4 deletions packages/react/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
//https://github.com/krasimir/webpack-library-starter/blob/master/webpack.config.js
const base = require('../../config/webpack.config.base');

const config = base(__dirname, './src/index.tsx', 'staat-react', 'index.js');
const config = base(__dirname, './src/index.ts', 'staat-react', 'index.js');

config.externals = {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
root: 'react'
root: 'react',
},
staat: {
commonjs: 'staat',
commonjs2: 'staat',
amd: 'staat',
root: 'staat'
}
root: 'staat',
},
};

module.exports = config;
Loading

0 comments on commit 289ef9e

Please sign in to comment.