@jivanf/vest
Version:
Declarative Form Validations Framework
1,279 lines (1,242 loc) • 77.2 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('n4s'), require('vest-utils'), require('vestjs-runtime'), require('context'), require('@angular/core')) :
typeof define === 'function' && define.amd ? define(['exports', 'n4s', 'vest-utils', 'vestjs-runtime', 'context', '@angular/core'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vest = {}, global.n4s, global["vest-utils"], global["vestjs-runtime"], global.context, global.core));
})(this, (function (exports, n4s, vestUtils, vestjsRuntime, context, core) { 'use strict';
const VestIsolateType = {
Each: 'Each',
Focused: 'Focused',
Group: 'Group',
OmitWhen: 'OmitWhen',
SkipWhen: 'SkipWhen',
Suite: 'Suite',
Test: 'Test',
};
function IsolateSuite(callback) {
return vestjsRuntime.Isolate.create(VestIsolateType.Suite, callback, {
optional: {},
});
}
class SuiteOptionalFields {
static setOptionalField(suite, fieldName, setter) {
const current = suite.data.optional;
const currentField = current[fieldName];
vestUtils.assign(current, {
[fieldName]: vestUtils.assign({}, currentField, setter(currentField)),
});
}
static getOptionalField(suite, fieldName) {
var _a;
return (_a = SuiteOptionalFields.getOptionalFields(suite)[fieldName]) !== null && _a !== void 0 ? _a : {};
}
static getOptionalFields(suite) {
var _a, _b;
return (_b = (_a = suite.data) === null || _a === void 0 ? void 0 : _a.optional) !== null && _b !== void 0 ? _b : {};
}
}
var OptionalFieldTypes;
(function (OptionalFieldTypes) {
OptionalFieldTypes[OptionalFieldTypes["CUSTOM_LOGIC"] = 0] = "CUSTOM_LOGIC";
OptionalFieldTypes[OptionalFieldTypes["AUTO"] = 1] = "AUTO";
})(OptionalFieldTypes || (OptionalFieldTypes = {}));
exports.Modes = void 0;
(function (Modes) {
Modes["EAGER"] = "EAGER";
Modes["ALL"] = "ALL";
Modes["ONE"] = "ONE";
})(exports.Modes || (exports.Modes = {}));
const SuiteContext = context.createCascade((ctxRef, parentContext) => {
if (parentContext) {
return null;
}
return vestUtils.assign({
inclusion: {},
mode: vestUtils.tinyState.createTinyState(exports.Modes.EAGER),
suiteParams: [],
testMemoCache,
}, ctxRef);
});
function useCurrentTest(msg) {
return SuiteContext.useX(msg).currentTest;
}
function useGroupName() {
return SuiteContext.useX().groupName;
}
function useInclusion() {
return SuiteContext.useX().inclusion;
}
function useMode() {
return SuiteContext.useX().mode();
}
function useSkipped() {
var _a;
return (_a = SuiteContext.useX().skipped) !== null && _a !== void 0 ? _a : false;
}
function useOmitted() {
var _a;
return (_a = SuiteContext.useX().omitted) !== null && _a !== void 0 ? _a : false;
}
const testMemoCache = vestUtils.cache(10);
function useTestMemoCache() {
return SuiteContext.useX().testMemoCache;
}
function useSuiteParams() {
return SuiteContext.useX().suiteParams;
}
// @vx-allow use-use
function optional(optionals) {
var _a;
const suiteRoot = vestjsRuntime.VestRuntime.useAvailableRoot();
const suiteParams = useSuiteParams();
const dataObject = (_a = suiteParams === null || suiteParams === void 0 ? void 0 : suiteParams[0]) !== null && _a !== void 0 ? _a : {};
// There are two types of optional field declarations:
// 1 AUTO: Vest will automatically determine whether the field should be omitted
// Based on the current run. Vest will omit "auto" added fields without any
// configuration if their tests did not run at all in the suite, or if the data object
// contains a blank value for the field.
//
// 2 Custom logic: Vest will determine whether they should fail based on the custom
// logic supplied by the developer.
// If the developer supplies a function - when the function returns true, the field will be omitted.
// If the developer supplies a boolean - the field will be omitted if the value is true.
// If the developer supplies a value - the field will be omitted if the value is blank.
// AUTO case (field name)
if (vestUtils.isArray(optionals) || vestUtils.isStringValue(optionals)) {
vestUtils.asArray(optionals).forEach(optionalField => {
SuiteOptionalFields.setOptionalField(suiteRoot, optionalField, () => ({
type: OptionalFieldTypes.AUTO,
applied: vestUtils.hasOwnProperty(dataObject, optionalField)
? n4s.enforce.isBlank().test(dataObject === null || dataObject === void 0 ? void 0 : dataObject[optionalField])
: false,
rule: null,
}));
});
}
else {
// CUSTOM_LOGIC case (function or boolean)
for (const field in optionals) {
const value = optionals[field];
SuiteOptionalFields.setOptionalField(suiteRoot, field, () => ({
type: OptionalFieldTypes.CUSTOM_LOGIC,
rule: value,
applied: n4s.enforce.isBlank().test(value) || value === true,
}));
}
}
}
function useIsOptionalFieldApplied(fieldName) {
var _a, _b;
if (!fieldName) {
return false;
}
const root = vestjsRuntime.VestRuntime.useAvailableRoot();
return ((_b = (_a = SuiteOptionalFields.getOptionalField(root, fieldName)) === null || _a === void 0 ? void 0 : _a.applied) !== null && _b !== void 0 ? _b : false);
}
const CommonStates = {
PENDING: 'PENDING',
INITIAL: 'INITIAL',
DONE: 'DONE',
};
const State = {
[CommonStates.PENDING]: CommonStates.PENDING,
[CommonStates.INITIAL]: CommonStates.INITIAL,
[CommonStates.DONE]: CommonStates.DONE,
};
const machine$1 = {
initial: State.INITIAL,
states: {
[State.DONE]: {},
[State.INITIAL]: {
[State.PENDING]: State.PENDING,
[State.DONE]: State.DONE,
},
[State.PENDING]: {
[State.DONE]: State.DONE,
},
},
};
const CommonStateMachine = vestUtils.StateMachine(machine$1);
const TestStatus = {
[CommonStates.PENDING]: CommonStates.PENDING,
CANCELED: 'CANCELED',
FAILED: 'FAILED',
OMITTED: 'OMITTED',
PASSING: 'PASSING',
SKIPPED: 'SKIPPED',
UNTESTED: 'UNTESTED',
WARNING: 'WARNING',
};
const TestAction = {
RESET: 'RESET',
};
const machine = {
initial: TestStatus.UNTESTED,
states: {
'*': {
[TestStatus.OMITTED]: TestStatus.OMITTED,
[TestAction.RESET]: TestStatus.UNTESTED,
},
[TestStatus.UNTESTED]: {
[TestStatus.CANCELED]: TestStatus.CANCELED,
[TestStatus.FAILED]: TestStatus.FAILED,
[TestStatus.PASSING]: TestStatus.PASSING,
[TestStatus.PENDING]: TestStatus.PENDING,
[TestStatus.SKIPPED]: TestStatus.SKIPPED,
[TestStatus.WARNING]: TestStatus.WARNING,
},
[TestStatus.PENDING]: {
[TestStatus.CANCELED]: TestStatus.CANCELED,
[TestStatus.FAILED]: TestStatus.FAILED,
[TestStatus.PASSING]: TestStatus.PASSING,
[TestStatus.SKIPPED]: [
TestStatus.SKIPPED,
(force) => force === true,
],
[TestStatus.WARNING]: TestStatus.WARNING,
},
[TestStatus.SKIPPED]: {},
[TestStatus.FAILED]: {},
[TestStatus.WARNING]: {},
[TestStatus.PASSING]: {},
[TestStatus.CANCELED]: {},
[TestStatus.OMITTED]: {},
},
};
const IsolateTestStateMachine = vestUtils.StateMachine(machine);
var Severity;
(function (Severity) {
Severity["WARNINGS"] = "warnings";
Severity["ERRORS"] = "errors";
})(Severity || (Severity = {}));
var SeverityCount;
(function (SeverityCount) {
SeverityCount["ERROR_COUNT"] = "errorCount";
SeverityCount["WARN_COUNT"] = "warnCount";
})(SeverityCount || (SeverityCount = {}));
function countKeyBySeverity(severity) {
return severity === Severity.ERRORS
? SeverityCount.ERROR_COUNT
: SeverityCount.WARN_COUNT;
}
var TestSeverity;
(function (TestSeverity) {
TestSeverity["Error"] = "error";
TestSeverity["Warning"] = "warning";
})(TestSeverity || (TestSeverity = {}));
function IsolateTest(callback, input, key) {
const payload = Object.assign(Object.assign({}, IsolateTestBase()), { fieldName: input.fieldName, testFn: input.testFn });
if (input.groupName) {
payload.groupName = input.groupName;
}
if (input.message) {
payload.message = input.message;
}
const isolate = vestjsRuntime.Isolate.create(VestIsolateType.Test, callback, payload, key !== null && key !== void 0 ? key : null);
return isolate;
}
function IsolateTestBase() {
return {
severity: TestSeverity.Error,
status: IsolateTestStateMachine.initial(),
};
}
/**
* Injection token for accessing the current test context in Angular environments.
* Used internally by runSyncTest to provide the test object to the test function.
*/
const VEST_TEST_CONTEXT = new core.InjectionToken('VEST_TEST_CONTEXT');
var ErrorStrings;
(function (ErrorStrings) {
ErrorStrings["HOOK_CALLED_OUTSIDE"] = "hook called outside of a running suite.";
ErrorStrings["EXPECTED_VEST_TEST"] = "Expected value to be an instance of IsolateTest";
ErrorStrings["FIELD_NAME_REQUIRED"] = "Field name must be passed";
ErrorStrings["SUITE_MUST_BE_INITIALIZED_WITH_FUNCTION"] = "Suite must be initialized with a function";
ErrorStrings["PROMISIFY_REQUIRE_FUNCTION"] = "Vest.Promisify must be called with a function";
ErrorStrings["PARSER_EXPECT_RESULT_OBJECT"] = "Vest parser: expected argument at position 0 to be Vest's result object.";
ErrorStrings["WARN_MUST_BE_CALLED_FROM_TEST"] = "Warn must be called from within the body of a test function";
ErrorStrings["EACH_CALLBACK_MUST_BE_A_FUNCTION"] = "Each must be called with a function";
ErrorStrings["INVALID_PARAM_PASSED_TO_FUNCTION"] = "Incompatible params passed to {fn_name} function. \"{param}\" must be of type {expected}";
ErrorStrings["TESTS_CALLED_IN_DIFFERENT_ORDER"] = "Vest Critical Error: Tests called in different order than previous run.\n expected: {fieldName}\n received: {prevName}\n This can happen on one of two reasons:\n 1. You're using if/else statements to conditionally select tests. Instead, use \"skipWhen\".\n 2. You are iterating over a list of tests, and their order changed. Use \"each\" and a custom key prop so that Vest retains their state.";
ErrorStrings["UNEXPECTED_TEST_REGISTRATION_ERROR"] = "Unexpected error encountered during test registration.\n Please report this issue to Vest's Github repository.\n Test Object: {testObject}.\n Error: {error}.";
ErrorStrings["UNEXPECTED_TEST_RUN_ERROR"] = "Unexpected error encountered during test run. Please report this issue to Vest's Github repository.\n Test Object: {testObject}.";
ErrorStrings["INCLUDE_SELF"] = "Trying to call include.when on the same field.";
})(ErrorStrings || (ErrorStrings = {}));
class VestIsolate {
static getStatus(isolate) {
var _a;
return (_a = isolate.status) !== null && _a !== void 0 ? _a : CommonStates.INITIAL;
}
static setStatus(isolate, status, payload) {
isolate.status = this.stateMachine.staticTransition(VestIsolate.getStatus(isolate), status, payload);
}
static statusEquals(isolate, status) {
return VestIsolate.getStatus(isolate) === status;
}
static setPending(isolate) {
this.setStatus(isolate, CommonStates.PENDING);
}
static setDone(isolate) {
this.setStatus(isolate, CommonStates.DONE);
}
static isPending(isolate) {
return VestIsolate.statusEquals(isolate, CommonStates.PENDING);
}
}
VestIsolate.stateMachine = CommonStateMachine;
class VestTest extends VestIsolate {
// Read
static getData(test) {
vestUtils.invariant(test.data);
return test.data;
}
static is(isolate) {
return vestjsRuntime.IsolateSelectors.isIsolateType(isolate, VestIsolateType.Test);
}
static isX(isolate) {
vestUtils.invariant(VestTest.is(isolate), ErrorStrings.EXPECTED_VEST_TEST);
}
static cast(isolate) {
VestTest.isX(isolate);
return isolate;
}
static warns(test) {
return VestTest.getData(test).severity === TestSeverity.Warning;
}
static isOmitted(test) {
return VestTest.statusEquals(test, TestStatus.OMITTED);
}
static isUntested(test) {
return VestTest.statusEquals(test, TestStatus.UNTESTED);
}
static isFailing(test) {
return VestTest.statusEquals(test, TestStatus.FAILED);
}
static isCanceled(test) {
return VestTest.statusEquals(test, TestStatus.CANCELED);
}
static isSkipped(test) {
return VestTest.statusEquals(test, TestStatus.SKIPPED);
}
static isPassing(test) {
return VestTest.statusEquals(test, TestStatus.PASSING);
}
static isWarning(test) {
return VestTest.statusEquals(test, TestStatus.WARNING);
}
static hasFailures(test) {
return VestTest.isFailing(test) || VestTest.isWarning(test);
}
static isNonActionable(test) {
return (VestTest.isSkipped(test) ||
VestTest.isOmitted(test) ||
VestTest.isCanceled(test));
}
static isTested(test) {
return VestTest.hasFailures(test) || VestTest.isPassing(test);
}
static awaitsResolution(test) {
// Is the test in a state where it can still be run, or complete running
// and its final status is indeterminate?
return (VestTest.isSkipped(test) ||
VestTest.isUntested(test) ||
VestTest.isPending(test));
}
static isAsyncTest(test) {
return vestUtils.isPromise(VestTest.getData(test).asyncTest);
}
// Mutate
// static setPending(test: TIsolateTest) {
// this.setStatus(test, TestStatus.PENDING);
// }
static fail(test) {
VestTest.setStatus(test, VestTest.warns(test) ? TestStatus.WARNING : TestStatus.FAILED);
}
static pass(test) {
VestTest.setStatus(test, TestStatus.PASSING);
}
static warn(test) {
VestTest.setData(test, current => (Object.assign(Object.assign({}, current), { severity: TestSeverity.Warning })));
}
static setData(test, setter) {
test.data = vestUtils.optionalFunctionValue(setter, VestTest.getData(test));
}
static skip(test, force) {
// Without this force flag, the test will be marked as skipped even if it is pending.
// This means that it will not be counted in "allIncomplete" and its done callbacks
// will not be called, or will be called prematurely.
// What this mostly say is that when we have a pending test for one field, and we then
// start typing in a different field - the pending test will be canceled, which
// is usually an unwanted behavior.
// The only scenario in which we DO want to cancel the async test regardless
// is when we specifically skip a test with `skipWhen`, which is handled by the
// "force" boolean flag.
// I am not a fan of this flag, but it gets the job done.
VestTest.setStatus(test, TestStatus.SKIPPED, force);
}
static cancel(test) {
VestTest.setStatus(test, TestStatus.CANCELED);
vestjsRuntime.IsolateMutator.abort(test, TestStatus.CANCELED);
}
static omit(test) {
VestTest.setStatus(test, TestStatus.OMITTED);
}
static reset(test) {
VestTest.setStatus(test, TestAction.RESET);
}
}
VestTest.stateMachine = IsolateTestStateMachine;
function nonMatchingFieldName(WithFieldName, fieldName) {
return !!fieldName && !matchingFieldName(WithFieldName, fieldName);
}
function matchingFieldName(WithFieldName, fieldName) {
return !!(fieldName && WithFieldName.fieldName === fieldName);
}
function matchesOrHasNoFieldName(WithFieldName, fieldName) {
if (fieldName) {
return matchingFieldName(WithFieldName, fieldName);
}
return true;
}
function isSameProfileTest(testObject1, testObject2) {
const { groupName: gn1 } = VestTest.getData(testObject1);
const { groupName: gn2, fieldName: fn2 } = VestTest.getData(testObject2);
return (matchingFieldName(VestTest.getData(testObject1), fn2) &&
gn1 === gn2 &&
// Specifically using == here. The reason is that when serializing
// suite result, empty key gets removed, but it can also be null.
testObject1.key == testObject2.key);
}
function cancelOverriddenPendingTest(prevRunTestObject, currentRunTestObject) {
if (currentRunTestObject !== prevRunTestObject &&
isSameProfileTest(prevRunTestObject, currentRunTestObject) &&
VestTest.isPending(prevRunTestObject)) {
VestTest.cancel(prevRunTestObject);
}
}
var FocusModes;
(function (FocusModes) {
FocusModes[FocusModes["ONLY"] = 0] = "ONLY";
FocusModes[FocusModes["SKIP"] = 1] = "SKIP";
})(FocusModes || (FocusModes = {}));
function IsolateFocused(focusMode, match) {
return vestjsRuntime.Isolate.create(VestIsolateType.Focused, vestUtils.noop, {
focusMode,
match: vestUtils.asArray(match).filter(vestUtils.isStringValue),
matchAll: match === true,
});
}
class FocusSelectors {
static isSkipFocused(focus, fieldName) {
return ((focus === null || focus === void 0 ? void 0 : focus.data.focusMode) === FocusModes.SKIP &&
(hasFocus(focus, fieldName) || focus.data.matchAll === true));
}
static isOnlyFocused(focus, fieldName) {
return ((focus === null || focus === void 0 ? void 0 : focus.data.focusMode) === FocusModes.ONLY && hasFocus(focus, fieldName));
}
static isIsolateFocused(isolate) {
return vestjsRuntime.IsolateSelectors.isIsolateType(isolate, VestIsolateType.Focused);
}
}
/**
* Adds a field or a list of fields into the inclusion list
*
* @example
*
* only('username');
*/
// @vx-allow use-use
function only(match) {
return IsolateFocused(FocusModes.ONLY, defaultMatch(match));
}
/**
* Adds a field or a list of fields into the exclusion list
*
* @example
*
* skip('username');
*/
// @vx-allow use-use
function skip(match) {
return IsolateFocused(FocusModes.SKIP, defaultMatch(match));
}
function defaultMatch(match) {
return match === false ? [] : match;
}
function hasFocus(focus, fieldName) {
var _a, _b;
return (vestUtils.isNotEmpty(focus === null || focus === void 0 ? void 0 : focus.data.match) &&
(fieldName ? (_b = (_a = focus === null || focus === void 0 ? void 0 : focus.data.match) === null || _a === void 0 ? void 0 : _a.includes(fieldName)) !== null && _b !== void 0 ? _b : true : true));
}
var _a, _b;
class SummaryBase {
constructor() {
this.errorCount = 0;
this.warnCount = 0;
this.testCount = 0;
this.pendingCount = 0;
}
}
class SuiteSummary extends SummaryBase {
constructor() {
super(...arguments);
this[_a] = [];
this[_b] = [];
this.groups = {};
this.tests = {};
this.valid = null;
}
}
_a = Severity.ERRORS, _b = Severity.WARNINGS;
const suiteResultCache = vestUtils.cache();
const preAggCache = vestUtils.cache();
function useCreateVestState({ suiteName, VestReconciler, }) {
const stateRef = {
doneCallbacks: vestUtils.tinyState.createTinyState(() => []),
fieldCallbacks: vestUtils.tinyState.createTinyState(() => ({})),
preAggCache,
suiteId: vestUtils.seq(),
suiteName,
suiteResultCache,
};
return vestjsRuntime.VestRuntime.createRef(VestReconciler, stateRef);
}
function useX() {
return vestjsRuntime.VestRuntime.useXAppData();
}
function useDoneCallbacks() {
return useX().doneCallbacks();
}
function useFieldCallbacks() {
return useX().fieldCallbacks();
}
function useSuiteName() {
return useX().suiteName;
}
function useSuiteId() {
return useX().suiteId;
}
function useSuiteResultCache(action) {
const suiteResultCache = useX().suiteResultCache;
return suiteResultCache([useSuiteId()], action);
}
function usePreAggCache(action) {
const preAggCache = useX().preAggCache;
return preAggCache([useSuiteId()], action);
}
function useExpireSuiteResultCache() {
const suiteResultCache = useX().suiteResultCache;
suiteResultCache.invalidate([useSuiteId()]);
// whenever we invalidate the entire result, we also want to invalidate the preagg cache
// so that we do not get stale results there.
// there may be a better place to do this, but for now, this should work.
preAggCache.invalidate([useSuiteId()]);
}
function useResetCallbacks() {
const [, , resetDoneCallbacks] = useDoneCallbacks();
const [, , resetFieldCallbacks] = useFieldCallbacks();
resetDoneCallbacks();
resetFieldCallbacks();
}
function useResetSuite() {
useResetCallbacks();
vestjsRuntime.VestRuntime.reset();
}
function useLoadSuite(rootNode) {
vestjsRuntime.VestRuntime.useSetHistoryRoot(rootNode);
useExpireSuiteResultCache();
}
// calls collectAll or getByFieldName depending on whether fieldName is provided
function gatherFailures(testGroup, severityKey, fieldName) {
return fieldName
? getByFieldName(testGroup, severityKey, fieldName)
: collectAll(testGroup, severityKey);
}
function getByFieldName(testGroup, severityKey, fieldName) {
var _a;
return ((_a = testGroup === null || testGroup === void 0 ? void 0 : testGroup[fieldName]) === null || _a === void 0 ? void 0 : _a[severityKey]) || [];
}
function collectAll(testGroup, severityKey) {
const output = {};
const countKey = countKeyBySeverity(severityKey);
for (const field in testGroup) {
if (vestUtils.isPositive(testGroup[field][countKey])) {
// We will probably never get to the fallback array
// leaving it just in case the implementation changes
output[field] = testGroup[field][severityKey] || [];
}
}
return output;
}
function bindSuiteSelectors(get) {
return {
getError: (...args) => get().getError(...args),
getErrors: (...args) => get().getErrors(...args),
getErrorsByGroup: (...args) => get().getErrorsByGroup(...args),
getMessage: (...args) => get().getMessage(...args),
getWarning: (...args) => get().getWarning(...args),
getWarnings: (...args) => get().getWarnings(...args),
getWarningsByGroup: (...args) => get().getWarningsByGroup(...args),
hasErrors: (...args) => get().hasErrors(...args),
hasErrorsByGroup: (...args) => get().hasErrorsByGroup(...args),
hasWarnings: (...args) => get().hasWarnings(...args),
hasWarningsByGroup: (...args) => get().hasWarningsByGroup(...args),
isPending: (...args) => {
return get().isPending(...args);
},
isTested: (...args) => get().isTested(...args),
isValid: (...args) => get().isValid(...args),
isValidByGroup: (...args) => get().isValidByGroup(...args),
};
}
// eslint-disable-next-line max-lines-per-function, max-statements
function suiteSelectors(summary) {
const selectors = {
getError,
getErrors,
getErrorsByGroup,
getMessage,
getWarning,
getWarnings,
getWarningsByGroup,
hasErrors,
hasErrorsByGroup,
hasWarnings,
hasWarningsByGroup,
isPending,
isTested,
isValid,
isValidByGroup,
};
return selectors;
// Booleans
function isValid(fieldName) {
var _a;
return Boolean(fieldName ? (_a = summary.tests[fieldName]) === null || _a === void 0 ? void 0 : _a.valid : summary.valid);
}
function isValidByGroup(groupName, fieldName) {
const group = summary.groups[groupName];
if (!group) {
return false;
}
if (fieldName) {
return isFieldValid(group, fieldName);
}
for (const fieldName in group) {
if (!isFieldValid(group, fieldName)) {
return false;
}
}
return true;
}
function hasWarnings(fieldName) {
return hasFailures(summary, SeverityCount.WARN_COUNT, fieldName);
}
function hasErrors(fieldName) {
return hasFailures(summary, SeverityCount.ERROR_COUNT, fieldName);
}
function isTested(fieldName) {
var _a;
return vestUtils.isPositive((_a = summary.tests[fieldName]) === null || _a === void 0 ? void 0 : _a.testCount);
}
function hasWarningsByGroup(groupName, fieldName) {
return hasFailuresByGroup(summary, SeverityCount.WARN_COUNT, groupName, fieldName);
}
function hasErrorsByGroup(groupName, fieldName) {
return hasFailuresByGroup(summary, SeverityCount.ERROR_COUNT, groupName, fieldName);
}
function getWarnings(fieldName) {
return getFailures(summary, Severity.WARNINGS, fieldName);
}
function getWarning(fieldName) {
return getFailure(Severity.WARNINGS, summary, fieldName);
}
function getErrors(fieldName) {
return getFailures(summary, Severity.ERRORS, fieldName);
}
function getError(fieldName) {
return getFailure(Severity.ERRORS, summary, fieldName);
}
function getErrorsByGroup(groupName, fieldName) {
return getFailuresByGroup(summary, Severity.ERRORS, groupName, fieldName);
}
function getMessage(fieldName) {
return getError(fieldName) || getWarning(fieldName);
}
function getWarningsByGroup(groupName, fieldName) {
return getFailuresByGroup(summary, Severity.WARNINGS, groupName, fieldName);
}
function isPending(fieldName) {
var _a;
return fieldName
? vestUtils.greaterThan((_a = summary.tests[fieldName]) === null || _a === void 0 ? void 0 : _a.pendingCount, 0)
: vestUtils.greaterThan(summary.pendingCount, 0);
}
}
function getFailures(summary, severityKey, fieldName) {
return gatherFailures(summary.tests, severityKey, fieldName);
}
// Gathers all failures of a given severity within a group
// With a fieldName, it will only gather failures for that field
function getFailuresByGroup(summary, severityKey, groupName, fieldName) {
return gatherFailures(summary.groups[groupName], severityKey, fieldName);
}
// Checks if a field is valid within a container object - can be within a group or top level
function isFieldValid(testContainer, fieldName) {
var _a;
return !!((_a = testContainer[fieldName]) === null || _a === void 0 ? void 0 : _a.valid);
}
// Checks if a there are any failures of a given severity within a group
// If a fieldName is provided, it will only check for failures within that field
function hasFailuresByGroup(summary, severityCount, groupName, fieldName) {
var _a, _b;
const group = summary.groups[groupName];
if (!group) {
return false;
}
if (fieldName) {
return vestUtils.isPositive((_a = group[fieldName]) === null || _a === void 0 ? void 0 : _a[severityCount]);
}
for (const field in group) {
if (vestUtils.isPositive((_b = group[field]) === null || _b === void 0 ? void 0 : _b[severityCount])) {
return true;
}
}
return false;
}
// Checks if there are any failures of a given severity
// If a fieldName is provided, it will only check for failures within that field
function hasFailures(summary, countKey, fieldName) {
var _a;
const failureCount = fieldName
? (_a = summary.tests[fieldName]) === null || _a === void 0 ? void 0 : _a[countKey]
: summary[countKey] || 0;
return vestUtils.isPositive(failureCount);
}
function getFailure(severity, summary, fieldName) {
var _a;
const summaryKey = summary[severity];
if (!fieldName) {
return summaryKey[0];
}
return (_a = summaryKey.find((summaryFailure) => matchingFieldName(summaryFailure, fieldName))) === null || _a === void 0 ? void 0 : _a.message;
}
class SummaryFailure {
constructor(fieldName, message, groupName) {
this.fieldName = fieldName;
this.message = message;
this.groupName = groupName;
}
static fromTestObject(testObject) {
const { fieldName, message, groupName } = VestTest.getData(testObject);
return new SummaryFailure(fieldName, message, groupName);
}
}
class TestWalker {
static hasNoTests(root = TestWalker.defaultRoot()) {
if (!root)
return true;
return !vestjsRuntime.Walker.has(root, VestTest.is);
}
static someTests(predicate, root = TestWalker.defaultRoot()) {
if (!root)
return false;
return vestjsRuntime.Walker.some(root, isolate => {
VestTest.isX(isolate);
return predicate(isolate);
}, VestTest.is);
}
static everyTest(predicate, root = TestWalker.defaultRoot()) {
if (!root)
return false;
return vestjsRuntime.Walker.every(root, isolate => {
VestTest.isX(isolate);
return predicate(isolate);
}, VestTest.is);
}
static walkTests(callback, root = TestWalker.defaultRoot()) {
if (!root)
return;
vestjsRuntime.Walker.walk(root, (isolate, breakout) => {
callback(VestTest.cast(isolate), breakout);
}, VestTest.is);
}
static reduceTests(callback, initialValue, root = TestWalker.defaultRoot()) {
if (!root)
return initialValue;
return vestjsRuntime.Walker.reduce(root, (acc, isolate, breakout) => {
return callback(acc, VestTest.cast(isolate), breakout);
}, initialValue, VestTest.is);
}
static pluckTests(predicate, root = TestWalker.defaultRoot()) {
if (!root)
return;
vestjsRuntime.Walker.pluck(root, isolate => {
VestTest.isX(isolate);
return predicate(isolate);
}, VestTest.is);
}
static resetField(fieldName) {
TestWalker.walkTests(testObject => {
if (matchingFieldName(VestTest.getData(testObject), fieldName)) {
VestTest.reset(testObject);
}
}, TestWalker.defaultRoot());
}
static removeTestByFieldName(fieldName, root = TestWalker.defaultRoot()) {
TestWalker.pluckTests(testObject => {
return matchingFieldName(VestTest.getData(testObject), fieldName);
}, root);
}
}
TestWalker.defaultRoot = vestjsRuntime.VestRuntime.useAvailableRoot;
class SuiteWalker {
static useHasPending(predicate) {
const root = SuiteWalker.defaultRoot();
if (!root) {
return false;
}
const allPending = SuiteWalker.usePreAggs().pending;
if (vestUtils.isEmpty(allPending)) {
return false;
}
return allPending.some(vestUtils.Predicates.all(predicate !== null && predicate !== void 0 ? predicate : true));
}
static usePreAggs() {
return usePreAggCache(buildPreAggCache);
}
// Checks whether there are pending isolates in the tree.
// If a fieldname is provided, will only check tests with a matching fieldname.
static useHasRemainingWithTestNameMatching(fieldName) {
return SuiteWalker.useHasPending(vestUtils.Predicates.any(vestUtils.isNullish(fieldName), vestUtils.Predicates.all(VestTest.is, (testObject) => {
return matchesOrHasNoFieldName(VestTest.getData(testObject), fieldName);
})));
}
}
SuiteWalker.defaultRoot = vestjsRuntime.VestRuntime.useAvailableRoot;
function buildPreAggCache() {
const root = SuiteWalker.defaultRoot();
const base = {
pending: [],
failures: {
errors: {},
warnings: {},
},
};
if (!root) {
return base;
}
return vestjsRuntime.Walker.reduce(root,
// eslint-disable-next-line complexity, max-statements
(agg, isolate) => {
var _a, _b;
if (VestIsolate.isPending(isolate)) {
agg.pending.push(isolate);
}
if (VestTest.is(isolate)) {
const fieldName = VestTest.getData(isolate).fieldName;
if (VestTest.isWarning(isolate)) {
agg.failures.warnings[fieldName] =
(_a = agg.failures.warnings[fieldName]) !== null && _a !== void 0 ? _a : [];
agg.failures.warnings[fieldName].push(isolate);
}
if (VestTest.isFailing(isolate)) {
agg.failures.errors[fieldName] = (_b = agg.failures.errors[fieldName]) !== null && _b !== void 0 ? _b : [];
agg.failures.errors[fieldName].push(isolate);
}
}
return agg;
}, base);
}
const nonMatchingGroupName = vestUtils.bindNot(matchingGroupName);
function matchingGroupName(testObject, groupName) {
return VestTest.getData(testObject).groupName === groupName;
}
/**
* Checks that a given test object matches the currently specified severity level
*/
function nonMatchingSeverityProfile(severity, testObject) {
return vestUtils.either(severity === Severity.WARNINGS, VestTest.warns(testObject));
}
/**
* The difference between this file and hasFailures is that hasFailures uses the static
* summary object, while this one uses the actual validation state
*/
function hasErrorsByTestObjects(fieldName) {
return hasFailuresByTestObjects(Severity.ERRORS, fieldName);
}
function hasFailuresByTestObjects(severityKey, fieldName) {
const allFailures = SuiteWalker.usePreAggs().failures;
if (vestUtils.isEmpty(allFailures[severityKey])) {
return false;
}
if (fieldName) {
return !vestUtils.isEmpty(allFailures[severityKey][fieldName]);
}
return true;
}
function hasGroupFailuresByTestObjects(severityKey, groupName, fieldName) {
return TestWalker.someTests(testObject => {
if (nonMatchingGroupName(testObject, groupName)) {
return false;
}
return hasFailuresByTestObject(testObject, severityKey, fieldName);
});
}
/**
* Determines whether a certain test profile has failures.
*/
function hasFailuresByTestObject(testObject, severityKey, fieldName) {
if (!VestTest.hasFailures(testObject)) {
return false;
}
if (nonMatchingFieldName(VestTest.getData(testObject), fieldName)) {
return false;
}
if (nonMatchingSeverityProfile(severityKey, testObject)) {
return false;
}
return true;
}
function useShouldAddValidProperty(fieldName) {
// Is the field optional, and the optional condition is applied
if (useIsOptionalFieldApplied(fieldName)) {
return true;
}
// Are there no tests?
if (TestWalker.hasNoTests()) {
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 (useHasNonOptionalIncomplete(fieldName)) {
return false;
}
// Does the field have no missing tests?
return useNoMissingTests(fieldName);
}
function useShouldAddValidPropertyInGroup(groupName, fieldName) {
if (useIsOptionalFieldApplied(fieldName)) {
return true;
}
if (hasGroupFailuresByTestObjects(Severity.ERRORS, groupName, fieldName)) {
return false;
}
// Do the given group/field have any pending tests that are not optional?
if (useHasNonOptionalIncompleteByGroup(groupName, fieldName)) {
return false;
}
return useNoMissingTestsByGroup(groupName, fieldName);
}
// Does the given field have any pending tests that are not optional?
function useHasNonOptionalIncomplete(fieldName) {
return SuiteWalker.useHasPending(vestUtils.Predicates.all(VestTest.is, (testObject) => !nonMatchingFieldName(VestTest.getData(testObject), fieldName), () => !useIsOptionalFieldApplied(fieldName)));
}
// Do the given group/field have any pending tests that are not optional?
function useHasNonOptionalIncompleteByGroup(groupName, fieldName) {
return SuiteWalker.useHasPending(vestUtils.Predicates.all(VestTest.is, (testObject) => !nonMatchingGroupName(testObject, groupName), (testObject) => !nonMatchingFieldName(VestTest.getData(testObject), fieldName), () => !useIsOptionalFieldApplied(fieldName)));
}
// Did all of the tests for the provided field run/omit?
// This makes sure that the fields are not skipped or pending.
function useNoMissingTests(fieldName) {
return TestWalker.everyTest(testObject => {
return useNoMissingTestsLogic(testObject, fieldName);
});
}
// Does the group have no missing tests?
function useNoMissingTestsByGroup(groupName, fieldName) {
return TestWalker.everyTest(testObject => {
if (nonMatchingGroupName(testObject, groupName)) {
return true;
}
return useNoMissingTestsLogic(testObject, fieldName);
});
}
function useNoMissingTestsLogic(testObject, fieldName) {
if (nonMatchingFieldName(VestTest.getData(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 already 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 (VestTest.isOmitted(testObject) ||
VestTest.isTested(testObject) ||
useOptionalTestAwaitsResolution(testObject));
}
function useOptionalTestAwaitsResolution(testObject) {
// Does the test belong to an optional field,
// and the test itself is still in an indeterminate state?
const root = vestjsRuntime.VestRuntime.useAvailableRoot();
const { fieldName } = VestTest.getData(testObject);
return (SuiteOptionalFields.getOptionalField(root, fieldName).type ===
OptionalFieldTypes.AUTO && VestTest.awaitsResolution(testObject));
}
function useProduceSuiteSummary() {
// @vx-allow use-use (TODO: fix this. the error is in the lint rule)
const summary = TestWalker.reduceTests((summary, testObject) => {
const fieldName = VestTest.getData(testObject).fieldName;
summary.tests[fieldName] = useAppendToTest(summary.tests, testObject);
summary.groups = useAppendToGroup(summary.groups, testObject);
if (VestTest.isOmitted(testObject)) {
return summary;
}
if (summary.tests[fieldName].valid === false) {
summary.valid = false;
}
return addSummaryStats(testObject, summary);
}, new SuiteSummary());
summary.valid = summary.valid === false ? false : useShouldAddValidProperty();
return summary;
}
function addSummaryStats(testObject, summary) {
if (VestTest.isWarning(testObject)) {
summary.warnCount++;
summary.warnings.push(SummaryFailure.fromTestObject(testObject));
}
else if (VestTest.isFailing(testObject)) {
summary.errorCount++;
summary.errors.push(SummaryFailure.fromTestObject(testObject));
}
if (VestTest.isPending(testObject)) {
summary.pendingCount++;
}
if (shouldCountTestRun(testObject)) {
summary.testCount++;
}
return summary;
}
function useAppendToTest(tests, testObject) {
const fieldName = VestTest.getData(testObject).fieldName;
const test = appendTestObject(tests[fieldName], testObject);
// If `valid` is false to begin with, keep it that way. Otherwise, assess.
test.valid =
test.valid === false ? false : useShouldAddValidProperty(fieldName);
return test;
}
/**
* Appends to a group object if within a group
*/
function useAppendToGroup(groups, testObject) {
const { groupName, fieldName } = VestTest.getData(testObject);
if (!groupName) {
return groups;
}
groups[groupName] = groups[groupName] || {};
const group = groups[groupName];
group[fieldName] = appendTestObject(group[fieldName], testObject);
group[fieldName].valid =
group[fieldName].valid === false
? false
: useShouldAddValidPropertyInGroup(groupName, fieldName);
return groups;
}
/**
* Appends the test to a results object.
*/
// eslint-disable-next-line max-statements, complexity
function appendTestObject(summaryKey, testObject) {
const { message } = VestTest.getData(testObject);
// Let's first create a new object, so we don't mutate the original.
const nextSummaryKey = vestUtils.defaultTo(summaryKey ? Object.assign({}, summaryKey) : null, baseTestStats);
// If the test is not actionable, we don't need to append it to the summary.
if (VestTest.isNonActionable(testObject))
return nextSummaryKey;
// Increment the pending count if the test is pending.
if (VestTest.isPending(testObject)) {
nextSummaryKey.pendingCount++;
}
// Increment the error count if the test is failing.
if (VestTest.isFailing(testObject)) {
incrementFailures(Severity.ERRORS);
}
else if (VestTest.isWarning(testObject)) {
// Increment the warning count if the test is warning.
incrementFailures(Severity.WARNINGS);
}
// Increment the test count.
if (shouldCountTestRun(testObject)) {
nextSummaryKey.testCount++;
}
return nextSummaryKey;
// Helper function to increment the failure count.
function incrementFailures(severity) {
const countKey = countKeyBySeverity(severity);
nextSummaryKey[countKey]++;
if (message) {
nextSummaryKey[severity] = (nextSummaryKey[severity] || []).concat(message);
}
}
}
function baseTestStats() {
return vestUtils.assign(new SummaryBase(), {
errors: [],
valid: true,
warnings: [],
});
}
function shouldCountTestRun(testObject) {
return VestTest.isTested(testObject) || VestTest.isPending(testObject);
}
function useCreateSuiteResult() {
return useSuiteResultCache(() => {
// @vx-allow use-use
const summary = useProduceSuiteSummary();
// @vx-allow use-use
const suiteName = useSuiteName();
return Object.freeze(constructSuiteResultObject(summary, suiteName));
});
}
function constructSuiteResultObject(summary, suiteName) {
return vestUtils.assign(summary, suiteSelectors(summary), {
suiteName,
});
}
// @vx-allow use-use
function LazyDraft() {
const emptySummary = constructSuiteResultObject(new SuiteSummary());
return new Proxy(emptySummary, {
get: (_, prop) => {
// @vx-allow use-use
const result = useCreateSuiteResult();
return result[prop];
},
});
}
/**
* Conditionally skips running tests within the callback.
*
* @example
*
* skipWhen(res => res.hasErrors('username'), () => {
* test('username', 'User already taken', async () => await doesUserExist(username)
* });
*/
// @vx-allow use-use
function skipWhen(condition, callback) {
vestjsRuntime.Isolate.create(VestIsolateType.SkipWhen, () => {
SuiteContext.run({
skipped:
// Checking for nested conditional. If we're in a nested skipWhen,
// we should skip the test if the parent conditional is true.
useIsExcludedIndividually() ||
// Otherwise, we should skip the test if the conditional is true.
vestUtils.optionalFunctionValue(condition, LazyDraft()),
}, callback);
});
}
function useIsExcludedIndividually() {
return useSkipped();
}
/**
* Checks if context has included tests
*/
function useHasOnliedTests(testObject, fieldName) {
return vestUtils.isNotNullish(vestjsRuntime.Walker.findClosest(testObject, (child) => {
if (!FocusSelectors.isIsolateFocused(child))
return false;
return FocusSelectors.isOnlyFocused(child, fieldName);
}));
}
//Checks whether a certain test profile excluded by any of the exclusion groups.
function useClosestMatchingFocus(testObject) {
return vestjsRuntime.Walker.findClosest(testObject, (child) => {
var _a;
if (!FocusSelectors.isIsolateFocused(child))
return false;
const { fieldName } = VestTest.getData(testObject);
return ((_a = child.data.match) === null || _a === void 0 ? void 0 : _a.includes(fieldName)) || child.data.matchAll;
});
}
function useIsExcluded(testObject) {
const { fieldName } = VestTest.getData(testObject);
if (useIsExcludedIndividually())
return true;
const inclusion = useInclusion();
const focusMatch = useClosestMatchingFocus(testObject);
// if test is skipped
// no need to proceed
if (FocusSelectors.isSkipFocused(focusMatch))
return true;
const isTestIncluded = FocusSelectors.isOnlyFocused(focusMatch);
// if field is only'ed
if (isTestIncluded)
return false;
// If there is _ANY_ `only`ed test (and we already know this one isn't) return true
if (useHasOnliedTests(testObject)) {
// Check if inclusion rules for this field (`include` hook)
return !vestUtils.optionalFunctionValue(inclusion[fieldName], testObject);
}
// We're done here. This field is not excluded
return false;
}
/**
* Sets the current execution mode for the current suite.
*
* Supported modes:
* - `EAGER` - (default) Runs all tests, but stops on first failure for each given field.
* - `ALL` - Runs all tests, regardless of failures.
* - `ONE` - Stops suite execution on first failure of any field.
*
* @example
* ```js
* import {Modes, create} from 'vest';
*
* const suite = create('suite_name', () => {
* vest.mode(Modes.ALL);
*
* // ...
* });
* ```
* @param 'ALL' | 'EAGER' | 'ONE' mode - The mode to set.
*/
// @vx-allow use-use
function mode(mode) {
const [, setMode] = useMode();
setMode(mode);
}
function useIsMode(mode) {
const [currentMode] = useMode();
return currentMode === mode;
}
function useIsEager() {
return useIsMode(exports.Modes.EAGER);
}
function useIsOne() {
return useIsMode(exports.Modes.ONE);
}
function useShouldSkipBasedOnMode(testData) {
if (useIsOne()) {
return hasErrorsByTestObjects();
}
if (useIsEager()) {
return hasErrorsByTestObjects(testData.fieldName);
}
return false;
}
/**
* Conditionally omits tests from the suite.
*
* @example
*
* omitWhen(res => res.hasErrors('username'), () => {
* test('username', 'User already taken', async () => await doesUserExist(username)
* });
*/
// @vx-allow use-use
function omitWhen(conditional, callback) {
vestjsRuntime.Isolate.create(VestIsolateType.OmitWhen, () => {
SuiteContext.run({
omitted: useWithinActiveOmitWhen() ||
vestUtils.optionalFunctionValue(conditional, LazyDraft()),
}, callback);
});
}
// Checks that we're currently in an active