UNPKG

@jivanf/vest

Version:

Declarative Form Validations Framework

1,279 lines (1,242 loc) 77.2 kB
(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