Skip to content

Commit

Permalink
feat(vest): Add boolean control for optional fields (#910)
Browse files Browse the repository at this point in the history
  • Loading branch information
ealush committed Jul 24, 2022
1 parent f1b39ba commit d7d0893
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 169 deletions.
2 changes: 1 addition & 1 deletion packages/n4s/src/exports/compose.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ctx } from 'n4s';
import { invariant, StringObject, assign, mapFirst } from 'vest-utils';

import type { ComposeResult, LazyRuleRunners } from 'genEnforceLazy';
import { ctx } from 'n4s';
import { defaultToPassing, RuleDetailedResult } from 'ruleReturn';
import runLazyRule from 'runLazyRule';

Expand Down
2 changes: 1 addition & 1 deletion packages/n4s/src/exports/compounds.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { enforce } from 'n4s';
import { DropFirst } from 'utilityTypes';

import { allOf } from 'allOf';
import { anyOf } from 'anyOf';
import { enforce } from 'n4s';
import { noneOf } from 'noneOf';
import { oneOf } from 'oneOf';

Expand Down
2 changes: 1 addition & 1 deletion packages/n4s/src/exports/schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { enforce } from 'n4s';
import { DropFirst } from 'utilityTypes';

import { isArrayOf } from 'isArrayOf';
import { loose } from 'loose';
import { enforce } from 'n4s';
import { optional } from 'optional';
import { shape } from 'shape';

Expand Down
2 changes: 1 addition & 1 deletion packages/n4s/src/plugins/schema/isArrayOf.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ctx } from 'n4s';
import { mapFirst } from 'vest-utils';

import type { LazyRuleRunners } from 'genEnforceLazy';
import { ctx } from 'n4s';
import type { RuleDetailedResult } from 'ruleReturn';
import * as ruleReturn from 'ruleReturn';
import runLazyRule from 'runLazyRule';
Expand Down
1 change: 1 addition & 0 deletions packages/n4s/src/plugins/schema/loose.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ctx } from 'n4s';

