Skip to content

Commit

Permalink
feat(vest): suite.remove
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush committed Dec 26, 2020
1 parent 4dd6a30 commit b8746f9
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 68 deletions.
3 changes: 3 additions & 0 deletions jsconfig.json

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

8 changes: 4 additions & 4 deletions packages/vest/docs/result.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ If you need to access your validation results out of context - for example, from
In case your validations did not run yet, `.get` returns an empty validation result object - which can be helpful when trying to access validation result object when rendering the initial UI, or setting it in the initial state of your components.

```js
const v = vest.create('my_form', () => {
const suite = vest.create('my_form', () => {
/*...*/
});

v.get(); // -> returns the most recent result object for the current suite
suite.get(); // -> returns the most recent result object for the current suite
```

You can also use `.get()` to access the intermediate validation result during your suite's run, this is a replacement of the deprecated `vest.draft()` hook.

```js
const v = vest.create('my_form', data => {
const suite = vest.create('my_form', data => {
test('username', 'Username is too short', () => {
enforce(data.username).longerThanOrEquals(3);
});

if (!v.get().hasErrors('username')) {
if (!suite.get().hasErrors('username')) {
test('username', 'already taken', async () => {
// some async test
});
Expand Down
22 changes: 20 additions & 2 deletions packages/vest/docs/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,27 @@ In some cases, such as form reset, you want to discard of previous validation re
```js
import vest from 'vest';

const v = vest.create('suite_name', () => {
const suite = vest.create(() => {
// Your tests go here
});

v.reset(); // validation result is removed from Vest's state.
suite.reset(); // validation result is removed from Vest's state.
```

## Removing a single field from the validation result

Instead of resetting the whole suite, you can alternatively remove just one field. This is useful when dynamically adding and removing fields upon user interaction - and you want to delete a deleted field from the state.

```js
import vest from 'vest';

const suite = vest.create(() => {
// Your tests go here

test('username', 'must be at least 3 chars long', () => {
/*...*/
});
});

suite.remove('username'); // validation result is removed from Vest's state.
```
31 changes: 31 additions & 0 deletions packages/vest/docs/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,37 @@ export default vest.create('form-name', data => {
});
```

## test.each for dynamically creating tests from a table

Use test.each when you need to dynamically create tests from data, or when you have multiple tests that have the same overall structure.

test.each takes an array of arrays. The inner array contains the arguments that each of the tests will recieve.

Because of the dynamic nature of the iterative tests, you can also dynamically construct the fieldName and the test message by providing a function instead of a string. Your array's content will be passed over as arguments to each of these functions.

```js
/*
const data = {
products: [
['Game Boy Color', 25],
['Speak & Spell', 22.5],
['Tamagotchi', 15],
['Connect Four', 7.88],
]
}
*/

const suite = vest.create('store_edit', data => {
test.each(data.products)(
name => name,
'Price must be numeric and above zero.',
(_, price) => {
enforce(price).isNumeric().greaterThan(0);
}
);
});
```

**Read next about:**

- [Warn only tests](./warn).
Expand Down
1 change: 1 addition & 0 deletions packages/vest/src/core/suite/__tests__/createSuite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('Test createSuite module', () => {
expect(typeof create(Function.prototype)).toBe('function');
expect(typeof create(Function.prototype).get).toBe('function');
expect(typeof create(Function.prototype).reset).toBe('function');
expect(typeof create(Function.prototype).remove).toBe('function');
expect(create(Function.prototype).get()).toMatchSnapshot();
});

Expand Down
50 changes: 50 additions & 0 deletions packages/vest/src/core/suite/__tests__/remove.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import wait from 'wait';

import { dummyTest } from '../../../../testUtils/testDummy';

import vest from 'vest';

describe('suite.remove', () => {
it('Should remove field from validation result', async () => {
const suite = vest.create(() => {
dummyTest.failing('field1');
dummyTest.failing('field1');
dummyTest.failingAsync('field1', { time: 100 });
dummyTest.failing('field2');
dummyTest.passing('field2');
dummyTest.passing('field1');
});
suite();
expect(suite.get().testCount).toBe(6);
expect(suite.get().tests.field1.testCount).toBe(4);
expect(suite.get().tests.field2.testCount).toBe(2);
suite.remove('field1');
expect(suite.get().testCount).toBe(2);
expect(suite.get().tests.field1).toBeUndefined();
await wait(150);
expect(suite.get().testCount).toBe(2);
expect(suite.get().tests.field1).toBeUndefined();
});

it('Should clear the cache when removing a field', () => {
const suite = vest.create(() => {
dummyTest.failing('field1');
dummyTest.failing('field2');
});
suite();
const res = suite.get();
suite.remove('field2');
expect(suite.get()).not.toBe(res);
});

it('Should return silently when removing a field that does not exist', () => {
const suite = vest.create(() => {
dummyTest.failing('field1');
dummyTest.passing('field2');
});
suite();
const res = suite.get();
suite.remove('field3');
expect(suite.get()).toBe(res);
});
});
46 changes: 26 additions & 20 deletions packages/vest/src/core/suite/createSuite.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asArray from 'asArray';
import context from 'ctx';
import genId from 'genId';
import isFunction from 'isFunction';
Expand Down Expand Up @@ -38,30 +39,35 @@ const createSuite = withArgs(args => {
the `get`, and `reset` functions.
*/
return Object.defineProperties(
context.bind({ stateRef }, function () {
const [previousTestObjects] = useTestObjects();
const [{ pending }, setPending] = usePending();
stateRef.reset();
const suite = context.bind({ stateRef }, function () {
const [previousTestObjects] = useTestObjects();
const [{ pending }, setPending] = usePending();
stateRef.reset();

// Move all the active pending tests to the lagging array
setPending({ lagging: pending, pending: [] });
// Move all the active pending tests to the lagging array
setPending({ lagging: pending, pending: [] });

// Run the consumer's callback
tests.apply(null, arguments);
// Run the consumer's callback
tests.apply(null, arguments);

// Merge all the skipped tests with their previous results
mergeExcludedTests(previousTestObjects);
// Merge all the skipped tests with their previous results
mergeExcludedTests(previousTestObjects);

return produce();
}),
{
get: {
value: context.bind({ stateRef }, produce, /*isDraft:*/ true),
},
reset: { value: stateRef.reset },
}
);
return produce();
});
suite.get = context.bind({ stateRef }, produce, /*isDraft:*/ true);
suite.reset = stateRef.reset;
suite.remove = context.bind({ stateRef }, name => {
const [testObjects] = useTestObjects();

// We're mutating the array in `cancel`, so we have to first copy it.
asArray(testObjects).forEach(testObject => {
if (testObject.fieldName === name) {
testObject.cancel();
}
});
});
return suite;
});

export default createSuite;
26 changes: 3 additions & 23 deletions packages/vest/src/core/test/__tests__/runAsyncTest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,28 +77,6 @@ describe.each([CASE_PASSING /*, CASE_FAILING*/])(
})
);
}));

