Skip to content

Commit

Permalink
patch(vest): make state independent of context (ealush#609)
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush committed May 11, 2021
1 parent 9e7ddd3 commit a5aa3f1
Show file tree
Hide file tree
Showing 34 changed files with 296 additions and 322 deletions.
8 changes: 2 additions & 6 deletions jsconfig.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/n4s/src/meta/ruleMeta.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import message from 'ruleMessage';
import warn from 'ruleWarn';
import when from 'ruleWhen';
Expand Down
24 changes: 3 additions & 21 deletions packages/vest/src/core/produce/__tests__/produce.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import group from 'group';
import hasRemainingTests from 'hasRemainingTests';
import { setPending } from 'pending';
import produce from 'produce';
import { SEVERITY_COUNT_ERROR, SEVERITY_COUNT_WARN } from 'resultKeys';
import useTestCallbacks from 'useTestCallbacks';
import useTestObjects from 'useTestObjects';
import { useTestCallbacks, useTestObjects } from 'stateHooks';
import vest from 'vest';

const DRAFT_EXCLUDED_METHODS = ['done'];
Expand All @@ -29,25 +27,16 @@ const warningFields = {};
const SKIPPED_FIELD = 'skipped_field_name';
const groupName = 'group_name';

let state, produced, stateRef;
let produced, stateRef;

it.ctx = (str, cb) => it(str, () => context.run({ stateRef }, cb));
beforeEach.ctx = cb => beforeEach(() => context.run({ stateRef }, cb));

const KEPT_PROPERTIES = [
SEVERITY_COUNT_ERROR,
SEVERITY_COUNT_WARN,
'tests',
'name',
'groups',
];

let collect;

const getStateFromContext = () => {
context.run({}, ctx => {
stateRef = ctx.stateRef;
state = stateRef.current();
});
};

Expand Down Expand Up @@ -93,12 +82,6 @@ describe('module: produce', () => {
});
});

it('Should create a deep copy of subset of the state', () => {
expect(_.pick(stateRef.current(), KEPT_PROPERTIES)).isDeepCopyOf(
_.pick(produced, KEPT_PROPERTIES)
);
});