import type { RuleDetailedResult } from 'ruleReturn';
import * as ruleReturn from 'ruleReturn';
import runLazyRule from 'runLazyRule';
Expand Down
5 changes: 3 additions & 2 deletions packages/vest/src/core/isolate/isolates/omitWhen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function omitWhen(
ctx.run(
{
omitted:
isOmitted() ||
inActiveOmitWhen() ||
optionalFunctionValue(
conditional,
optionalFunctionValue(produceSuiteResult)
Expand All @@ -34,6 +34,7 @@ export default function omitWhen(
});
}

export function isOmitted(): boolean {
// Checks that we're currently in an active omitWhen block
export function inActiveOmitWhen(): boolean {
return !!ctx.useX().omitted;
}
8 changes: 3 additions & 5 deletions packages/vest/src/core/state/createStateRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { NestedArray } from 'nestedArray';
import type { State, UseState } from 'vast';

import VestTest from 'VestTest';
import { OptionalFieldDeclaration } from 'optionalFields';
import type { SuiteResult } from 'produceSuiteResult';

export default function createStateRef(
Expand Down Expand Up @@ -35,15 +36,12 @@ export type StateRef = {

type StateKeys = keyof StateRef;

type OptionalFields = Record<string, OptionalFieldDeclaration>;

export type StateKey<T extends StateKeys> = ReturnType<StateRef[T]>;
export type StateValue<T extends StateKeys> = StateKey<T>[0];
export type StateSetter<T extends StateKeys> = StateKey<T>[1];

type OptionalFields = Record<
string,
[rule: (() => boolean) | boolean, isApplied: boolean]
>;

type SuiteName = string | void;

type TestCallbacks = {
Expand Down
45 changes: 13 additions & 32 deletions packages/vest/src/core/state/stateHooks.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { ValueOf } from 'utilityTypes';
import {
cache as createCache,
nestedArray,
asArray,
assign,
optionalFunctionValue,
} from 'vest-utils';
import { cache as createCache, nestedArray, asArray, assign } from 'vest-utils';

import VestTest from 'VestTest';
import type { StateKey, StateRef, StateValue, VestTests } from 'createStateRef';
Expand All @@ -30,43 +24,30 @@ export function useTestCallbacks(): StateKey<'testCallbacks'> {

// OPTIONAL FIELDS

function useOptionalField(
fieldName: string
): ValueOf<StateValue<'optionalFields'>> {
const [optionalFields] = useOptionalFields();
return optionalFields[fieldName];
}

export function useOptionalFields(): StateKey<'optionalFields'> {
return useStateRef().optionalFields();
}

export function useSetOptionalField(
fieldName: string,
setter:
| ((
current: ValueOf<StateValue<'optionalFields'>>
) => ValueOf<StateValue<'optionalFields'>>)
| ValueOf<StateValue<'optionalFields'>>
): void {
setter: (
current: ValueOf<StateValue<'optionalFields'>>
) => Partial<ValueOf<StateValue<'optionalFields'>>>
) {
const [, setOptionalFields] = useOptionalFields();
setOptionalFields(optionalFields =>
assign(optionalFields, {
[fieldName]: optionalFunctionValue(setter, optionalFields[fieldName]),

setOptionalFields(prev =>
assign(prev, {
[fieldName]: assign({}, prev[fieldName], setter(prev[fieldName])),
})
);
}

export function useOptionalFieldApplied(
fieldName: string
): ValueOf<StateValue<'optionalFields'>>[1] {
return useOptionalField(fieldName)?.[1];
}

export function useOptionalFieldConfig(
export function useOptionalField(
fieldName: string
): ValueOf<StateValue<'optionalFields'>>[0] {
return useOptionalField(fieldName)?.[0];
): ValueOf<StateValue<'optionalFields'>> {
const [optionalFields] = useOptionalFields();
return optionalFields[fieldName] ?? {};
}

export function useTestObjects(): StateKey<'testObjects'> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,42 @@ import {
} from 'hasFailuresByTestObjects';
import { nonMatchingFieldName } from 'matchingFieldName';
import { nonMatchingGroupName } from 'matchingGroupName';
import {
useTestsFlat,
useAllIncomplete,
useOptionalFieldConfig,
useOptionalFieldApplied,
} from 'stateHooks';
import { optionalFiedIsApplied, OptionalFieldTypes } from 'optionalFields';
import { useTestsFlat, useAllIncomplete, useOptionalField } from 'stateHooks';

// eslint-disable-next-line max-statements, complexity
export function shouldAddValidProperty(fieldName?: string): boolean {
if (fieldIsOmitted(fieldName)) {
// Is the field optional, and the optional condition is applied
if (optionalFiedIsApplied(fieldName)) {
return true;
}

if (hasErrorsByTestObjects(fieldName)) {
return false;
}

const testObjects = useTestsFlat();

// Are there no tests?
if (isEmpty(testObjects)) {
return false;
}

// Does the field have any tests with errors?
if (hasErrorsByTestObjects(fieldName)) {
return false;
}

// Does the given field have any pending tests that are not optional?
if (hasNonOptionalIncomplete(fieldName)) {
return false;
}

// Does the field have no missing tests?
return noMissingTests(fieldName);
}

export function shouldAddValidPropertyInGroup(
groupName: string,
fieldName?: string
fieldName: string
): boolean {
if (fieldIsOmitted(fieldName)) {
if (optionalFiedIsApplied(fieldName)) {
return true;
}

Expand All @@ -59,61 +59,50 @@ export function shouldAddValidPropertyInGroup(
return noMissingTestsByGroup(groupName, fieldName);
}

function fieldIsOmitted(fieldName?: string) {
if (!fieldName) {
return false;
}

return useOptionalFieldApplied(fieldName) === true;
}

// Does the given field have any pending tests that are not optional?
function hasNonOptionalIncomplete(fieldName?: string) {
return isNotEmpty(
useAllIncomplete().filter(testObject =>
isOptionalFieldIncomplete(testObject, fieldName)
isTestObjectOptional(testObject, fieldName)
)
);
}

// Do the given group/field have any pending tests that are not optional?
function hasNonOptionalIncompleteByGroup(
groupName: string,
fieldName?: string
) {
function hasNonOptionalIncompleteByGroup(groupName: string, fieldName: string) {
return isNotEmpty(
useAllIncomplete().filter(testObject => {
if (nonMatchingGroupName(testObject, groupName)) {
return false;
}

return isOptionalFieldIncomplete(testObject, fieldName);
return isTestObjectOptional(testObject, fieldName);
})
);
}

function isOptionalFieldIncomplete(
function isTestObjectOptional(
testObject: VestTest,
fieldName?: string
): boolean {
if (nonMatchingFieldName(testObject, fieldName)) {
return false;
}
return useOptionalFieldConfig(testObject.fieldName) !== true;

return optionalFiedIsApplied(fieldName);
}

// Did all of the tests for the provided field run/omit?
// This makes sure that the fields are not skipped or pending.
function noMissingTests(fieldName?: string): boolean {
const testObjects = useTestsFlat();

return testObjects.every(testObject => {
if (nonMatchingFieldName(testObject, fieldName)) {
return true;
}

return missingTestsLogic(testObject, fieldName);
});
return testObjects.every(testObject =>
noMissingTestsLogic(testObject, fieldName)
);
}

// Does the group have no missing tests?
function noMissingTestsByGroup(groupName: string, fieldName?: string): boolean {
const testObjects = useTestsFlat();

Expand All @@ -122,18 +111,41 @@ function noMissingTestsByGroup(groupName: string, fieldName?: string): boolean {
return true;
}

return missingTestsLogic(testObject, fieldName);
return noMissingTestsLogic(testObject, fieldName);
});
}

function missingTestsLogic(testObject: VestTest, fieldName?: string): boolean {
// Does the object qualify as either tested or omitted (but not skipped!)
function noMissingTestsLogic(
testObject: VestTest,
fieldName?: string
): boolean {
if (nonMatchingFieldName(testObject, fieldName)) {
return true;
}

/**
* The reason we're checking for the optional field here and not in "omitOptionalFields"
* is because that unlike the bool/function check we do there, here it only depends on
* whether the field was tested alredy or not.
*
* We qualify the test as not missing only if it was already run, if it is omitted,
* or if it is marked as optional, even if the optional check did not apply yet -
* but the test did not reach its final state.
*/

return (
useOptionalFieldConfig(testObject.fieldName) === true ||
optionalTestAwaitsResolution(testObject) ||
testObject.isTested() ||
testObject.isOmitted()
);
}

function optionalTestAwaitsResolution(testObject: VestTest): boolean {
// Does the test belong to an optional field,
// and the test itself is still in an indeterminate state?
return (
useOptionalField(testObject.fieldName).type ===
OptionalFieldTypes.Delayed && testObject.awaitsResolution()
);
}
30 changes: 18 additions & 12 deletions packages/vest/src/core/test/VestTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,22 +133,10 @@ export default class VestTest {
return !this.isFailing();
}

hasFailures(): boolean {
return this.isFailing() || this.isWarning();
}

isNonActionable(): boolean {
return this.isSkipped() || this.isOmitted() || this.isCanceled();
}

isPending(): boolean {
return this.statusEquals(STATUS_PENDING);
}

isTested(): boolean {
return this.hasFailures() || this.isPassing();
}

isOmitted(): boolean {
return this.statusEquals(STATUS_OMITTED);
}
Expand Down Expand Up @@ -177,6 +165,24 @@ export default class VestTest {
return this.statusEquals(STATUS_WARNING);
}

hasFailures(): boolean {
return this.isFailing() || this.isWarning();
}

isNonActionable(): boolean {
return this.isSkipped() || this.isOmitted() || this.isCanceled();
}

isTested(): boolean {
return this.hasFailures() || this.isPassing();
}

awaitsResolution(): boolean {
// Is the test in a state where it can still be run, or complete running
// and its final status is indeterminate?
return this.isSkipped() || this.isUntested() || this.isPending();
}

statusEquals(status: KStatus): boolean {
return this.status === status;
}
Expand Down
5 changes: 3 additions & 2 deletions packages/vest/src/core/test/lib/registerPrevRunTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import cancelOverriddenPendingTest from 'cancelOverriddenPendingTest';
import { isExcluded } from 'exclusive';
import { useCursor } from 'isolateHooks';
import { shouldSkipBasedOnMode } from 'mode';
import { isOmitted } from 'omitWhen';
import { inActiveOmitWhen } from 'omitWhen';
import { optionalFiedIsApplied } from 'optionalFields';
import registerTest from 'registerTest';
import runAsyncTest from 'runAsyncTest';
import { isExcludedIndividually } from 'skipWhen';
Expand All @@ -25,7 +26,7 @@ export default function registerPrevRunTest(testObject: VestTest): VestTest {

const prevRunTest = useTestAtCursor(testObject);

if (isOmitted()) {
if (inActiveOmitWhen() || optionalFiedIsApplied(testObject.fieldName)) {
prevRunTest.omit();
cursor.next();
return prevRunTest;
Expand Down
Loading

1 comment on commit d7d0893

@vercel
Copy link

@vercel vercel bot commented on d7d0893 Jul 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

vest-next – ./website

vest-next-git-latest-ealush.vercel.app
vest-next.vercel.app
vest-website.vercel.app
vest-next-ealush.vercel.app

Please sign in to comment.