describe('When test is canceled', () => {
beforeEach(() => {
testObject.cancel();
});

it.ctx('Should remove test from pending array', () => {
const [pendingState] = usePending();
expect(pendingState.pending).toEqual(
expect.arrayContaining([testObject])
);
runRunAsyncTest(testObject);
return new Promise(done => {
setTimeout(() => {
expect(pendingState).toEqual(
expect.not.arrayContaining([testObject])
);
done();
});
});
});
});
});

describe('doneCallbacks', () => {
Expand Down Expand Up @@ -169,7 +147,9 @@ describe.each([CASE_PASSING /*, CASE_FAILING*/])(

describe('When test is canceled', () => {
beforeEach(() => {
testObject.cancel();
context.run({ stateRef }, () => {
testObject.cancel();
});
});

it('Should return without running any callback', () =>
Expand Down
4 changes: 4 additions & 0 deletions packages/vest/src/core/test/lib/VestTest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import id from 'genId';
import { removePending } from 'pending';
import removeTestFromState from 'removeTestFromState';

/**
* Describes a test call inside a Vest suite.
Expand Down Expand Up @@ -45,6 +47,8 @@ VestTest.prototype.warn = function () {

VestTest.prototype.cancel = function () {
this.canceled = true;
removePending(this);
removeTestFromState(this);
};

export default VestTest;
42 changes: 23 additions & 19 deletions packages/vest/src/core/test/lib/pending.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asArray from 'asArray';
import removeElementFromArray from 'removeElementFromArray';
import usePending from 'usePending';

Expand All @@ -10,26 +11,29 @@ export const setPending = testObject => {

const [pendingState, setPending] = usePending();

const lagging = pendingState.lagging.reduce((lagging, testObject) => {
/**
* If the test is of the same profile
* (same name + same group) we cancel
* it. Otherwise, it is lagging.
*/
if (
testObject.fieldName === fieldName &&
testObject.groupName === groupName &&
// This last case handles memoized tests
// because that retain their od across runs
testObject.id !== id
) {
testObject.cancel();
} else {
lagging.push(testObject);
}
const lagging = asArray(pendingState.lagging).reduce(
(lagging, testObject) => {
/**
* If the test is of the same profile
* (same name + same group) we cancel
* it. Otherwise, it is lagging.
*/
if (
testObject.fieldName === fieldName &&
testObject.groupName === groupName &&
// This last case handles memoized tests
// because that retain their od across runs
testObject.id !== id
) {
testObject.cancel();
} else {
lagging.push(testObject);
}

return lagging;
}, []);
return lagging;
},
[]
);

setPending(state => ({
lagging,
Expand Down
14 changes: 14 additions & 0 deletions packages/vest/src/core/test/lib/removeTestFromState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import asArray from 'asArray';
import removeElementFromArray from 'removeElementFromArray';
import useTestObjects from 'useTestObjects';

/**
* Removes test object from suite state
* @param {VestTest} testObject
*/
export default testObject => {
useTestObjects(testObjects =>
// using asArray to clear the cache.
asArray(removeElementFromArray(testObjects, testObject))
);
};

0 comments on commit b8746f9

Please sign in to comment.