it.each(GENERATED_METHODS)(
'Should add `%s` method to the output object',
name => {
Expand Down Expand Up @@ -418,8 +401,7 @@ describe('module: produce', () => {
});
describe('When no async tests', () => {
it.ctx('Sanity', () => {
state = stateRef.current();
expect(hasRemainingTests(state)).toBe(false);
expect(hasRemainingTests()).toBe(false);
});

describe('When invoked without a field name', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/vest/src/core/produce/collectFailureMessages.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isMatchingSeverityProfile from 'isMatchingSeverityProfile';
import useTestObjects from 'useTestObjects';
import { useTestObjects } from 'stateHooks';

/**
* @param {'warn'|'error'} severity Filter by severity.
Expand Down
4 changes: 2 additions & 2 deletions packages/vest/src/core/produce/genTestsSummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
SEVERITY_GROUP_ERROR,
TEST_COUNT,
} from 'resultKeys';
import useSuiteId from 'useSuiteId';
import useTestObjects from 'useTestObjects';
import { useSuiteId , useTestObjects } from 'stateHooks';


/**
* Reads the testObjects list and gets full validation result from it.
Expand Down
2 changes: 1 addition & 1 deletion packages/vest/src/core/produce/hasFaillures.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isMatchingSeverityProfile from 'isMatchingSeverityProfile';
import useTestObjects from 'useTestObjects';
import { useTestObjects } from 'stateHooks';

/**
* Determines whether a certain test profile has failures.
Expand Down
2 changes: 1 addition & 1 deletion packages/vest/src/core/produce/hasFailuresByGroup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { hasLogic } from 'hasFaillures';
import useTestObjects from 'useTestObjects';
import { useTestObjects } from 'stateHooks';

/**
* Checks whether there are failures in a given group.
Expand Down
7 changes: 4 additions & 3 deletions packages/vest/src/core/produce/produce.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import createCache from 'cache';
import context from 'ctx';
import genTestsSummary from 'genTestsSummary';
Expand All @@ -9,8 +10,7 @@ import hasRemainingTests from 'hasRemainingTests';
import isFunction from 'isFunction';
import { SEVERITY_GROUP_ERROR, SEVERITY_GROUP_WARN } from 'resultKeys';
import { HAS_WARNINGS, HAS_ERRORS } from 'sharedKeys';
import useTestCallbacks from 'useTestCallbacks';
import useTestObjects from 'useTestObjects';
import { useTestCallbacks, useTestObjects } from 'stateHooks';
import withArgs from 'withArgs';

const cache = createCache(20);
Expand Down Expand Up @@ -49,7 +49,8 @@ const done = withArgs(args => {
return output;
}

useTestCallbacks(current => {
const [, setTestCallbacks] = useTestCallbacks();
setTestCallbacks(current => {
if (fieldName) {
current.fieldCallbacks[fieldName] = (
current.fieldCallbacks[fieldName] || []
Expand Down
200 changes: 114 additions & 86 deletions packages/vest/src/core/state/__tests__/state.test.js
Original file line number Diff line number Diff line change
@@ -1,119 +1,147 @@
import context from 'ctx';
import state from 'state';
import createState from 'state';

let stateRef, useExampleState;
it.ctx = (str, cb) => it(str, () => context.run({ stateRef }, cb));
let state;

describe('state', () => {
beforeEach(() => {
useExampleState = state.registerHandler(() => 'I am an example!');
state = createState();
});

stateRef = state.createRef({
dynamicallyProvided: [state.registerHandler((a, b) => [a, b]), [1, 2]],
exampleState: useExampleState,
explicitValue: state.registerHandler(42),
keyWithObject: state.registerHandler(() => ({ a: true })),
describe('createState', () => {
it('Should return all stateRef methods', () => {
expect(state).toMatchInlineSnapshot(`
Object {
"registerStateKey": [Function],
"reset": [Function],
}
`);
});
});
describe('state initialization', () => {
it('Should initialize state with provided keys', () => {
expect(stateRef.current()).toEqual(initialState);

describe('state.registerStateKey', () => {
it('Should return a function', () => {
expect(typeof state.registerStateKey()).toBe('function');
});
});

describe('State reset', () => {
it.ctx('Should return state to its initial value', () => {
stateRef.reset();
it('Should append another state key on each call', () => {
const stateValues = Array.from({ length: 100 }, () => Math.random());
const stateGetters = stateValues.map(value =>
state.registerStateKey(value)
);
expect(
stateGetters.every(
(stateGetter, i) => stateGetter()[0] === stateValues[i]
)
).toBe(true);
expect(stateGetters).toHaveLength(100);
});

useExampleState(() => 'example_2');
describe('When initial value is a function', () => {
it('Should generate initial state from key', () => {
const initialStateKey = { key: 'value' };
const stateGetter = state.registerStateKey(() => initialStateKey);
expect(stateGetter()[0]).toBe(initialStateKey);
});
});

expect(stateRef.current()).toEqual({
...initialState,
exampleState: 'example_2',
describe('When initial value is not a function', () => {
it('Should use provided value as initial state', () => {
const stateValue = { key: 'value' };
const stateGetter = state.registerStateKey(stateValue);
expect(stateGetter()[0]).toBe(stateValue);
});
});

stateRef.reset();
expect(stateRef.current()).toEqual(initialState);
describe('When initial value is not provided', () => {
it('Should set initial state to undefined', () => {
const stateGetter = state.registerStateKey();
expect(stateGetter()[0]).toBeUndefined();
});
});
});

describe('useHook', () => {
describe('When passing a callback to the useHook function', () => {
it.ctx('Updates the state', () => {
useExampleState(() => 'I am the new value of the state!');
describe('State key function', () => {
it('Should return an Array with two elements', () => {
expect(state.registerStateKey()()).toHaveLength(2);
expect(state.registerStateKey('some value')()).toMatchInlineSnapshot(`
Array [
"some value",
[Function],
]
`);
});

expect(stateRef.current().exampleState).toBe(
'I am the new value of the state!'
);
describe('getting current value', () => {
it('Should have current value in the first array element', () => {
const stateGetter = state.registerStateKey('Some Value');
expect(stateGetter()[0]).toBe('Some Value');
});
});

describe('useHook return value', () => {
it.ctx('returns an array', () => {
expect(Array.isArray(useExampleState())).toBe(true);
expect(Array.isArray(useExampleState(() => null))).toBe(true);
expect(useExampleState()).toHaveLength(2);
describe('updating the state', () => {
it('Should contain state updater in the second array element', () => {
const stateGetter = state.registerStateKey('Some Value');
expect(typeof stateGetter()[1]).toBe('function');
});

describe('pos:0', () => {
it.ctx('Has the current value', () => {
expect(useExampleState()[0]).toBe('I am an example!');
expect(useExampleState(() => 'me too!')[0]).toBe('me too!');
expect(useExampleState()[0]).toBe(stateRef.current().exampleState);
});
it('Should update the state with provided value', () => {
const stateGetter = state.registerStateKey('Some Value');
const [, valueSetter] = stateGetter();
const nextValue = { key: 'value' };
valueSetter(nextValue);
expect(stateGetter()[0]).toBe(nextValue);
});

describe('pos:1', () => {
it.ctx('Has a function', () => {
expect(typeof useExampleState()[1]).toBe('function');
describe('When passing a function', () => {
it('Should update the state with the result of the function', () => {
const stateGetter = state.registerStateKey('Some Value');
const [, valueSetter] = stateGetter();
const nextValue = { key: 'value' };
valueSetter(() => nextValue);
expect(stateGetter()[0]).toBe(nextValue);
});

it.ctx('Updates the state', () => {
const [value, setValue] = useExampleState();
setValue('an array of words'.split(' '));
expect(useExampleState()[0]).toEqual(['an', 'array', 'of', 'words']);
expect(useExampleState()[0]).not.toEqual(value);
expect(stateRef.current().exampleState).toEqual([
'an',
'array',
'of',
'words',
]);
setValue(() => ' a function works too!');
expect(stateRef.current().exampleState).toEqual(
' a function works too!'
);
expect(stateRef.current().exampleState).toEqual(useExampleState()[0]);
it('Should pass the function the current state value', () => {
const setter = jest.fn(() => 100);
const stateGetter = state.registerStateKey('555');
const [, valueSetter] = stateGetter();
valueSetter(setter);
expect(setter).toHaveBeenCalledWith('555');
});
});
});
});

describe('onStateChange', () => {
const onStateChange = jest.fn();
beforeEach(() => {

stateRef = state.createRef({
keyWithObject: state.registerHandler(() => ({ a: true })),
}, onStateChange);
describe('state.reset', () => {
it('Should fill up the state with registered keys', () => {
const s1 = state.registerStateKey(111);
const s2 = state.registerStateKey(222);
const s3 = state.registerStateKey(333);
const s4 = state.registerStateKey(444);
s1()[1](555);
s2()[1](666);
s3()[1](777);
s4()[1](888);

// sanity
expect(s1()[0]).toBe(555);
expect(s2()[0]).toBe(666);
expect(s3()[0]).toBe(777);
expect(s4()[0]).toBe(888);

state.reset();

// testing now that everything is back to initial value
expect(s1()[0]).toBe(111);
expect(s2()[0]).toBe(222);
expect(s3()[0]).toBe(333);
expect(s4()[0]).toBe(444);
});

it('should run callback on state change', () => {
expect(onStateChange).toHaveBeenCalledTimes(1);
stateRef.set('key', 'value');
expect(onStateChange).toHaveBeenCalledTimes(2);
it('Should allow setting a value after a state reset', () => {
const stateGetter = state.registerStateKey(() => 'hello!');
const [, stateSetter] = stateGetter();
state.reset();
stateSetter('Good Bye!');
expect(stateGetter()[0]).toBe('Good Bye!');
});

it('should run callback with updated state value', () => {
stateRef.set('key', 'value');
expect(onStateChange).toHaveBeenCalledWith(stateRef.current(),'key', 'value');
});
})
});
});

const initialState = {
dynamicallyProvided: [1, 2],
exampleState: 'I am an example!',
explicitValue: 42,
keyWithObject: { a: true },
};
14 changes: 14 additions & 0 deletions packages/vest/src/core/state/createStateRef.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default function createStateRef(state, { suiteId, name }) {
return {
pending: state.registerStateKey(() => ({
pending: [],
lagging: [],
})),
suiteId: state.registerStateKey(() => ({ id: suiteId, name })),
testCallbacks: state.registerStateKey(() => ({
fieldCallbacks: [],
doneCallbacks: [],
})),
testObjects: state.registerStateKey(() => []),
};
}

0 comments on commit a5aa3f1

Please sign in to comment.