├── .DS_Store ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── integration.yml │ └── nightly.yml ├── .gitignore ├── .husky ├── _ │ └── husky.sh └── pre-commit ├── .prettierignore ├── .yarn ├── plugins │ └── @yarnpkg │ │ └── plugin-interactive-tools.cjs └── releases │ └── yarn-3.5.1.cjs ├── .yarnrc.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package.json ├── packages ├── anyone │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── all.test.ts │ │ │ ├── any.test.ts │ │ │ ├── anyoneTestValues.ts │ │ │ ├── none.test.ts │ │ │ ├── one.test.ts │ │ │ └── runAnyoneMethods.test.ts │ │ ├── anyone.ts │ │ ├── exports │ │ │ ├── all.ts │ │ │ ├── any.ts │ │ │ ├── none.ts │ │ │ └── one.ts │ │ └── runner │ │ │ └── runAnyoneMethods.ts │ └── tsconfig.json ├── context │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── cascade.test.ts.snap │ │ │ ├── cascade.test.ts │ │ │ └── context.test.ts │ │ └── context.ts │ └── tsconfig.json ├── n4s │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── enforce.test.ts │ │ │ └── enforceEager.test.ts │ │ ├── exports │ │ │ ├── __tests__ │ │ │ │ ├── compose.test.ts │ │ │ │ ├── date.test.ts │ │ │ │ ├── email.test.ts │ │ │ │ └── isUrl.test.ts │ │ │ ├── compose.ts │ │ │ ├── compounds.ts │ │ │ ├── date.ts │ │ │ ├── email.ts │ │ │ ├── isURL.ts │ │ │ └── schema.ts │ │ ├── lib │ │ │ ├── enforceUtilityTypes.ts │ │ │ ├── ruleReturn.ts │ │ │ ├── runLazyRule.ts │ │ │ └── transformResult.ts │ │ ├── n4s.ts │ │ ├── plugins │ │ │ ├── compounds │ │ │ │ ├── __tests__ │ │ │ │ │ ├── allOf.test.ts │ │ │ │ │ ├── noneOf.test.ts │ │ │ │ │ └── oneOf.test.ts │ │ │ │ ├── allOf.ts │ │ │ │ ├── anyOf.ts │ │ │ │ ├── noneOf.ts │ │ │ │ └── oneOf.ts │ │ │ └── schema │ │ │ │ ├── __tests__ │ │ │ │ ├── isArrayOf.test.ts │ │ │ │ ├── loose.test.ts │ │ │ │ ├── optional.test.ts │ │ │ │ ├── partial.test.ts │ │ │ │ ├── shape&loose.test.ts │ │ │ │ └── shape.test.ts │ │ │ │ ├── isArrayOf.ts │ │ │ │ ├── loose.ts │ │ │ │ ├── optional.ts │ │ │ │ ├── partial.ts │ │ │ │ ├── schemaTypes.ts │ │ │ │ └── shape.ts │ │ ├── rules │ │ │ ├── __tests__ │ │ │ │ ├── endsWith.test.ts │ │ │ │ ├── equals.test.ts │ │ │ │ ├── greaterThanOrEquals.test.ts │ │ │ │ ├── inside.test.ts │ │ │ │ ├── isBetween.test.ts │ │ │ │ ├── isBlank.test.ts │ │ │ │ ├── isBoolean.test.ts │ │ │ │ ├── isEven.test.ts │ │ │ │ ├── isKeyOf.test.ts │ │ │ │ ├── isNaN.test.ts │ │ │ │ ├── isNegative.test.ts │ │ │ │ ├── isNullish.test.ts │ │ │ │ ├── isNumber.test.ts │ │ │ │ ├── isOdd.test.ts │ │ │ │ ├── isPositive.test.ts │ │ │ │ ├── isString.test.ts │ │ │ │ ├── isTruthy.test.ts │ │ │ │ ├── isValueOf.test.ts │ │ │ │ ├── lessThan.test.ts │ │ │ │ ├── lessThanOrEquals.test.ts │ │ │ │ ├── longerThanOrEquals.test.ts │ │ │ │ ├── matches.test.ts │ │ │ │ ├── ruleCondition.test.ts │ │ │ │ ├── rules.test.ts │ │ │ │ ├── shorterThan.test.ts │ │ │ │ ├── shorterThanOrEquals.test.ts │ │ │ │ └── startsWith.test.ts │ │ │ ├── endsWith.ts │ │ │ ├── equals.ts │ │ │ ├── greaterThanOrEquals.ts │ │ │ ├── inside.ts │ │ │ ├── isBetween.ts │ │ │ ├── isBlank.ts │ │ │ ├── isBoolean.ts │ │ │ ├── isEven.ts │ │ │ ├── isKeyOf.ts │ │ │ ├── isNaN.ts │ │ │ ├── isNegative.ts │ │ │ ├── isNumber.ts │ │ │ ├── isOdd.ts │ │ │ ├── isString.ts │ │ │ ├── isTruthy.ts │ │ │ ├── isValueOf.ts │ │ │ ├── lessThan.ts │ │ │ ├── lessThanOrEquals.ts │ │ │ ├── longerThanOrEquals.ts │ │ │ ├── matches.ts │ │ │ ├── ruleCondition.ts │ │ │ ├── shorterThan.ts │ │ │ ├── shorterThanOrEquals.ts │ │ │ └── startsWith.ts │ │ └── runtime │ │ │ ├── __tests__ │ │ │ ├── enforceContext.test.ts │ │ │ └── message.test.ts │ │ │ ├── enforce.ts │ │ │ ├── enforceContext.ts │ │ │ ├── enforceEager.ts │ │ │ ├── genEnforceLazy.ts │ │ │ ├── rules.ts │ │ │ └── runtimeRules.ts │ ├── testUtils │ │ └── TEnforceMock.ts │ └── tsconfig.json ├── vast │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ └── vast.test.ts │ │ └── vast.ts │ └── tsconfig.json ├── vest-utils │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Predicates.ts │ │ ├── SimpleStateMachine.ts │ │ ├── StringObject.ts │ │ ├── __tests__ │ │ │ ├── Predicates.test.ts │ │ │ ├── SimpleStateMachine.test.ts │ │ │ ├── StringObject.test.ts │ │ │ ├── asArray.test.ts │ │ │ ├── bindNot.test.ts │ │ │ ├── bus.test.ts │ │ │ ├── cache.test.ts │ │ │ ├── callEach.test.ts │ │ │ ├── defaultTo.test.ts │ │ │ ├── deferThrow.test.ts │ │ │ ├── either.test.ts │ │ │ ├── greaterThan.test.ts │ │ │ ├── invariant.test.ts │ │ │ ├── isArray.test.ts │ │ │ ├── isBoolean.test.ts │ │ │ ├── isEmpty.test.ts │ │ │ ├── isNull.test.ts │ │ │ ├── isNumeric.test.ts │ │ │ ├── isPositive.test.ts │ │ │ ├── isPromise.test.ts │ │ │ ├── isString.test.ts │ │ │ ├── isUndefined.test.ts │ │ │ ├── lengthEquals.test.ts │ │ │ ├── longerThan.test.ts │ │ │ ├── mapFirst.test.ts │ │ │ ├── nonnullish.test.ts │ │ │ ├── numberEquals.test.ts │ │ │ ├── optionalFunctionValue.test.ts │ │ │ ├── seq.test.ts │ │ │ ├── text.test.ts │ │ │ └── tinyState.test.ts │ │ ├── asArray.ts │ │ ├── assign.ts │ │ ├── bindNot.ts │ │ ├── bus.ts │ │ ├── cache.ts │ │ ├── callEach.ts │ │ ├── defaultTo.ts │ │ ├── deferThrow.ts │ │ ├── either.ts │ │ ├── exports │ │ │ ├── __tests__ │ │ │ │ └── minifyObject.test.ts │ │ │ └── minifyObject.ts │ │ ├── freezeAssign.ts │ │ ├── globals.d.ts │ │ ├── greaterThan.ts │ │ ├── hasOwnProperty.ts │ │ ├── invariant.ts │ │ ├── isArrayValue.ts │ │ ├── isBooleanValue.ts │ │ ├── isEmpty.ts │ │ ├── isFunction.ts │ │ ├── isNull.ts │ │ ├── isNullish.ts │ │ ├── isNumeric.ts │ │ ├── isPositive.ts │ │ ├── isPromise.ts │ │ ├── isStringValue.ts │ │ ├── isUndefined.ts │ │ ├── lengthEquals.ts │ │ ├── longerThan.ts │ │ ├── mapFirst.ts │ │ ├── nonnullish.ts │ │ ├── noop.ts │ │ ├── numberEquals.ts │ │ ├── optionalFunctionValue.ts │ │ ├── seq.ts │ │ ├── text.ts │ │ ├── tinyState.ts │ │ ├── utilityTypes.ts │ │ ├── valueIsObject.ts │ │ └── vest-utils.ts │ └── tsconfig.json ├── vest │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ ├── integration.async-tests.test.ts.snap │ │ │ │ ├── integration.base.test.ts.snap │ │ │ │ ├── integration.stateful-async.test.ts.snap │ │ │ │ └── integration.stateful-tests.test.ts.snap │ │ │ ├── integration.async-tests.test.ts │ │ │ ├── integration.base.test.ts │ │ │ ├── integration.exclusive.test.ts │ │ │ ├── integration.stateful-async.test.ts │ │ │ ├── integration.stateful-tests.test.ts │ │ │ ├── isolate.test.ts │ │ │ └── state_refill.test.ts │ │ ├── core │ │ │ ├── Runtime.ts │ │ │ ├── StateMachines │ │ │ │ ├── CommonStateMachine.ts │ │ │ │ └── IsolateTestStateMachine.ts │ │ │ ├── VestBus │ │ │ │ ├── BusEvents.ts │ │ │ │ └── VestBus.ts │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── runtime.test.ts.snap │ │ │ │ └── runtime.test.ts │ │ │ ├── context │ │ │ │ └── SuiteContext.ts │ │ │ ├── isolate │ │ │ │ ├── IsolateEach │ │ │ │ │ └── IsolateEach.ts │ │ │ │ ├── IsolateReconciler.ts │ │ │ │ ├── IsolateSuite │ │ │ │ │ └── IsolateSuite.ts │ │ │ │ ├── IsolateTest │ │ │ │ │ ├── IsolateTest.ts │ │ │ │ │ ├── IsolateTestReconciler.ts │ │ │ │ │ ├── TestWalker.ts │ │ │ │ │ ├── VestTest.ts │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ ├── VestTestInspector.test.ts │ │ │ │ │ │ └── hasRemainingTests.test.ts │ │ │ │ │ ├── cancelOverriddenPendingTest.ts │ │ │ │ │ └── isSameProfileTest.ts │ │ │ │ ├── VestIsolate.ts │ │ │ │ ├── VestIsolateType.ts │ │ │ │ └── VestReconciler.ts │ │ │ └── test │ │ │ │ ├── TestTypes.ts │ │ │ │ ├── __tests__ │ │ │ │ ├── IsolateTest.test.ts │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── IsolateTest.test.ts.snap │ │ │ │ │ ├── memo.test.ts.snap │ │ │ │ │ └── test.test.ts.snap │ │ │ │ ├── key.test.ts │ │ │ │ ├── memo.test.ts │ │ │ │ ├── merging_of_previous_test_runs.test.ts │ │ │ │ ├── runAsyncTest.test.ts │ │ │ │ ├── test.test.ts │ │ │ │ └── testFunctionPayload.test.ts │ │ │ │ ├── helpers │ │ │ │ ├── __tests__ │ │ │ │ │ └── nonMatchingSeverityProfile.test.ts │ │ │ │ ├── matchingFieldName.ts │ │ │ │ ├── matchingGroupName.ts │ │ │ │ ├── nonMatchingSeverityProfile.ts │ │ │ │ └── shouldUseErrorMessage.ts │ │ │ │ ├── test.memo.ts │ │ │ │ ├── test.ts │ │ │ │ └── testLevelFlowControl │ │ │ │ ├── runTest.ts │ │ │ │ └── verifyTestRun.ts │ │ ├── errors │ │ │ └── ErrorStrings.ts │ │ ├── exports │ │ │ ├── SuiteSerializer.ts │ │ │ ├── __tests__ │ │ │ │ ├── SuiteSerializer.test.ts │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── SuiteSerializer.test.ts.snap │ │ │ │ ├── classnames.test.ts │ │ │ │ ├── debounce.test.ts │ │ │ │ ├── parser.test.ts │ │ │ │ └── promisify.test.ts │ │ │ ├── classnames.ts │ │ │ ├── debounce.ts │ │ │ ├── enforce@compose.ts │ │ │ ├── enforce@compounds.ts │ │ │ ├── enforce@date.ts │ │ │ ├── enforce@email.ts │ │ │ ├── enforce@isURL.ts │ │ │ ├── enforce@schema.ts │ │ │ ├── parser.ts │ │ │ └── promisify.ts │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── include.test.ts.snap │ │ │ │ ├── include.test.ts │ │ │ │ ├── mode.test.ts │ │ │ │ └── warn.test.ts │ │ │ ├── focused │ │ │ │ ├── FocusedKeys.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── focused.test.ts.snap │ │ │ │ │ └── focused.test.ts │ │ │ │ ├── focused.ts │ │ │ │ ├── useHasOnliedTests.ts │ │ │ │ └── useIsExcluded.ts │ │ │ ├── include.ts │ │ │ ├── optional │ │ │ │ ├── Modes.ts │ │ │ │ ├── OptionalTypes.ts │ │ │ │ ├── __tests__ │ │ │ │ │ └── optional.test.ts │ │ │ │ ├── mode.ts │ │ │ │ ├── omitOptionalFields.ts │ │ │ │ └── optional.ts │ │ │ └── warn.ts │ │ ├── isolates │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── each.test.ts.snap │ │ │ │ │ ├── group.test.ts.snap │ │ │ │ │ ├── omitWhen.test.ts.snap │ │ │ │ │ └── skipWhen.test.ts.snap │ │ │ │ ├── each.test.ts │ │ │ │ ├── group.test.ts │ │ │ │ ├── omitWhen.test.ts │ │ │ │ └── skipWhen.test.ts │ │ │ ├── each.ts │ │ │ ├── group.ts │ │ │ ├── omitWhen.ts │ │ │ └── skipWhen.ts │ │ ├── suite │ │ │ ├── SuiteTypes.ts │ │ │ ├── SuiteWalker.ts │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── create.test.ts.snap │ │ │ │ │ └── staticSuite.test.ts.snap │ │ │ │ ├── create.test.ts │ │ │ │ ├── remove.test.ts │ │ │ │ ├── resetField.test.ts │ │ │ │ ├── staticSuite.test.ts │ │ │ │ ├── subscribe.test.ts │ │ │ │ ├── suite.dump.ts │ │ │ │ ├── suiteSelectorsOnSuite.test.ts │ │ │ │ └── typedSuite.test.ts │ │ │ ├── createSuite.ts │ │ │ ├── getTypedMethods.ts │ │ │ ├── runCallbacks.ts │ │ │ └── validateParams │ │ │ │ └── validateSuiteParams.ts │ │ ├── suiteResult │ │ │ ├── Severity.ts │ │ │ ├── SuiteResultTypes.ts │ │ │ ├── SummaryFailure.ts │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── useProduceSuiteSummary.test.ts.snap │ │ │ │ └── useProduceSuiteSummary.test.ts │ │ │ ├── done │ │ │ │ ├── __tests__ │ │ │ │ │ └── done.test.ts │ │ │ │ ├── deferDoneCallback.ts │ │ │ │ └── shouldSkipDoneRegistration.ts │ │ │ ├── selectors │ │ │ │ ├── LazyDraft.ts │ │ │ │ ├── __tests__ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── collectFailureMessages.test.ts.snap │ │ │ │ │ ├── collectFailureMessages.test.ts │ │ │ │ │ ├── getFailure.test.ts │ │ │ │ │ ├── getFailures.test.ts │ │ │ │ │ ├── getFailuresByGroup.test.ts │ │ │ │ │ ├── hasFailures.test.ts │ │ │ │ │ ├── hasFailuresByGroup.test.ts │ │ │ │ │ ├── hasFailuresByTestObject.test.ts │ │ │ │ │ ├── isPending.test.ts │ │ │ │ │ ├── isTested.test.ts │ │ │ │ │ ├── isValid.test.ts │ │ │ │ │ ├── isValidByGroup.test.ts │ │ │ │ │ └── summaryFailures.test.ts │ │ │ │ ├── collectFailures.ts │ │ │ │ ├── hasFailuresByTestObjects.ts │ │ │ │ ├── shouldAddValidProperty.ts │ │ │ │ ├── suiteSelectors.ts │ │ │ │ └── useProduceSuiteSummary.ts │ │ │ ├── suiteResult.ts │ │ │ └── suiteRunResult.ts │ │ ├── testUtils │ │ │ ├── TVestMock.ts │ │ │ ├── __tests__ │ │ │ │ └── partition.test.ts │ │ │ ├── partition.ts │ │ │ ├── suiteDummy.ts │ │ │ ├── testDummy.ts │ │ │ ├── testPromise.ts │ │ │ └── vestMocks.ts │ │ └── vest.ts │ └── tsconfig.json └── vestjs-runtime │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ ├── Bus.ts │ ├── Isolate │ │ ├── Isolate.ts │ │ ├── IsolateInspector.ts │ │ ├── IsolateKeys.ts │ │ ├── IsolateMutator.ts │ │ ├── IsolateSelectors.ts │ │ └── __tests__ │ │ │ ├── Isolate.test.ts │ │ │ ├── IsolateInspector.test.ts │ │ │ ├── IsolateMutator.test.ts │ │ │ ├── __snapshots__ │ │ │ └── asyncIsolate.test.ts.snap │ │ │ └── asyncIsolate.test.ts │ ├── IsolateWalker.ts │ ├── Reconciler.ts │ ├── RuntimeEvents.ts │ ├── VestRuntime.ts │ ├── __tests__ │ │ └── IsolateWalker.test.ts │ ├── errors │ │ └── ErrorStrings.ts │ ├── exports │ │ ├── IsolateSerializer.ts │ │ ├── __tests__ │ │ │ ├── IsolateSerializer.test.ts │ │ │ └── __snapshots__ │ │ │ │ └── IsolateSerializer.test.ts.snap │ │ └── test-utils.ts │ └── vestjs-runtime.ts │ └── tsconfig.json ├── tsconfig.json ├── vitest.config.ts ├── vitest.workspace.js ├── vx ├── cli.js ├── commands │ ├── build │ │ └── build.js │ ├── dev │ │ └── dev.js │ ├── init │ │ ├── init.js │ │ ├── prompt │ │ │ ├── index.js │ │ │ └── package.json │ │ └── template │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ └── tsconfig.json.tmpl │ ├── npmignore │ │ └── npmignore.js │ ├── precommit │ │ └── precommit.js │ ├── prepare │ │ └── prepare.js │ ├── release │ │ └── release.js │ ├── test │ │ └── test.js │ └── tsconfig │ │ └── tsconfig.js ├── config │ ├── rollup │ │ ├── format.js │ │ ├── getPlugins.js │ │ ├── plugins │ │ │ ├── addCJSPackageJson.js │ │ │ ├── addModulePackageJson.js │ │ │ └── handleExports.js │ │ └── rollup.config.cjs │ └── vitest │ │ ├── customMatchers.ts │ │ └── vitest.d.ts ├── eslint-plugin-vest-internal │ ├── lib │ │ ├── index.js │ │ └── rules │ │ │ ├── common │ │ │ ├── directives.js │ │ │ ├── matchers.js │ │ │ └── selectors.js │ │ │ └── use-use.js │ ├── package.json │ └── yarn.lock ├── exec.js ├── getFailure ├── logger.js ├── opts.js ├── package.json ├── packageExports.js ├── packageNames.js ├── scripts │ ├── build │ │ ├── buildPackage.js │ │ └── cleanupDistFiles.js │ ├── genNpmIgnore.js │ ├── genTsConfig.js │ ├── genVitestConfig.js │ └── release │ │ ├── depsTree.js │ │ ├── determineChangeLevel.js │ │ ├── genDiffData.js │ │ ├── github │ │ ├── commitIgnorePattern.js │ │ ├── createRelease.js │ │ ├── getDiff.js │ │ ├── listAllChangedPackages.js │ │ ├── listAllChangesSinceStableBranch.js │ │ └── matchPackageNameInCommit.js │ │ ├── packagesToRelease.js │ │ ├── releaseKeywords.js │ │ ├── releasePackage.js │ │ └── steps │ │ ├── commitChangesToGit.js │ │ ├── create_git_tag.sh │ │ ├── publishPackage.js │ │ ├── push_to_latest_branch.sh │ │ ├── setNextVersion.js │ │ ├── updateChangelog.js │ │ └── updateLocalDepsToLatest.js ├── util │ ├── concatTruthy.js │ ├── exportedModules.js │ ├── joinTruthy.js │ ├── once.js │ ├── packageJson.js │ ├── packageList.js │ ├── pathsPerPackage.js │ ├── rootPackageJson.js │ ├── runOnActivePackages.js │ └── taggedBranch.js ├── vxContext.js └── vxPath.js ├── website ├── README.md ├── babel.config.js ├── docs │ ├── api_reference.md │ ├── community_resources │ │ ├── _category_.json │ │ ├── integrations.md │ │ ├── showcase.md │ │ └── tutorials.md │ ├── concepts.md │ ├── enforce │ │ ├── _category_.json │ │ ├── builtin-enforce-plugins │ │ │ ├── _category_.json │ │ │ ├── compound_rules.md │ │ │ ├── date.md │ │ │ ├── email.md │ │ │ ├── isUrl.md │ │ │ ├── plugins.md │ │ │ └── schema_rules.md │ │ ├── composing_enforce_rules.md │ │ ├── consuming_external_rules.md │ │ ├── creating_custom_rules.md │ │ ├── enforce.md │ │ ├── enforce_rules.md │ │ └── failing_with_a_message.md │ ├── get_started.md │ ├── recipes │ │ ├── _category_.json │ │ ├── any_test.md │ │ └── typescript_enums.md │ ├── server_side_validations.md │ ├── suite_serialization.md │ ├── typescript_support.md │ ├── understanding_state.md │ ├── upgrade_guide.md │ ├── utilities │ │ ├── _category_.json │ │ ├── classnames.md │ │ └── promisify.md │ ├── vest_vs_the_rest.md │ ├── writing_tests │ │ ├── _category_.json │ │ ├── advanced_test_features │ │ │ ├── _category_.json │ │ │ ├── debounce.md │ │ │ ├── dynamic_tests.md │ │ │ ├── grouping_tests.md │ │ │ └── test.memo.md │ │ ├── async_tests.md │ │ ├── failing_with_a_custom_message.md │ │ ├── the_test_function.md │ │ └── warn_only_tests.md │ └── writing_your_suite │ │ ├── _category_.json │ │ ├── accessing_the_result.md │ │ ├── dirty_checking.md │ │ ├── execution_modes.md │ │ ├── including_and_excluding │ │ ├── _category_.json │ │ ├── include.md │ │ ├── omitWhen.md │ │ ├── skipWhen.md │ │ └── skip_and_only.md │ │ ├── optional_fields.md │ │ └── vests_suite.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── components │ │ ├── Common.module.css │ │ ├── Demo.js │ │ ├── Demo.module.css │ │ ├── HomepageFeatures.js │ │ ├── HomepageFeatures.module.css │ │ ├── RawExample.js │ │ └── RawExample.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ └── vest-5-is-ready.md ├── static │ ├── .nojekyll │ ├── favicon.ico │ └── img │ │ ├── favicon.ico │ │ ├── github.svg │ │ ├── logo.svg │ │ ├── og.jpg │ │ └── play_arrow.svg ├── versioned_docs │ └── version-4.x │ │ ├── api_reference.md │ │ ├── community_resources │ │ ├── _category_.json │ │ ├── integrations.md │ │ ├── showcase.md │ │ └── tutorials.md │ │ ├── concepts.md │ │ ├── enforce │ │ ├── _category_.json │ │ ├── builtin-enforce-plugins │ │ │ ├── _category_.json │ │ │ ├── compound_rules.md │ │ │ └── schema_rules.md │ │ ├── composing_enforce_rules.md │ │ ├── consuming_external_rules.md │ │ ├── creating_custom_rules.md │ │ ├── enforce.md │ │ ├── enforce_rules.md │ │ └── failing_with_a_message.md │ │ ├── get_started.md │ │ ├── recipes │ │ ├── _category_.json │ │ ├── any_test.md │ │ └── typescript_enums.md │ │ ├── understanding_state.md │ │ ├── upgrade_guide.md │ │ ├── using_with_node.md │ │ ├── utilities │ │ ├── _category_.json │ │ ├── classnames.md │ │ └── promisify.md │ │ ├── writing_tests │ │ ├── _category_.json │ │ ├── advanced_test_features │ │ │ ├── _category_.json │ │ │ ├── dynamic_tests.md │ │ │ ├── grouping_tests.md │ │ │ └── test.memo.md │ │ ├── async_tests.md │ │ ├── failing_with_a_custom_message.md │ │ ├── using_the_test_function.md │ │ └── warn_only_tests.md │ │ └── writing_your_suite │ │ ├── _category_.json │ │ ├── eager.md │ │ ├── including_and_excluding │ │ ├── _category_.json │ │ ├── include.md │ │ ├── omitWhen.md │ │ ├── skipWhen.md │ │ ├── skip_and_only.md │ │ └── skip_and_only_group.md │ │ ├── optional_fields.md │ │ ├── result_object.md │ │ └── vests_suite.md ├── versioned_sidebars │ └── version-4.x-sidebars.json └── versions.json └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealush/vest/8742d6f2145afe5e0cc4cfb449166e8349330c6d/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tsconfig.json linguist-generated -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ealush 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | | Q | A | 10 | | ---------------- | ----- | 11 | | Bug fix? | ✔/✖ | 12 | | New feature? | ✔/✖ | 13 | | Breaking change? | ✔/✖ | 14 | | Deprecations? | ✔/✖ | 15 | | Documentation? | ✔/✖ | 16 | | Tests added? | ✔/✖ | 17 | | Types added? | ✔/✖ | 18 | | Related issues | | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: monthly 7 | time: '03:00' 8 | open-pull-requests-limit: 10 9 | target-branch: latest 10 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [20.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 1 20 | - name: Push To Nightly Branch 21 | run: | 22 | echo "Pushing to nightly branch" 23 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 24 | git config user.name "github-actions[bot]" 25 | git branch -D nightly || echo "No nightly branch" 26 | git push origin --delete nightly || echo "No nightly branch" 27 | git checkout -b nightly || git checkout nightly 28 | git push origin nightly 29 | -------------------------------------------------------------------------------- /.husky/_/husky.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealush/vest/8742d6f2145afe5e0cc4cfb449166e8349330c6d/.husky/_/husky.sh -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn vx precommit -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .yarn/ 4 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nmMode: hardlinks-local 2 | 3 | nodeLinker: node-modules 4 | 5 | plugins: 6 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 7 | spec: '@yarnpkg/plugin-interactive-tools' 8 | 9 | yarnPath: .yarn/releases/yarn-3.5.1.cjs 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Evyatar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/anyone/.npmignore: -------------------------------------------------------------------------------- 1 | # Autogenerated section. Do not edit manually. 2 | node_modules 3 | src 4 | !types/ 5 | !dist/ 6 | tsconfig.json 7 | !one/ 8 | !none/ 9 | !any/ 10 | !all/ 11 | 12 | # Manual Section. Edit at will. -------------------------------------------------------------------------------- /packages/anyone/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ealush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/anyone/src/__tests__/anyoneTestValues.ts: -------------------------------------------------------------------------------- 1 | export const FALSY_VALUES = [false, 0, null, undefined, '', NaN]; 2 | 3 | export const TRUTHY_VALUES = [true, 1, 'true', [], {}]; 4 | -------------------------------------------------------------------------------- /packages/anyone/src/__tests__/runAnyoneMethods.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { FALSY_VALUES, TRUTHY_VALUES } from './anyoneTestValues'; 4 | 5 | import run from 'runAnyoneMethods'; 6 | 7 | describe('lib/run', () => { 8 | describe('When value is falsy', () => { 9 | it.each([FALSY_VALUES])('Should return `false` ("%s")', value => 10 | expect(run(value)).toBe(false), 11 | ); 12 | }); 13 | 14 | describe('When value is truthy', () => { 15 | it.each([TRUTHY_VALUES])('Should return `true` ("%s")', value => 16 | expect(run(value)).toBe(true), 17 | ); 18 | }); 19 | 20 | describe('When value is a function', () => { 21 | describe('When value is falsy', () => { 22 | it.each([FALSY_VALUES])('Should return `false` ("%s")', value => 23 | expect(run(() => value)).toBe(false), 24 | ); 25 | }); 26 | 27 | describe('When value is truthy', () => { 28 | it.each([TRUTHY_VALUES])('Should return `true` ("%s")', value => 29 | expect(run(() => value)).toBe(true), 30 | ); 31 | }); 32 | }); 33 | 34 | describe('When the function throws an error', () => { 35 | it('Should return false', () => { 36 | expect( 37 | run(() => { 38 | throw new Error(); 39 | }), 40 | ).toBe(false); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/anyone/src/anyone.ts: -------------------------------------------------------------------------------- 1 | export { default as all } from 'all'; 2 | export { default as any } from 'any'; 3 | export { default as none } from 'none'; 4 | export { default as one } from 'one'; 5 | -------------------------------------------------------------------------------- /packages/anyone/src/exports/all.ts: -------------------------------------------------------------------------------- 1 | import run from 'runAnyoneMethods'; 2 | 3 | /** 4 | * Checks that at all passed arguments evaluate to a truthy value. 5 | */ 6 | export default function all(...args: unknown[]): boolean { 7 | return args.every(run); 8 | } 9 | -------------------------------------------------------------------------------- /packages/anyone/src/exports/any.ts: -------------------------------------------------------------------------------- 1 | import run from 'runAnyoneMethods'; 2 | 3 | /** 4 | * Checks that at least one passed argument evaluates to a truthy value. 5 | */ 6 | export default function any(...args: unknown[]): boolean { 7 | return args.some(run); 8 | } 9 | -------------------------------------------------------------------------------- /packages/anyone/src/exports/none.ts: -------------------------------------------------------------------------------- 1 | import { bindNot } from 'vest-utils'; 2 | 3 | import run from 'runAnyoneMethods'; 4 | 5 | /** 6 | * Checks that at none of the passed arguments evaluate to a truthy value. 7 | */ 8 | export default function none(...args: unknown[]): boolean { 9 | return args.every(bindNot(run)); 10 | } 11 | -------------------------------------------------------------------------------- /packages/anyone/src/exports/one.ts: -------------------------------------------------------------------------------- 1 | import run from 'runAnyoneMethods'; 2 | 3 | /** 4 | * Checks that at only one passed argument evaluates to a truthy value. 5 | */ 6 | export default function one(...args: unknown[]): boolean { 7 | let count = 0; 8 | 9 | for (let i = 0; i < args.length; i++) { 10 | if (run(args[i])) { 11 | count++; 12 | } 13 | 14 | if (count > 1) { 15 | return false; 16 | } 17 | } 18 | 19 | return count === 1; 20 | } 21 | -------------------------------------------------------------------------------- /packages/anyone/src/runner/runAnyoneMethods.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from 'vest-utils'; 2 | 3 | /** 4 | * Accepts a value or a function, and coerces it into a boolean value 5 | */ 6 | export default function run(arg: unknown): boolean { 7 | if (isFunction(arg)) { 8 | try { 9 | return check(arg()); 10 | } catch (err) { 11 | return false; 12 | } 13 | } 14 | 15 | return check(arg); 16 | } 17 | 18 | function check(value: unknown): boolean { 19 | // We use abstract equality intentionally here. This enables falsy valueOf support. 20 | return Array.isArray(value) ? true : value != false && Boolean(value); 21 | } 22 | -------------------------------------------------------------------------------- /packages/anyone/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "rootDir": ".", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "declarationDir": "./types", 7 | "declarationMap": true, 8 | "outDir": "./dist", 9 | "paths": { 10 | "anyone": ["./src/anyone.ts"], 11 | "runAnyoneMethods": ["./src/runner/runAnyoneMethods.ts"], 12 | "one": ["./src/exports/one.ts"], 13 | "none": ["./src/exports/none.ts"], 14 | "any": ["./src/exports/any.ts"], 15 | "all": ["./src/exports/all.ts"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/context/.npmignore: -------------------------------------------------------------------------------- 1 | # Autogenerated section. Do not edit manually. 2 | node_modules 3 | src 4 | !types/ 5 | !dist/ 6 | tsconfig.json 7 | 8 | 9 | # Manual Section. Edit at will. -------------------------------------------------------------------------------- /packages/context/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # context - Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## 2.0.0 2021-12-24 8 | 9 | - 7c43eab major(context): use named export in context (ealush) 10 | 11 | ## 1.1.16 - 2021-07-02 12 | 13 | ### Fixed and improved 14 | 15 | - 34e0414 improved conditions (undefined) 16 | - 33f4e46 release (undefined) 17 | - 6fe40c7 better bundle (undefined) 18 | - c6387ab before ts settings (undefined) 19 | - c0e9708 generate correct d.ts file (undefined) 20 | - 8e01b8e x (undefined) 21 | - afb3960 x (undefined) 22 | - e0a8463 add changelog support (undefined) 23 | - cc46c38 current (undefined) 24 | 25 | ## 1.1.15 - 2021-07-02 26 | 27 | ### Fixed and improved 28 | 29 | - 34e0414 improved conditions (undefined) 30 | - 33f4e46 release (undefined) 31 | - 6fe40c7 better bundle (undefined) 32 | - c6387ab before ts settings (undefined) 33 | - c0e9708 generate correct d.ts file (undefined) 34 | - 8e01b8e x (undefined) 35 | - afb3960 x (undefined) 36 | - e0a8463 add changelog support (undefined) 37 | - cc46c38 current (undefined) 38 | -------------------------------------------------------------------------------- /packages/context/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ealush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/context/src/__tests__/__snapshots__/cascade.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Cascading Context > context.run > Should clear context after callback run 1`] = ` 4 | { 5 | "a": 1, 6 | } 7 | `; 8 | 9 | exports[`Cascading Context > context.run > Should clear context after callback run 2`] = ` 10 | { 11 | "a": 1, 12 | "b": 2, 13 | } 14 | `; 15 | 16 | exports[`Cascading Context > createCascade > Should return all methods 1`] = ` 17 | { 18 | "bind": [Function], 19 | "run": [Function], 20 | "use": [Function], 21 | "useX": [Function], 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /packages/context/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "rootDir": ".", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "declarationDir": "./types", 7 | "declarationMap": true, 8 | "outDir": "./dist", 9 | "paths": { 10 | "context": ["./src/context.ts"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/n4s/.npmignore: -------------------------------------------------------------------------------- 1 | # Autogenerated section. Do not edit manually. 2 | node_modules 3 | src 4 | !types/ 5 | !dist/ 6 | tsconfig.json 7 | !schema/ 8 | !isURL/ 9 | !email/ 10 | !date/ 11 | !compounds/ 12 | !compose/ 13 | 14 | # Manual Section. Edit at will. -------------------------------------------------------------------------------- /packages/n4s/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ealush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/n4s/src/__tests__/enforceEager.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, afterEach } from 'vitest'; 2 | 3 | import enforceEager from 'enforceEager'; 4 | 5 | describe(`enforce eager`, () => { 6 | it('should throw when rule fails', () => { 7 | expect(() => enforceEager([]).isString()).toThrow(); 8 | expect(() => enforceEager(1).greaterThan(1)).toThrow(); 9 | expect(() => enforceEager(1).greaterThan(1).lessThan(0)).toThrow(); 10 | }); 11 | 12 | it('Should return silently when rule passes', () => { 13 | enforceEager(1).isNumber(); 14 | enforceEager(1).greaterThan(0); 15 | enforceEager(1).greaterThan(0).lessThan(10); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/n4s/src/exports/compounds.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | 3 | import { allOf } from 'allOf'; 4 | import { anyOf } from 'anyOf'; 5 | import { EnforceCustomMatcher } from 'enforceUtilityTypes'; 6 | import { Lazy } from 'genEnforceLazy'; 7 | import { noneOf } from 'noneOf'; 8 | import { oneOf } from 'oneOf'; 9 | import { RuleDetailedResult } from 'ruleReturn'; 10 | 11 | enforce.extend({ allOf, anyOf, noneOf, oneOf }); 12 | 13 | type EnforceCompoundRule = ( 14 | value: unknown, 15 | ...rules: Lazy[] 16 | ) => RuleDetailedResult; 17 | 18 | /* eslint-disable @typescript-eslint/no-namespace */ 19 | declare global { 20 | namespace n4s { 21 | interface EnforceCustomMatchers { 22 | allOf: EnforceCustomMatcher; 23 | anyOf: EnforceCustomMatcher; 24 | noneOf: EnforceCustomMatcher; 25 | oneOf: EnforceCustomMatcher; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/n4s/src/exports/date.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | import isAfter from 'validator/es/lib/isAfter'; 3 | import isBefore from 'validator/es/lib/isBefore'; 4 | import isDate from 'validator/es/lib/isDate'; 5 | import isISO8601 from 'validator/es/lib/isISO8601'; 6 | 7 | import { EnforceCustomMatcher } from 'enforceUtilityTypes'; 8 | 9 | enforce.extend({ isAfter, isBefore, isDate, isISO8601 }); 10 | 11 | /* eslint-disable @typescript-eslint/no-namespace */ 12 | declare global { 13 | namespace n4s { 14 | interface EnforceCustomMatchers { 15 | isAfter: EnforceCustomMatcher; 16 | isBefore: EnforceCustomMatcher; 17 | isDate: EnforceCustomMatcher; 18 | isISO8601: EnforceCustomMatcher; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/n4s/src/exports/email.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | import isEmail from 'validator/es/lib/isEmail'; 3 | 4 | import { EnforceCustomMatcher } from 'enforceUtilityTypes'; 5 | 6 | enforce.extend({ isEmail }); 7 | 8 | /* eslint-disable @typescript-eslint/no-namespace */ 9 | declare global { 10 | namespace n4s { 11 | interface EnforceCustomMatchers { 12 | isEmail: EnforceCustomMatcher; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/n4s/src/exports/isURL.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | import isURL from 'validator/es/lib/isURL'; 3 | 4 | import { EnforceCustomMatcher } from 'enforceUtilityTypes'; 5 | 6 | enforce.extend({ isURL }); 7 | 8 | /* eslint-disable @typescript-eslint/no-namespace */ 9 | declare global { 10 | namespace n4s { 11 | interface EnforceCustomMatchers { 12 | isURL: EnforceCustomMatcher; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/n4s/src/exports/schema.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | 3 | import { EnforceCustomMatcher } from 'enforceUtilityTypes'; 4 | import { isArrayOf } from 'isArrayOf'; 5 | import { loose } from 'loose'; 6 | import { optional } from 'optional'; 7 | import { shape } from 'shape'; 8 | 9 | export { partial } from 'partial'; 10 | 11 | enforce.extend({ isArrayOf, loose, optional, shape }); 12 | 13 | /* eslint-disable @typescript-eslint/no-namespace */ 14 | declare global { 15 | namespace n4s { 16 | interface EnforceCustomMatchers { 17 | isArrayOf: EnforceCustomMatcher; 18 | loose: EnforceCustomMatcher; 19 | shape: EnforceCustomMatcher; 20 | optional: EnforceCustomMatcher; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/n4s/src/lib/enforceUtilityTypes.ts: -------------------------------------------------------------------------------- 1 | import type { CB, DropFirst } from 'vest-utils'; 2 | 3 | export type EnforceCustomMatcher = ( 4 | ...args: DropFirst> 5 | ) => R; 6 | -------------------------------------------------------------------------------- /packages/n4s/src/lib/ruleReturn.ts: -------------------------------------------------------------------------------- 1 | import type { Stringable } from 'vest-utils'; 2 | import { defaultTo } from 'vest-utils'; 3 | 4 | export default function ruleReturn( 5 | pass: boolean, 6 | message?: string, 7 | ): RuleDetailedResult { 8 | const output: RuleDetailedResult = { pass }; 9 | 10 | if (message) { 11 | output.message = message; 12 | } 13 | 14 | return output; 15 | } 16 | 17 | export function failing(): RuleDetailedResult { 18 | return ruleReturn(false); 19 | } 20 | 21 | export function passing(): RuleDetailedResult { 22 | return ruleReturn(true); 23 | } 24 | 25 | export function defaultToFailing( 26 | callback: (...args: any[]) => RuleDetailedResult, 27 | ): RuleDetailedResult { 28 | return defaultTo(callback, failing()); 29 | } 30 | 31 | export function defaultToPassing( 32 | callback: (...args: any[]) => RuleDetailedResult, 33 | ): RuleDetailedResult { 34 | return defaultTo(callback, passing()); 35 | } 36 | 37 | export type RuleReturn = 38 | | boolean 39 | | { 40 | pass: boolean; 41 | message?: Stringable; 42 | }; 43 | 44 | export type RuleDetailedResult = { pass: boolean; message?: string }; 45 | -------------------------------------------------------------------------------- /packages/n4s/src/lib/runLazyRule.ts: -------------------------------------------------------------------------------- 1 | import type { LazyRuleRunners } from 'genEnforceLazy'; 2 | import type { RuleDetailedResult } from 'ruleReturn'; 3 | import * as ruleReturn from 'ruleReturn'; 4 | 5 | export default function runLazyRule( 6 | lazyRule: LazyRuleRunners, 7 | currentValue: any, 8 | ): RuleDetailedResult { 9 | try { 10 | return lazyRule.run(currentValue); 11 | } catch { 12 | return ruleReturn.failing(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/n4s/src/lib/transformResult.ts: -------------------------------------------------------------------------------- 1 | import { invariant, optionalFunctionValue, isBoolean } from 'vest-utils'; 2 | 3 | import ruleReturn, { RuleReturn, RuleDetailedResult } from 'ruleReturn'; 4 | import type { RuleValue, Args } from 'runtimeRules'; 5 | 6 | /** 7 | * Transform the result of a rule into a standard format 8 | */ 9 | export function transformResult( 10 | result: RuleReturn, 11 | ruleName: string, 12 | value: RuleValue, 13 | ...args: Args 14 | ): RuleDetailedResult { 15 | validateResult(result); 16 | 17 | // if result is boolean 18 | if (isBoolean(result)) { 19 | return ruleReturn(result); 20 | } 21 | return ruleReturn( 22 | result.pass, 23 | optionalFunctionValue(result.message, ruleName, value, ...args), 24 | ); 25 | } 26 | 27 | function validateResult(result: RuleReturn): void { 28 | // if result is boolean, or if result.pass is boolean 29 | invariant( 30 | isBoolean(result) || (result && isBoolean(result.pass)), 31 | 'Incorrect return value for rule: ' + JSON.stringify(result), 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/n4s/src/n4s.ts: -------------------------------------------------------------------------------- 1 | export { enforce } from 'enforce'; 2 | export { ctx } from 'enforceContext'; 3 | 4 | /* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars */ 5 | declare global { 6 | namespace n4s { 7 | interface EnforceCustomMatchers {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/compounds/__tests__/allOf.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { enforce } from 'enforce'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | import 'compounds'; 6 | 7 | describe('allOf', () => { 8 | describe('Lazy Assertions', () => { 9 | describe('When all rules are satisfied', () => { 10 | it('Should return a passing result', () => { 11 | expect( 12 | enforce 13 | .allOf(enforce.isArray(), enforce.longerThan(2)) 14 | .run([1, 2, 3]), 15 | ).toEqual(ruleReturn.passing()); 16 | }); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/compounds/__tests__/noneOf.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { enforce } from 'enforce'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | import 'compounds'; 6 | 7 | describe('noneOf', () => { 8 | describe('Lazy Assertions', () => { 9 | describe('When none of the rules are satisfied', () => { 10 | it('Should return a passing result', () => { 11 | expect( 12 | enforce.noneOf(enforce.isArray(), enforce.longerThan(2)).run('x'), 13 | ).toEqual(ruleReturn.passing()); 14 | }); 15 | }); 16 | 17 | describe('When some of the rules are satisfied', () => { 18 | it('Should return a failing result', () => { 19 | expect( 20 | enforce.noneOf(enforce.isArray(), enforce.longerThan(2)).run([2]), 21 | ).toEqual(ruleReturn.failing()); 22 | }); 23 | }); 24 | 25 | describe('When all of the rules are satisfied', () => { 26 | it('Should return a failing result', () => { 27 | expect( 28 | enforce.noneOf(enforce.isArray(), enforce.longerThan(2)).run([2, 3]), 29 | ).toEqual(ruleReturn.failing()); 30 | }); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/compounds/__tests__/oneOf.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { enforce } from 'enforce'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | 6 | import 'schema'; 7 | import 'compounds'; 8 | 9 | describe('enforce.oneOf', () => { 10 | it('Should fail when multiple enforcements are met', () => { 11 | expect( 12 | enforce.oneOf(enforce.isNumber(), enforce.greaterThan(2)).run(3), 13 | ).toEqual(ruleReturn.failing()); 14 | }); 15 | 16 | it('Should pass when only one enforcement is met', () => { 17 | expect( 18 | User.run({ 19 | name: { 20 | first: 'John', 21 | last: 'Doe', 22 | }, 23 | }), 24 | ).toEqual(ruleReturn.passing()); 25 | expect(User.run({ id: 11 })).toEqual(ruleReturn.passing()); 26 | }); 27 | 28 | it('Should fail when no enforcement is met', () => { 29 | expect(User.run({})).toEqual(ruleReturn.failing()); 30 | }); 31 | }); 32 | 33 | const Entity = enforce.loose({ 34 | id: enforce.isNumber(), 35 | }); 36 | 37 | const Person = enforce.loose({ 38 | name: enforce.shape({ 39 | first: enforce.isString().longerThan(2), 40 | last: enforce.isString().longerThan(2), 41 | }), 42 | }); 43 | const User = enforce.oneOf(Entity, Person); 44 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/compounds/allOf.ts: -------------------------------------------------------------------------------- 1 | import { mapFirst } from 'vest-utils'; 2 | 3 | import type { Lazy } from 'genEnforceLazy'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | import type { RuleDetailedResult } from 'ruleReturn'; 6 | import runLazyRule from 'runLazyRule'; 7 | 8 | export function allOf(value: unknown, ...rules: Lazy[]): RuleDetailedResult { 9 | return ruleReturn.defaultToPassing( 10 | mapFirst(rules, (rule, breakout) => { 11 | const res = runLazyRule(rule, value); 12 | breakout(!res.pass, res); 13 | }), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/compounds/anyOf.ts: -------------------------------------------------------------------------------- 1 | import { mapFirst } from 'vest-utils'; 2 | 3 | import type { Lazy } from 'genEnforceLazy'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | import type { RuleDetailedResult } from 'ruleReturn'; 6 | import runLazyRule from 'runLazyRule'; 7 | 8 | export function anyOf(value: unknown, ...rules: Lazy[]): RuleDetailedResult { 9 | return ruleReturn.defaultToFailing( 10 | mapFirst(rules, (rule, breakout) => { 11 | const res = runLazyRule(rule, value); 12 | breakout(res.pass, res); 13 | }), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/compounds/noneOf.ts: -------------------------------------------------------------------------------- 1 | import { mapFirst } from 'vest-utils'; 2 | 3 | import type { Lazy } from 'genEnforceLazy'; 4 | import type { RuleDetailedResult } from 'ruleReturn'; 5 | import * as ruleReturn from 'ruleReturn'; 6 | import runLazyRule from 'runLazyRule'; 7 | 8 | export function noneOf(value: unknown, ...rules: Lazy[]): RuleDetailedResult { 9 | return ruleReturn.defaultToPassing( 10 | mapFirst(rules, (rule, breakout) => { 11 | const res = runLazyRule(rule, value); 12 | 13 | breakout(res.pass, ruleReturn.failing()); 14 | }), 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/compounds/oneOf.ts: -------------------------------------------------------------------------------- 1 | import { greaterThan } from 'vest-utils'; 2 | 3 | import { equals } from 'equals'; 4 | import type { Lazy } from 'genEnforceLazy'; 5 | import ruleReturn, { RuleDetailedResult } from 'ruleReturn'; 6 | import runLazyRule from 'runLazyRule'; 7 | 8 | const REQUIRED_COUNT = 1; 9 | 10 | export function oneOf(value: unknown, ...rules: Lazy[]): RuleDetailedResult { 11 | let passingCount = 0; 12 | rules.some(rule => { 13 | const res = runLazyRule(rule, value); 14 | 15 | if (res.pass) { 16 | passingCount++; 17 | } 18 | 19 | if (greaterThan(passingCount, REQUIRED_COUNT)) { 20 | return false; 21 | } 22 | }); 23 | 24 | return ruleReturn(equals(passingCount, REQUIRED_COUNT)); 25 | } 26 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/__tests__/loose.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { enforce } from 'enforce'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | import 'schema'; 6 | 7 | describe('enforce.loose for loose matching', () => { 8 | describe('lazy interface', () => { 9 | it('Should return a passing return when value has non-enforced keys', () => { 10 | expect( 11 | enforce 12 | .loose({ username: enforce.isString(), age: enforce.isNumber() }) 13 | .run({ username: 'ealush', age: 31, foo: 'bar' }), 14 | ).toEqual(ruleReturn.passing()); 15 | }); 16 | }); 17 | describe('eager interface', () => { 18 | it('Should return silently return when value has non-enforced keys', () => { 19 | enforce({ username: 'ealush', age: 31, foo: 'bar' }).loose({ 20 | username: enforce.isString(), 21 | age: enforce.isNumber(), 22 | }); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/__tests__/shape.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { enforce } from 'enforce'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | import 'schema'; 6 | import 'compounds'; 7 | 8 | describe('enforce.shape exact matching', () => { 9 | describe('lazy interface', () => { 10 | it('Should return a failing return when value has non-enforced keys', () => { 11 | expect( 12 | enforce 13 | .shape({ username: enforce.isString(), age: enforce.isNumber() }) 14 | .run({ username: 'ealush', age: 31, foo: 'bar' }), 15 | ).toEqual(ruleReturn.failing()); 16 | }); 17 | }); 18 | describe('eager interface', () => { 19 | it('Should throw an error when value has non-enforced keys', () => { 20 | expect(() => { 21 | enforce({ username: 'ealush', age: 31, foo: 'bar' }).shape({ 22 | username: enforce.isString(), 23 | age: enforce.isNumber(), 24 | }); 25 | }).toThrow(); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/isArrayOf.ts: -------------------------------------------------------------------------------- 1 | import { ctx } from 'n4s'; 2 | import { mapFirst } from 'vest-utils'; 3 | 4 | import type { LazyRuleRunners } from 'genEnforceLazy'; 5 | import type { RuleDetailedResult } from 'ruleReturn'; 6 | import * as ruleReturn from 'ruleReturn'; 7 | import runLazyRule from 'runLazyRule'; 8 | 9 | export function isArrayOf( 10 | inputArray: any[], 11 | currentRule: LazyRuleRunners, 12 | ): RuleDetailedResult { 13 | return ruleReturn.defaultToPassing( 14 | mapFirst(inputArray, (currentValue, breakout, index) => { 15 | const res = ctx.run( 16 | { value: currentValue, set: true, meta: { index } }, 17 | () => runLazyRule(currentRule, currentValue), 18 | ); 19 | 20 | breakout(!res.pass, res); 21 | }), 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/loose.ts: -------------------------------------------------------------------------------- 1 | import { ctx } from 'n4s'; 2 | 3 | import type { RuleDetailedResult } from 'ruleReturn'; 4 | import * as ruleReturn from 'ruleReturn'; 5 | import runLazyRule from 'runLazyRule'; 6 | import type { ShapeObject } from 'schemaTypes'; 7 | 8 | export function loose( 9 | inputObject: Record, 10 | shapeObject: ShapeObject, 11 | ): RuleDetailedResult { 12 | for (const key in shapeObject) { 13 | const currentValue = inputObject[key]; 14 | const currentRule = shapeObject[key]; 15 | 16 | const res = ctx.run({ value: currentValue, set: true, meta: { key } }, () => 17 | runLazyRule(currentRule, currentValue), 18 | ); 19 | 20 | if (!res.pass) { 21 | return res; 22 | } 23 | } 24 | 25 | return ruleReturn.passing(); 26 | } 27 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/optional.ts: -------------------------------------------------------------------------------- 1 | import { isNullish } from 'vest-utils'; 2 | 3 | import type { Lazy } from 'genEnforceLazy'; 4 | import type { RuleDetailedResult } from 'ruleReturn'; 5 | import * as ruleReturn from 'ruleReturn'; 6 | import runLazyRule from 'runLazyRule'; 7 | 8 | export function optional(value: any, ruleChain: Lazy): RuleDetailedResult { 9 | if (isNullish(value)) { 10 | return ruleReturn.passing(); 11 | } 12 | return runLazyRule(ruleChain, value); 13 | } 14 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/partial.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | 3 | // Help needed improving the typings of this file. 4 | // Ideally, we'd be able to extend ShapeObject, but that's not possible. 5 | export function partial>(shapeObject: T): T { 6 | const output = {} as T; 7 | for (const key in shapeObject) { 8 | output[key] = enforce.optional(shapeObject[key]) as T[Extract< 9 | keyof T, 10 | string 11 | >]; 12 | } 13 | return output; 14 | } 15 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/schemaTypes.ts: -------------------------------------------------------------------------------- 1 | import { LazyRuleRunners } from 'genEnforceLazy'; 2 | 3 | export interface ShapeObject 4 | extends Record, 5 | Record {} 6 | -------------------------------------------------------------------------------- /packages/n4s/src/plugins/schema/shape.ts: -------------------------------------------------------------------------------- 1 | import { hasOwnProperty } from 'vest-utils'; 2 | 3 | import { loose } from 'loose'; 4 | import type { RuleDetailedResult } from 'ruleReturn'; 5 | import * as ruleReturn from 'ruleReturn'; 6 | import type { ShapeObject } from 'schemaTypes'; 7 | 8 | export function shape( 9 | inputObject: Record, 10 | shapeObject: ShapeObject, 11 | ): RuleDetailedResult { 12 | const baseRes = loose(inputObject, shapeObject); 13 | if (!baseRes.pass) { 14 | return baseRes; 15 | } 16 | for (const key in inputObject) { 17 | if (!hasOwnProperty(shapeObject, key)) { 18 | return ruleReturn.failing(); 19 | } 20 | } 21 | 22 | return ruleReturn.passing(); 23 | } 24 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/endsWith.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { endsWith } from 'endsWith'; 4 | 5 | describe('Tests isArray rule', () => { 6 | const word = 'meow'; 7 | const totallyDifferentWord = 'lorem'; 8 | it('Should return true for the same word', () => { 9 | expect(endsWith(word, word)).toBe(true); 10 | }); 11 | 12 | it('Should return true for a suffix', () => { 13 | expect(endsWith(word, word.substring(word.length / 2, word.length))).toBe( 14 | true, 15 | ); 16 | }); 17 | 18 | it('Should return true for empty suffix', () => { 19 | expect(endsWith(word, '')).toBe(true); 20 | }); 21 | 22 | it('Should return false for a wrong suffix', () => { 23 | expect(endsWith(word, word.substring(0, word.length - 1))).toBe(false); 24 | }); 25 | 26 | it('Should return false for a suffix which is a totally different word', () => { 27 | expect(endsWith(word, totallyDifferentWord)).toBe(false); 28 | }); 29 | 30 | it('Should return false for a suffix longer than the word', () => { 31 | expect(endsWith(word, word.repeat(2))).toBe(false); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/equals.test.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { sample } from 'lodash'; 3 | import { describe, it, expect } from 'vitest'; 4 | 5 | import { equals } from 'equals'; 6 | 7 | const VALUES = [ 8 | faker.lorem.word(), 9 | faker.number.int(), 10 | { [faker.lorem.slug()]: faker.lorem.word() }, 11 | [faker.number.int()], 12 | faker.datatype.boolean(), 13 | ]; 14 | 15 | const LOOSE_PAIRS = [ 16 | [1, '1'], 17 | [1, true], 18 | [false, 0], 19 | [undefined, null], 20 | ]; 21 | 22 | describe('Equals rule', () => { 23 | it('Should return true for same value', () => { 24 | VALUES.forEach(value => expect(equals(value, value)).toBe(true)); 25 | }); 26 | 27 | it('Should return true for same different value', () => { 28 | VALUES.forEach(value => { 29 | let sampled = value; 30 | 31 | // consistently produce a different value 32 | while (sampled === value) { 33 | // @ts-expect-error - testing different types of values 34 | sampled = sample(VALUES); 35 | } 36 | 37 | expect(equals(value, sampled)).toBe(false); 38 | }); 39 | }); 40 | 41 | it('Should treat loose equality as false', () => { 42 | LOOSE_PAIRS.forEach(pair => expect(equals(pair[0], pair[1])).toBe(false)); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isBetween.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { isBetween } from 'isBetween'; 4 | 5 | describe('Tests isBetween rule', () => { 6 | it('Should return true for 5 between 0 and 10', () => { 7 | expect(isBetween(5, 0, 10)).toBe(true); 8 | }); 9 | 10 | it('Should return true for 5 between 4 and 6', () => { 11 | expect(isBetween(5, 4, 6)).toBe(true); 12 | }); 13 | 14 | it('Should return true for 5 not between 5 and 6', () => { 15 | expect(isBetween(5, 5, 6)).toBe(true); 16 | }); 17 | 18 | it('Should return true -5 between -5 and -6', () => { 19 | expect(isBetween(-5, -6, -5)).toBe(true); 20 | }); 21 | 22 | it('Should return true for -5 between -1 and -10', () => { 23 | expect(isBetween(-5, -10, -1)).toBe(true); 24 | }); 25 | 26 | it('Should return true for 5 between 5 and 5', () => { 27 | expect(isBetween(5, 5, 5)).toBe(true); 28 | }); 29 | 30 | it('Should return false for bad type for value', () => { 31 | expect(isBetween('string', 5, 10)).toBe(false); 32 | }); 33 | 34 | it('Should return false for bad type for min', () => { 35 | expect(isBetween(5, 'string', 10)).toBe(false); 36 | }); 37 | 38 | it('Should return false for bad type for max', () => { 39 | expect(isBetween(5, 4, 'string')).toBe(false); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isBlank.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { isBlank, isNotBlank } from 'isBlank'; 4 | 5 | describe('isBlank', () => { 6 | it('Should return true for a string of white spaces', () => { 7 | expect(isBlank(' ')).toBe(true); 8 | }); 9 | 10 | it('Should return false for a string with at least a non-whitespace', () => { 11 | expect(isBlank('not blank')).toBe(false); 12 | }); 13 | 14 | it('Should return true for undefined', () => { 15 | expect(isBlank(undefined)).toBeTruthy(); 16 | }); 17 | 18 | it('Should return true for null', () => { 19 | expect(isBlank(null)).toBeTruthy(); 20 | }); 21 | }); 22 | 23 | describe('isNotBlank', () => { 24 | it('Should return false for a string of white spaces', () => { 25 | expect(isNotBlank(' ')).toBe(false); 26 | }); 27 | 28 | it('Should return true for a string with at least a non-whitespace', () => { 29 | expect(isNotBlank('not blank')).toBe(true); 30 | }); 31 | 32 | it('Should return false for undefined', () => { 33 | expect(isNotBlank(undefined)).toBeFalsy(); 34 | }); 35 | 36 | it('Should return false for null', () => { 37 | expect(isNotBlank(null)).toBeFalsy(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isBoolean.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { enforce } from 'enforce'; 4 | 5 | describe('isBoolean', () => { 6 | it('Should pass for a boolean value', () => { 7 | enforce(true).isBoolean(); 8 | enforce(false).isBoolean(); 9 | }); 10 | 11 | it('Should fail for a non boolean value', () => { 12 | expect(() => enforce('true').isBoolean()).toThrow(); 13 | }); 14 | }); 15 | 16 | describe('isNotBoolean', () => { 17 | it('Should pass for a non boolean value', () => { 18 | enforce('true').isNotBoolean(); 19 | enforce([false]).isNotBoolean(); 20 | }); 21 | 22 | it('Should fail for a boolean value', () => { 23 | expect(() => enforce(true).isNotBoolean()).toThrow(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isNaN.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import * as NaNRule from 'isNaN'; 4 | 5 | describe('Tests isNaN rule', () => { 6 | it('Should return true for `NaN` value', () => { 7 | expect(NaNRule.isNaN(NaN)).toBe(true); 8 | }); 9 | 10 | it.each([ 11 | undefined, 12 | null, 13 | false, 14 | true, 15 | Object, 16 | Array(0), 17 | '', 18 | ' ', 19 | 0, 20 | 1, 21 | '0', 22 | '1', 23 | ])('Should return false for %s value', v => { 24 | expect(NaNRule.isNaN(v)).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isNullish.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { enforce } from 'n4s'; 4 | 5 | describe('enforce.isNullish', () => { 6 | it('Should return true for `null` value', () => { 7 | expect(enforce.isNullish().test(null)).toBe(true); 8 | }); 9 | 10 | it('Should return true for `undefined` value', () => { 11 | expect(enforce.isNullish().test(undefined)).toBe(true); 12 | }); 13 | 14 | it.each([ 15 | NaN, 16 | false, 17 | true, 18 | Object, 19 | Array(0), 20 | '', 21 | ' ', 22 | 0, 23 | 1, 24 | '0', 25 | '1', 26 | Function.prototype, 27 | ])('Should return false for %s value', v => { 28 | expect(enforce.isNullish().test(v)).toBe(false); 29 | }); 30 | }); 31 | 32 | describe('enforce.isNotNullish', () => { 33 | it('Should return false for `null` value', () => { 34 | expect(enforce.isNotNullish().test(null)).toBe(false); 35 | }); 36 | 37 | it('Should return false for `undefined` value', () => { 38 | expect(enforce.isNotNullish().test(undefined)).toBe(false); 39 | }); 40 | 41 | it.each([ 42 | NaN, 43 | false, 44 | true, 45 | Object, 46 | Array(0), 47 | '', 48 | ' ', 49 | 0, 50 | 1, 51 | '0', 52 | '1', 53 | Function.prototype, 54 | ])('Should return true for %s value', v => { 55 | expect(enforce.isNotNullish().test(v)).toBe(true); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isNumber.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { isNumber } from 'isNumber'; 4 | 5 | describe('Tests isNumber rule', () => { 6 | it('Should return true for a number', () => { 7 | expect(isNumber(42)).toBe(true); 8 | }); 9 | 10 | it('Should return true for a NaN', () => { 11 | expect(isNumber(NaN)).toBe(true); 12 | }); 13 | 14 | it('Should return false a string', () => { 15 | expect(isNumber('1')).toBe(false); 16 | }); 17 | 18 | it('Should return false an array', () => { 19 | expect(isNumber([1, 2, 3])).toBe(false); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isPositive.test.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | 3 | describe('Test isPositive rule', () => { 4 | it('Shiuld fail for non-numeric values', () => { 5 | expect(() => enforce(false).isPositive()).toThrow(); 6 | expect(() => enforce([]).isPositive()).toThrow(); 7 | expect(() => enforce({}).isPositive()).toThrow(); 8 | }); 9 | 10 | it('Should fail for negative values', () => { 11 | expect(() => enforce(-1).isPositive()).toThrow(); 12 | expect(() => enforce(-1.1).isPositive()).toThrow(); 13 | expect(() => enforce('-1').isPositive()).toThrow(); 14 | expect(() => enforce('-1.10').isPositive()).toThrow(); 15 | }); 16 | 17 | it('Should pass for positive values', () => { 18 | enforce(10).isPositive(); 19 | enforce(10.1).isPositive(); 20 | enforce('10').isPositive(); 21 | enforce('10.10').isPositive(); 22 | }); 23 | 24 | it('Should fail for zero', () => { 25 | expect(() => enforce(0).isPositive()).toThrow(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isString.test.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | 3 | describe('Tests isString rule', () => { 4 | it('Should fail for non-string values', () => { 5 | expect(() => enforce(42).isString()).toThrow(); 6 | expect(() => enforce([]).isString()).toThrow(); 7 | }); 8 | 9 | it('Should pass for string values', () => { 10 | enforce('I love you').isString(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/isTruthy.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { isTruthy } from 'isTruthy'; 4 | 5 | describe('Tests isTruthy rule', () => { 6 | const values = [ 7 | [0, false, 0], 8 | [null, false, 'null'], 9 | [undefined, false, 'undefined'], 10 | [false, false, 'false'], 11 | [{}, true, '{}'], 12 | [[], true, '[]'], 13 | ['', false, '""'], 14 | [1, true, 1], 15 | ['hi', true, 'hi'], 16 | [new Date(), true, 'new Date()'], 17 | [() => true, true, '() => true'], 18 | [[1], true, '[1]'], 19 | ]; 20 | 21 | for (const set of values) { 22 | const value = set[0], 23 | expected = set[1], 24 | name = set[2]; 25 | 26 | it(`The value ${name} with type ${typeof value} Should return ${expected}`, () => { 27 | expect(isTruthy(value)).toBe(expected); 28 | }); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/rules.test.ts: -------------------------------------------------------------------------------- 1 | import rules from 'rules'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('Tests enforce rules API', () => { 5 | it('Should expose all enforce rules', () => { 6 | Object.keys(rules()).forEach(rule => { 7 | // @ts-ignore - dynamically checking all built-in rules 8 | expect(rules()[rule]).toBeInstanceOf(Function); 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/__tests__/startsWith.test.ts: -------------------------------------------------------------------------------- 1 | import { startsWith } from 'startsWith'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('Tests isArray rule', () => { 5 | const word = 'meow'; 6 | const totallyDifferentWord = 'lorem'; 7 | it('Should return true for the same word', () => { 8 | expect(startsWith(word, word)).toBe(true); 9 | }); 10 | 11 | it('Should return true for a prefix', () => { 12 | expect(startsWith(word, word.substring(0, word.length / 2))).toBe(true); 13 | }); 14 | 15 | it('Should return true for empty prefix', () => { 16 | expect(startsWith(word, '')).toBe(true); 17 | }); 18 | 19 | it('Should return false for a wrong prefix', () => { 20 | expect(startsWith(word, word.substring(1, word.length - 1))).toBe(false); 21 | }); 22 | 23 | it('Should return false for a prefix which is a totally different word', () => { 24 | expect(startsWith(word, totallyDifferentWord)).toBe(false); 25 | }); 26 | 27 | it('Should return false for a prefix longer than the word', () => { 28 | expect(startsWith(word, word.repeat(2))).toBe(false); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/endsWith.ts: -------------------------------------------------------------------------------- 1 | import { isStringValue as isString, bindNot } from 'vest-utils'; 2 | 3 | export function endsWith(value: string, arg1: string): boolean { 4 | return isString(value) && isString(arg1) && value.endsWith(arg1); 5 | } 6 | 7 | export const doesNotEndWith = bindNot(endsWith); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/equals.ts: -------------------------------------------------------------------------------- 1 | import { bindNot } from 'vest-utils'; 2 | 3 | export function equals(value: unknown, arg1: unknown): boolean { 4 | return value === arg1; 5 | } 6 | 7 | export const notEquals = bindNot(equals); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/greaterThanOrEquals.ts: -------------------------------------------------------------------------------- 1 | import { greaterThan, numberEquals } from 'vest-utils'; 2 | 3 | export function greaterThanOrEquals( 4 | value: string | number, 5 | gte: string | number, 6 | ): boolean { 7 | return numberEquals(value, gte) || greaterThan(value, gte); 8 | } 9 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/inside.ts: -------------------------------------------------------------------------------- 1 | import { isStringValue as isString, bindNot, isArray } from 'vest-utils'; 2 | 3 | export function inside(value: unknown, arg1: string | unknown[]): boolean { 4 | if (isArray(arg1)) { 5 | return arg1.indexOf(value) !== -1; 6 | } 7 | 8 | // both value and arg1 are strings 9 | if (isString(arg1) && isString(value)) { 10 | return arg1.indexOf(value) !== -1; 11 | } 12 | 13 | return false; 14 | } 15 | 16 | export const notInside = bindNot(inside); 17 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isBetween.ts: -------------------------------------------------------------------------------- 1 | import { bindNot } from 'vest-utils'; 2 | 3 | import { greaterThanOrEquals as gte } from 'greaterThanOrEquals'; 4 | import { lessThanOrEquals as lte } from 'lessThanOrEquals'; 5 | 6 | export function isBetween( 7 | value: number | string, 8 | min: number | string, 9 | max: number | string, 10 | ): boolean { 11 | return gte(value, min) && lte(value, max); 12 | } 13 | 14 | export const isNotBetween = bindNot(isBetween); 15 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isBlank.ts: -------------------------------------------------------------------------------- 1 | import { isStringValue, bindNot, isNullish, BlankValue } from 'vest-utils'; 2 | 3 | export function isBlank(value: unknown): value is BlankValue { 4 | return isNullish(value) || (isStringValue(value) && !value.trim()); 5 | } 6 | 7 | export const isNotBlank = bindNot(isBlank); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isBoolean.ts: -------------------------------------------------------------------------------- 1 | import { bindNot, isBoolean } from 'vest-utils'; 2 | 3 | export const isNotBoolean = bindNot(isBoolean); 4 | export { isBoolean }; 5 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isEven.ts: -------------------------------------------------------------------------------- 1 | import { isNumeric } from 'vest-utils'; 2 | 3 | import type { RuleValue } from 'runtimeRules'; 4 | 5 | /** 6 | * Validates that a given value is an even number 7 | */ 8 | export const isEven = (value: RuleValue): boolean => { 9 | if (isNumeric(value)) { 10 | return value % 2 === 0; 11 | } 12 | return false; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isKeyOf.ts: -------------------------------------------------------------------------------- 1 | import { bindNot } from 'vest-utils'; 2 | 3 | export function isKeyOf(key: string | symbol | number, obj: any): boolean { 4 | return key in obj; 5 | } 6 | 7 | export const isNotKeyOf = bindNot(isKeyOf); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isNaN.ts: -------------------------------------------------------------------------------- 1 | import { bindNot } from 'vest-utils'; 2 | 3 | export function isNaN(value: unknown): boolean { 4 | return Number.isNaN(value); 5 | } 6 | 7 | export const isNotNaN = bindNot(isNaN); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isNegative.ts: -------------------------------------------------------------------------------- 1 | import { lessThan } from 'lessThan'; 2 | 3 | export function isNegative(value: number | string): boolean { 4 | return lessThan(value, 0); 5 | } 6 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isNumber.ts: -------------------------------------------------------------------------------- 1 | import { bindNot } from 'vest-utils'; 2 | 3 | export function isNumber(value: unknown): value is number { 4 | return Boolean(typeof value === 'number'); 5 | } 6 | 7 | export const isNotNumber = bindNot(isNumber); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isOdd.ts: -------------------------------------------------------------------------------- 1 | import { isNumeric } from 'vest-utils'; 2 | 3 | import type { RuleValue } from 'runtimeRules'; 4 | /** 5 | * Validates that a given value is an odd number 6 | */ 7 | export const isOdd = (value: RuleValue): boolean => { 8 | if (isNumeric(value)) { 9 | return value % 2 !== 0; 10 | } 11 | 12 | return false; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isString.ts: -------------------------------------------------------------------------------- 1 | import { isStringValue as isString, bindNot } from 'vest-utils'; 2 | 3 | export const isNotString = bindNot(isString); 4 | export { isString }; 5 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isTruthy.ts: -------------------------------------------------------------------------------- 1 | import { bindNot } from 'vest-utils'; 2 | 3 | export function isTruthy(value: unknown): boolean { 4 | return !!value; 5 | } 6 | 7 | export const isFalsy = bindNot(isTruthy); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/isValueOf.ts: -------------------------------------------------------------------------------- 1 | import { bindNot, isNullish } from 'vest-utils'; 2 | 3 | export function isValueOf(value: any, objectToCheck: any): boolean { 4 | if (isNullish(objectToCheck)) { 5 | return false; 6 | } 7 | 8 | for (const key in objectToCheck) { 9 | if (objectToCheck[key] === value) { 10 | return true; 11 | } 12 | } 13 | 14 | return false; 15 | } 16 | export const isNotValueOf = bindNot(isValueOf); 17 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/lessThan.ts: -------------------------------------------------------------------------------- 1 | import { isNumeric } from 'vest-utils'; 2 | 3 | export function lessThan(value: string | number, lt: string | number): boolean { 4 | return isNumeric(value) && isNumeric(lt) && Number(value) < Number(lt); 5 | } 6 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/lessThanOrEquals.ts: -------------------------------------------------------------------------------- 1 | import { numberEquals } from 'vest-utils'; 2 | 3 | import { lessThan } from 'lessThan'; 4 | 5 | export function lessThanOrEquals( 6 | value: string | number, 7 | lte: string | number, 8 | ): boolean { 9 | return numberEquals(value, lte) || lessThan(value, lte); 10 | } 11 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/longerThanOrEquals.ts: -------------------------------------------------------------------------------- 1 | import { greaterThanOrEquals } from 'greaterThanOrEquals'; 2 | 3 | export function longerThanOrEquals( 4 | value: string | unknown[], 5 | arg1: string | number, 6 | ): boolean { 7 | return greaterThanOrEquals(value.length, arg1); 8 | } 9 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/matches.ts: -------------------------------------------------------------------------------- 1 | import { isStringValue as isString, bindNot } from 'vest-utils'; 2 | 3 | export function matches(value: string, regex: RegExp | string): boolean { 4 | if (regex instanceof RegExp) { 5 | return regex.test(value); 6 | } else if (isString(regex)) { 7 | return new RegExp(regex).test(value); 8 | } 9 | return false; 10 | } 11 | 12 | export const notMatches = bindNot(matches); 13 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/ruleCondition.ts: -------------------------------------------------------------------------------- 1 | import type { RuleReturn } from 'ruleReturn'; 2 | 3 | export function condition( 4 | value: any, 5 | callback: (value: any) => RuleReturn, 6 | ): RuleReturn { 7 | try { 8 | return callback(value); 9 | } catch { 10 | return false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/shorterThan.ts: -------------------------------------------------------------------------------- 1 | import { lessThan } from 'lessThan'; 2 | 3 | export function shorterThan( 4 | value: string | unknown[], 5 | arg1: string | number, 6 | ): boolean { 7 | return lessThan(value.length, arg1); 8 | } 9 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/shorterThanOrEquals.ts: -------------------------------------------------------------------------------- 1 | import { lessThanOrEquals } from 'lessThanOrEquals'; 2 | 3 | export function shorterThanOrEquals( 4 | value: string | unknown[], 5 | arg1: string | number, 6 | ): boolean { 7 | return lessThanOrEquals(value.length, arg1); 8 | } 9 | -------------------------------------------------------------------------------- /packages/n4s/src/rules/startsWith.ts: -------------------------------------------------------------------------------- 1 | import { isStringValue as isString, bindNot } from 'vest-utils'; 2 | 3 | export function startsWith(value: string, arg1: string): boolean { 4 | return isString(value) && isString(arg1) && value.startsWith(arg1); 5 | } 6 | 7 | export const doesNotStartWith = bindNot(startsWith); 8 | -------------------------------------------------------------------------------- /packages/n4s/src/runtime/enforceContext.ts: -------------------------------------------------------------------------------- 1 | import { createCascade } from 'context'; 2 | import { assign, Nullable } from 'vest-utils'; 3 | 4 | export const ctx = createCascade((ctxRef, parentContext): CTXType => { 5 | const base = { 6 | value: ctxRef.value, 7 | meta: ctxRef.meta || {}, 8 | }; 9 | 10 | if (!parentContext) { 11 | return assign(base, { 12 | parent: emptyParent, 13 | }); 14 | } else if (ctxRef.set) { 15 | return assign(base, { 16 | parent: (): EnforceContext => stripContext(parentContext), 17 | }); 18 | } 19 | 20 | return parentContext; 21 | }); 22 | 23 | function stripContext(ctx: CTXType): EnforceContext { 24 | return { 25 | value: ctx.value, 26 | meta: ctx.meta, 27 | parent: ctx.parent, 28 | }; 29 | } 30 | 31 | type CTXType = { 32 | meta: Record; 33 | value: any; 34 | set?: boolean; 35 | parent: () => Nullable; 36 | }; 37 | 38 | export type EnforceContext = Nullable<{ 39 | meta: Record; 40 | value: any; 41 | parent: () => EnforceContext; 42 | }>; 43 | 44 | function emptyParent(): null { 45 | return null; 46 | } 47 | -------------------------------------------------------------------------------- /packages/n4s/src/runtime/runtimeRules.ts: -------------------------------------------------------------------------------- 1 | import type { DropFirst } from 'vest-utils'; 2 | 3 | import type { RuleReturn } from 'ruleReturn'; 4 | import rules from 'rules'; 5 | 6 | export type Args = any[]; 7 | 8 | export type RuleValue = any; 9 | 10 | export type RuleBase = (value: RuleValue, ...args: Args) => RuleReturn; 11 | 12 | export type Rule = Record; 13 | 14 | type BaseRules = typeof baseRules; 15 | type KBaseRules = keyof BaseRules; 16 | 17 | const baseRules = rules(); 18 | 19 | function getRule(ruleName: string): RuleBase { 20 | return baseRules[ruleName as KBaseRules]; 21 | } 22 | 23 | export { baseRules, getRule }; 24 | 25 | type Rules> = n4s.EnforceCustomMatchers< 26 | Rules & E 27 | > & 28 | Record Rules & E> & { 29 | [P in KBaseRules]: ( 30 | ...args: DropFirst> | Args 31 | ) => Rules & E; 32 | }; 33 | 34 | /* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface */ 35 | declare global { 36 | namespace n4s { 37 | interface IRules extends Rules {} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/n4s/testUtils/TEnforceMock.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | 3 | export type TEnforceMock = typeof enforce; 4 | -------------------------------------------------------------------------------- /packages/vast/.npmignore: -------------------------------------------------------------------------------- 1 | # Autogenerated section. Do not edit manually. 2 | node_modules 3 | src 4 | !types/ 5 | !dist/ 6 | tsconfig.json 7 | 8 | 9 | # Manual Section. Edit at will. -------------------------------------------------------------------------------- /packages/vast/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # vast - Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## 2.0.0 - 2021-12-24 8 | 9 | - Use named exports 10 | 11 | ## 1.0.11 - 2021-07-02 12 | 13 | ### Fixed and improved 14 | 15 | - 34e0414 improved conditions (undefined) 16 | - 33f4e46 release (undefined) 17 | - 6fe40c7 better bundle (undefined) 18 | - c6387ab before ts settings (undefined) 19 | - c0e9708 generate correct d.ts file (undefined) 20 | - 8e01b8e x (undefined) 21 | - afb3960 x (undefined) 22 | - e0a8463 add changelog support (undefined) 23 | - cc46c38 current (undefined) 24 | - b6db1c6 transform any to unknowns (ealush) 25 | - 81aad51 fix most tests (ealush) 26 | -------------------------------------------------------------------------------- /packages/vast/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ealush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/vast/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "rootDir": ".", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "declarationDir": "./types", 7 | "declarationMap": true, 8 | "outDir": "./dist", 9 | "paths": { 10 | "vast": ["./src/vast.ts"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/vest-utils/.npmignore: -------------------------------------------------------------------------------- 1 | # Autogenerated section. Do not edit manually. 2 | node_modules 3 | src 4 | !types/ 5 | !dist/ 6 | tsconfig.json 7 | !minifyObject/ 8 | 9 | # Manual Section. Edit at will. -------------------------------------------------------------------------------- /packages/vest-utils/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ealush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/vest-utils/README.md: -------------------------------------------------------------------------------- 1 | # vest-utils 2 | 3 | Set of shared functions used by the different components of Vest validations framefork. You probably do not need to use this package directly. 4 | 5 | The modules in this package are considered internal and are likely to change in the future. 6 | -------------------------------------------------------------------------------- /packages/vest-utils/src/Predicates.ts: -------------------------------------------------------------------------------- 1 | import { isEmpty } from 'isEmpty'; 2 | import optionalFunctionValue from 'optionalFunctionValue'; 3 | import { Predicate } from 'utilityTypes'; 4 | 5 | export function all(...p: Predicate[]): (value: T) => boolean { 6 | return (value: T) => 7 | isEmpty(p) 8 | ? false 9 | : p.every(predicate => optionalFunctionValue(predicate, value)); 10 | } 11 | 12 | export function any(...p: Predicate[]): (value: T) => boolean { 13 | return (value: T) => 14 | isEmpty(p) 15 | ? false 16 | : p.some(predicate => optionalFunctionValue(predicate, value)); 17 | } 18 | -------------------------------------------------------------------------------- /packages/vest-utils/src/StringObject.ts: -------------------------------------------------------------------------------- 1 | import optionalFunctionValue from 'optionalFunctionValue'; 2 | import type { Stringable } from 'utilityTypes'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/ban-types 5 | export function StringObject(value?: Stringable): String { 6 | return new String(optionalFunctionValue(value)); 7 | } 8 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/StringObject.test.ts: -------------------------------------------------------------------------------- 1 | import { StringObject } from 'StringObject'; 2 | import { describe, test, expect } from 'vitest'; 3 | 4 | describe('StringObject', () => { 5 | test('returns an instance of String', () => { 6 | const str = StringObject('hello'); 7 | expect(str).toBeInstanceOf(String); 8 | }); 9 | 10 | describe('When the passed value is a function that returns a string', () => { 11 | it('should return a string object with the value of the function', () => { 12 | const str = StringObject(() => 'hello'); 13 | expect(str).toBeInstanceOf(String); 14 | expect(str.toString()).toBe('hello'); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/asArray.test.ts: -------------------------------------------------------------------------------- 1 | import asArray from 'asArray'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('asArray', () => { 5 | it('should return an array', () => { 6 | expect(asArray('test')).toEqual(['test']); 7 | expect(asArray(['test'])).toEqual(['test']); 8 | }); 9 | 10 | it('Should create a shallow copy of the array', () => { 11 | const arr = ['test']; 12 | expect(asArray(arr)).not.toBe(arr); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/bindNot.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect, vi } from 'vitest'; 2 | 3 | import { bindNot } from 'vest-utils'; 4 | 5 | describe('bindNot', () => { 6 | it('Should return return a function', () => { 7 | expect(typeof bindNot(vi.fn())).toBe('function'); 8 | }); 9 | 10 | test('calling returned function runs accepted function', () => { 11 | const fn = vi.fn(); 12 | 13 | expect(fn).not.toHaveBeenCalled(); 14 | const not = bindNot(fn); 15 | expect(fn).not.toHaveBeenCalled(); 16 | not(); 17 | expect(fn).toHaveBeenCalledTimes(1); 18 | }); 19 | 20 | it('Should pass arguments to accepted function', () => { 21 | const fn = vi.fn(); 22 | 23 | const not = bindNot(fn); 24 | not(1, 2, 3, 4); 25 | expect(fn).toHaveBeenCalledWith(1, 2, 3, 4); 26 | }); 27 | 28 | it('Should return the boolean negation of the original function', () => { 29 | expect(bindNot(() => true)()).toBe(false); 30 | expect(bindNot(() => false)()).toBe(true); 31 | expect(bindNot(() => 'string')()).toBe(false); 32 | expect(bindNot(() => [])()).toBe(false); 33 | expect(bindNot(() => '')()).toBe(true); 34 | expect(bindNot(() => 0)()).toBe(true); 35 | expect(bindNot(() => NaN)()).toBe(true); 36 | expect(bindNot(() => null)()).toBe(true); 37 | expect(bindNot(() => undefined)()).toBe(true); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/callEach.test.ts: -------------------------------------------------------------------------------- 1 | import callEach from 'callEach'; 2 | import { describe, it, expect, vi } from 'vitest'; 3 | 4 | describe('callEach', () => { 5 | it('should call all functions in the array', () => { 6 | const mockFn1 = vi.fn(); 7 | const mockFn2 = vi.fn(); 8 | const mockFn3 = vi.fn(); 9 | 10 | callEach([mockFn1, mockFn2, mockFn3]); 11 | 12 | expect(mockFn1).toHaveBeenCalled(); 13 | expect(mockFn2).toHaveBeenCalled(); 14 | expect(mockFn3).toHaveBeenCalled(); 15 | }); 16 | 17 | it('should not throw an error if the array is empty', () => { 18 | expect(() => callEach([])).not.toThrow(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/deferThrow.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; 2 | 3 | import { deferThrow } from 'vest-utils'; 4 | // @ts-ignore 5 | const _to = global.setTimeout; 6 | describe('deferThrow', () => { 7 | beforeEach(() => { 8 | // @ts-ignore 9 | global.setTimeout = vi.fn(); 10 | }); 11 | 12 | afterEach(() => { 13 | global.setTimeout = _to; 14 | }); 15 | it('Should start a timer', () => { 16 | deferThrow(); 17 | expect(global.setTimeout).toHaveBeenCalled(); 18 | }); 19 | 20 | it('Should throw a timed out error with the provided message', () => { 21 | deferThrow('message'); 22 | // @ts-ignore 23 | const timeoutCB = global.setTimeout.mock.calls[0][0]; 24 | expect(() => timeoutCB()).toThrow('message'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/either.test.ts: -------------------------------------------------------------------------------- 1 | import either from 'either'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('either', () => { 5 | it('returns true if one argument is truthy and the other is falsy', () => { 6 | expect(either(true, false)).toBe(true); 7 | expect(either(1, 0)).toBe(true); 8 | expect(either('hello', '')).toBe(true); 9 | }); 10 | 11 | it('returns false if both arguments are truthy or falsy', () => { 12 | expect(either(true, true)).toBe(false); 13 | expect(either(false, false)).toBe(false); 14 | expect(either(1, 2)).toBe(false); 15 | expect(either('', null)).toBe(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/isArray.test.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from 'isArrayValue'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('Tests isArray rule', () => { 5 | it('Should return true for an empty array', () => { 6 | expect(isArray([])).toBe(true); 7 | }); 8 | 9 | it('Should return true for an array with elements', () => { 10 | expect(isArray([1, 2, 3])).toBe(true); 11 | }); 12 | 13 | it('Should return false a string', () => { 14 | expect(isArray('1')).toBe(false); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/isBoolean.test.ts: -------------------------------------------------------------------------------- 1 | import isBoolean from 'isBooleanValue'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('isBoolean', () => { 5 | it('Should pass for a boolean value', () => { 6 | expect(isBoolean(true)).toBe(true); 7 | expect(isBoolean(false)).toBe(true); 8 | expect(isBoolean(Boolean(1))).toBe(true); 9 | }); 10 | 11 | it('Should fail for a non boolean value', () => { 12 | expect(isBoolean('true')).toBe(false); 13 | expect(isBoolean([false])).toBe(false); 14 | expect(isBoolean(null)).toBe(false); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/isNull.test.ts: -------------------------------------------------------------------------------- 1 | import { isNull } from 'isNull'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('Tests isNull rule', () => { 5 | it('Should return true for `null` value', () => { 6 | expect(isNull(null)).toBe(true); 7 | }); 8 | 9 | it.each([ 10 | undefined, 11 | NaN, 12 | false, 13 | true, 14 | Object, 15 | Array(0), 16 | '', 17 | ' ', 18 | 0, 19 | 1, 20 | '0', 21 | '1', 22 | Function.prototype, 23 | ])('Should return false for %s value', v => { 24 | expect(isNull(v)).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/isNumeric.test.ts: -------------------------------------------------------------------------------- 1 | import { isNumeric } from 'isNumeric'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | const NUMERICS = ['-10', '0', 0xff, '0xFF', '8e5', '3.1415', +10, '0144']; 5 | 6 | const NON_NUMERICS = [ 7 | '-0x42', 8 | '7.2acdgs', 9 | '', 10 | {}, 11 | NaN, 12 | null, 13 | true, 14 | Infinity, 15 | undefined, 16 | ]; 17 | 18 | describe('Tests isNumeric rule', () => { 19 | it('Should return true for numeric values', () => { 20 | NUMERICS.forEach(value => expect(isNumeric(value)).toBe(true)); 21 | }); 22 | 23 | it('Should return false for non numeric values', () => { 24 | // @ts-expect-error - testing bad usage 25 | NON_NUMERICS.forEach(value => expect(isNumeric(value)).toBe(false)); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/isPromise.test.ts: -------------------------------------------------------------------------------- 1 | import isPromise from 'isPromise'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('isPromise', () => { 5 | it('should return true for a Promise', () => { 6 | expect(isPromise(new Promise(() => {}))).toBe(true); 7 | }); 8 | 9 | it('should return false for a non-Promise value', () => { 10 | expect(isPromise('not a Promise')).toBe(false); 11 | }); 12 | 13 | it('should return false for null or undefined', () => { 14 | expect(isPromise(null)).toBe(false); 15 | expect(isPromise(undefined)).toBe(false); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/isString.test.ts: -------------------------------------------------------------------------------- 1 | import isStringValue from 'isStringValue'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('Tests isString rule', () => { 5 | it('Should return false for non-string values', () => { 6 | expect(isStringValue(42)).toBe(false); 7 | expect(isStringValue([])).toBe(false); 8 | }); 9 | 10 | it('Should return true for string values', () => { 11 | expect(isStringValue('I love you')).toBe(true); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/isUndefined.test.ts: -------------------------------------------------------------------------------- 1 | import { isUndefined } from 'isUndefined'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('Tests isUndefined rule', () => { 5 | it('Should return true for `undefined` value', () => { 6 | expect(isUndefined(undefined)).toBe(true); 7 | expect(isUndefined()).toBe(true); 8 | }); 9 | 10 | it.each([ 11 | null, 12 | NaN, 13 | false, 14 | true, 15 | Object, 16 | Array(0), 17 | '', 18 | ' ', 19 | 0, 20 | 1, 21 | '0', 22 | '1', 23 | () => undefined, 24 | ])('Should return false for %s value', v => { 25 | expect(isUndefined(v)).toBe(false); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/mapFirst.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { mapFirst } from 'vest-utils'; 4 | 5 | describe('mapFirst', () => { 6 | it('should return the broken out result', () => { 7 | const result = mapFirst([1, 2, 3], (value, breakout) => { 8 | breakout(value === 3, { pass: true }); 9 | }); 10 | 11 | expect(result).toEqual({ pass: true }); 12 | }); 13 | 14 | it('Should respect the breakout conditional', () => { 15 | const result = mapFirst([1, 2, 3], (_, breakout) => { 16 | breakout(false, 0); 17 | breakout(false, 0); 18 | breakout(true, 1); 19 | }); 20 | 21 | expect(result).toBe(1); 22 | }); 23 | 24 | describe('When not broken out', () => { 25 | it('Should return undefined', () => { 26 | const result = mapFirst([1, 2, 3], () => {}); 27 | 28 | expect(result).toBeUndefined(); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/nonnullish.test.ts: -------------------------------------------------------------------------------- 1 | import { nonnullish } from 'nonnullish'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('nonnullish', () => { 5 | it('should return the value if it is not null or undefined', () => { 6 | const value = 'hello'; 7 | expect(nonnullish(value)).toBe(value); 8 | }); 9 | 10 | it('should throw an error if the value is null', () => { 11 | const value = null; 12 | expect(() => nonnullish(value)).toThrow(); 13 | }); 14 | 15 | it('should throw an error if the value is undefined', () => { 16 | const value = undefined; 17 | expect(() => nonnullish(value)).toThrow(); 18 | }); 19 | 20 | it('should throw a custom error message if provided', () => { 21 | const value = null; 22 | const errorMessage = 'Value must be defined'; 23 | expect(() => nonnullish(value, errorMessage)).toThrow(errorMessage); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/optionalFunctionValue.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | 3 | import { optionalFunctionValue } from 'vest-utils'; 4 | 5 | describe('optionalFunctionValue', () => { 6 | describe('When not a function', () => { 7 | it.each([0, undefined, false, true, 1, [], {}, null, NaN])( 8 | 'Should return the same value', 9 | value => { 10 | expect(optionalFunctionValue(value)).toBe(value); 11 | }, 12 | ); 13 | }); 14 | 15 | describe('When value is a function', () => { 16 | it('Should call the function and return its return value', () => { 17 | const value = vi.fn(() => 'return value'); 18 | 19 | expect(optionalFunctionValue(value)).toBe('return value'); 20 | expect(value).toHaveBeenCalled(); 21 | }); 22 | it('Should run with arguments array', () => { 23 | const value = vi.fn((...args) => args.join('|')); 24 | const args = [1, 2, 3, 4]; 25 | expect(optionalFunctionValue(value, ...args)).toBe('1|2|3|4'); 26 | expect(value).toHaveBeenCalledWith(...args); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/vest-utils/src/__tests__/seq.test.ts: -------------------------------------------------------------------------------- 1 | import seq, { genSeq } from 'seq'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | describe('lib:seq', () => { 5 | it('Should return a new id on each run', () => { 6 | Array.from({ length: 100 }, () => seq()).reduce((existing, seq) => { 7 | expect(existing).not.toHaveProperty(seq.toString()); 8 | Object.assign(existing, { [seq]: true }); 9 | expect(existing).toHaveProperty(seq.toString()); 10 | return existing; 11 | }, {}); 12 | }); 13 | 14 | describe('genSeq', () => { 15 | it('Creates a namespaced sequence', () => { 16 | const seq = genSeq('test'); 17 | expect(seq()).toBe('test_0'); 18 | expect(seq()).toBe('test_1'); 19 | expect(seq()).toBe('test_2'); 20 | 21 | const seq2 = genSeq('test2'); 22 | expect(seq2()).toBe('test2_0'); 23 | expect(seq2()).toBe('test2_1'); 24 | expect(seq2()).toBe('test2_2'); 25 | 26 | expect(seq()).toBe('test_3'); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/vest-utils/src/asArray.ts: -------------------------------------------------------------------------------- 1 | export default function asArray(possibleArg: T | T[]): T[] { 2 | return ([] as T[]).concat(possibleArg); 3 | } 4 | -------------------------------------------------------------------------------- /packages/vest-utils/src/assign.ts: -------------------------------------------------------------------------------- 1 | export default Object.assign; 2 | -------------------------------------------------------------------------------- /packages/vest-utils/src/bindNot.ts: -------------------------------------------------------------------------------- 1 | export default function bindNot unknown>(fn: T) { 2 | return (...args: Parameters): boolean => !fn(...args); 3 | } 4 | -------------------------------------------------------------------------------- /packages/vest-utils/src/bus.ts: -------------------------------------------------------------------------------- 1 | import type { CB } from 'utilityTypes'; 2 | 3 | const EVENT_WILDCARD = '*'; 4 | 5 | export function createBus(): BusType { 6 | const listeners: Record = {}; 7 | 8 | return { 9 | emit(event: string, data?: any) { 10 | getListeners(event) 11 | .concat(getListeners(EVENT_WILDCARD)) 12 | .forEach(handler => { 13 | handler(data); 14 | }); 15 | }, 16 | 17 | on(event: string, handler: CB): OnReturn { 18 | listeners[event] = getListeners(event).concat(handler); 19 | 20 | return { 21 | off() { 22 | listeners[event] = getListeners(event).filter(h => h !== handler); 23 | }, 24 | }; 25 | }, 26 | }; 27 | 28 | function getListeners(event: string): CB[] { 29 | return listeners[event] || []; 30 | } 31 | } 32 | 33 | type OnReturn = { off: CB }; 34 | 35 | export type BusType = { 36 | on: (event: string, handler: CB) => OnReturn; 37 | emit: (event: string, data?: any) => void; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/vest-utils/src/callEach.ts: -------------------------------------------------------------------------------- 1 | import type { CB } from 'utilityTypes'; 2 | 3 | export default function callEach(arr: CB[]): void { 4 | return arr.forEach(fn => fn()); 5 | } 6 | -------------------------------------------------------------------------------- /packages/vest-utils/src/defaultTo.ts: -------------------------------------------------------------------------------- 1 | import optionalFunctionValue from 'optionalFunctionValue'; 2 | import { DynamicValue, Nullish } from 'utilityTypes'; 3 | 4 | export default function defaultTo( 5 | value: DynamicValue>, 6 | defaultValue: DynamicValue, 7 | ): T { 8 | return optionalFunctionValue(value) ?? optionalFunctionValue(defaultValue); 9 | } 10 | -------------------------------------------------------------------------------- /packages/vest-utils/src/deferThrow.ts: -------------------------------------------------------------------------------- 1 | export default function deferThrow(message?: string): void { 2 | setTimeout(() => { 3 | throw new Error(message); 4 | }, 0); 5 | } 6 | 7 | export type TDeferThrow = typeof deferThrow; 8 | -------------------------------------------------------------------------------- /packages/vest-utils/src/either.ts: -------------------------------------------------------------------------------- 1 | export default function either(a: unknown, b: unknown): boolean { 2 | return !!a !== !!b; 3 | } 4 | -------------------------------------------------------------------------------- /packages/vest-utils/src/freezeAssign.ts: -------------------------------------------------------------------------------- 1 | import assign from 'assign'; 2 | 3 | export function freezeAssign(...args: Partial[]): T { 4 | return Object.freeze(assign(...(args as [Partial]))); 5 | } 6 | -------------------------------------------------------------------------------- /packages/vest-utils/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; 2 | declare const __LIB_VERSION__: string; 3 | declare const LIBRARY_NAME: string; 4 | -------------------------------------------------------------------------------- /packages/vest-utils/src/greaterThan.ts: -------------------------------------------------------------------------------- 1 | import { isNumeric } from 'isNumeric'; 2 | 3 | export function greaterThan( 4 | value: number | string, 5 | gt: number | string, 6 | ): boolean { 7 | return isNumeric(value) && isNumeric(gt) && Number(value) > Number(gt); 8 | } 9 | -------------------------------------------------------------------------------- /packages/vest-utils/src/hasOwnProperty.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A safe hasOwnProperty access 3 | */ 4 | export default function hasOwnProperty( 5 | obj: T, 6 | key: string | number | symbol, 7 | ): key is keyof T { 8 | return Object.prototype.hasOwnProperty.call(obj, key); 9 | } 10 | -------------------------------------------------------------------------------- /packages/vest-utils/src/invariant.ts: -------------------------------------------------------------------------------- 1 | import optionalFunctionValue from 'optionalFunctionValue'; 2 | import type { Stringable } from 'utilityTypes'; 3 | 4 | export default function invariant( 5 | condition: any, 6 | // eslint-disable-next-line @typescript-eslint/ban-types 7 | message?: String | Stringable, 8 | ): asserts condition { 9 | if (condition) { 10 | return; 11 | } 12 | 13 | // If message is a string object (rather than string literal) 14 | // Throw the value directly as a string 15 | // Alternatively, throw an error with the message 16 | throw message instanceof String 17 | ? message.valueOf() 18 | : new Error(message ? optionalFunctionValue(message) : message); 19 | } 20 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isArrayValue.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | 3 | // The module is named "isArrayValue" since it 4 | // is conflicting with a nested npm dependency. 5 | // We may need to revisit this in the future. 6 | 7 | export function isArray(value: unknown): value is Array { 8 | return Boolean(Array.isArray(value)); 9 | } 10 | 11 | export const isNotArray = bindNot(isArray); 12 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isBooleanValue.ts: -------------------------------------------------------------------------------- 1 | export default function isBoolean(value: unknown): value is boolean { 2 | return !!value === value; 3 | } 4 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isEmpty.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | import hasOwnProperty from 'hasOwnProperty'; 3 | import { lengthEquals } from 'lengthEquals'; 4 | import { isObject } from 'valueIsObject'; 5 | 6 | export function isEmpty(value: unknown): boolean { 7 | if (!value) { 8 | return true; 9 | } else if (hasOwnProperty(value, 'length')) { 10 | return lengthEquals(value as string | unknown[], 0); 11 | } else if (isObject(value)) { 12 | return lengthEquals(Object.keys(value as Record), 0); 13 | } 14 | 15 | return false; 16 | } 17 | 18 | export const isNotEmpty = bindNot(isEmpty); 19 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isFunction.ts: -------------------------------------------------------------------------------- 1 | export default function isFunction( 2 | value: unknown, 3 | ): value is (...args: unknown[]) => unknown { 4 | return typeof value === 'function'; 5 | } 6 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isNull.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | 3 | export function isNull(value: unknown): value is null { 4 | return value === null; 5 | } 6 | 7 | export const isNotNull = bindNot(isNull); 8 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isNullish.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | import { isNull } from 'isNull'; 3 | import { isUndefined } from 'isUndefined'; 4 | import { Nullish } from 'utilityTypes'; 5 | 6 | export function isNullish(value: any): value is Nullish { 7 | return isNull(value) || isUndefined(value); 8 | } 9 | 10 | export const isNotNullish = bindNot(isNullish); 11 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isNumeric.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | 3 | export function isNumeric(value: string | number): boolean { 4 | const str = String(value); 5 | const num = Number(value); 6 | const result = 7 | !isNaN(parseFloat(str)) && !isNaN(Number(value)) && isFinite(num); 8 | return Boolean(result); 9 | } 10 | 11 | export const isNotNumeric = bindNot(isNumeric); 12 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isPositive.ts: -------------------------------------------------------------------------------- 1 | import { greaterThan } from 'greaterThan'; 2 | 3 | export function isPositive(value: number | string): boolean { 4 | return greaterThan(value, 0); 5 | } 6 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isPromise.ts: -------------------------------------------------------------------------------- 1 | import isFunction from 'isFunction'; 2 | 3 | export default function isPromise(value: any): value is Promise { 4 | return !!value && isFunction(value.then); 5 | } 6 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isStringValue.ts: -------------------------------------------------------------------------------- 1 | export default function isStringValue(v: unknown): v is string { 2 | return String(v) === v; 3 | } 4 | -------------------------------------------------------------------------------- /packages/vest-utils/src/isUndefined.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | 3 | export function isUndefined(value?: unknown): value is undefined { 4 | return value === undefined; 5 | } 6 | 7 | export const isNotUndefined = bindNot(isUndefined); 8 | -------------------------------------------------------------------------------- /packages/vest-utils/src/lengthEquals.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | import { numberEquals } from 'numberEquals'; 3 | 4 | export function lengthEquals( 5 | value: string | unknown[], 6 | arg1: string | number, 7 | ): boolean { 8 | return numberEquals(value.length, arg1); 9 | } 10 | 11 | export const lengthNotEquals = bindNot(lengthEquals); 12 | -------------------------------------------------------------------------------- /packages/vest-utils/src/longerThan.ts: -------------------------------------------------------------------------------- 1 | import { greaterThan } from 'greaterThan'; 2 | 3 | export function longerThan( 4 | value: string | unknown[], 5 | arg1: string | number, 6 | ): boolean { 7 | return greaterThan(value.length, arg1); 8 | } 9 | -------------------------------------------------------------------------------- /packages/vest-utils/src/mapFirst.ts: -------------------------------------------------------------------------------- 1 | export default function mapFirst( 2 | array: T[], 3 | callback: ( 4 | item: T, 5 | breakout: (conditional: boolean, value: unknown) => void, 6 | index: number, 7 | ) => unknown, 8 | ): any { 9 | let broke = false; 10 | let breakoutValue = null; 11 | for (let i = 0; i < array.length; i++) { 12 | callback(array[i], breakout, i); 13 | 14 | if (broke) { 15 | return breakoutValue; 16 | } 17 | } 18 | 19 | function breakout(conditional: boolean, value: unknown) { 20 | if (conditional) { 21 | broke = true; 22 | breakoutValue = value; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/vest-utils/src/nonnullish.ts: -------------------------------------------------------------------------------- 1 | import invariant from 'invariant'; 2 | 3 | import { isNullish } from 'isNullish'; 4 | import { Nullish } from 'utilityTypes'; 5 | 6 | export function nonnullish(value: Nullish, error?: string): T { 7 | invariant(!isNullish(value), error); 8 | 9 | return value; 10 | } 11 | -------------------------------------------------------------------------------- /packages/vest-utils/src/noop.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-empty-function 2 | export function noop() {} 3 | -------------------------------------------------------------------------------- /packages/vest-utils/src/numberEquals.ts: -------------------------------------------------------------------------------- 1 | import bindNot from 'bindNot'; 2 | import { isNumeric } from 'isNumeric'; 3 | 4 | export function numberEquals( 5 | value: string | number, 6 | eq: string | number, 7 | ): boolean { 8 | return isNumeric(value) && isNumeric(eq) && Number(value) === Number(eq); 9 | } 10 | 11 | export const numberNotEquals = bindNot(numberEquals); 12 | -------------------------------------------------------------------------------- /packages/vest-utils/src/optionalFunctionValue.ts: -------------------------------------------------------------------------------- 1 | import isFunction from 'isFunction'; 2 | import { DynamicValue } from 'utilityTypes'; 3 | 4 | export default function optionalFunctionValue( 5 | value: DynamicValue, 6 | ...args: unknown[] 7 | ): T { 8 | return isFunction(value) ? value(...args) : value; 9 | } 10 | -------------------------------------------------------------------------------- /packages/vest-utils/src/seq.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @returns a unique numeric id. 3 | */ 4 | 5 | import { CB } from 'utilityTypes'; 6 | 7 | const seq = genSeq(); 8 | 9 | export default seq; 10 | 11 | export function genSeq(namespace?: string): CB { 12 | return ( 13 | (n: number) => () => 14 | `${namespace ? namespace + '_' : ''}${n++}` 15 | )(0); 16 | } 17 | -------------------------------------------------------------------------------- /packages/vest-utils/src/text.ts: -------------------------------------------------------------------------------- 1 | import { isEmpty } from 'isEmpty'; 2 | import { isObject } from 'valueIsObject'; 3 | 4 | const regexp = /{(.*?)}/g; 5 | 6 | export function text(str: string, ...substitutions: Array): string { 7 | const first = substitutions[0]; 8 | 9 | if (isObject(first)) { 10 | return str.replace(regexp, (placeholder, key: string) => { 11 | return `${first[key] ?? placeholder}`; 12 | }); 13 | } 14 | 15 | const subs = [...substitutions]; 16 | 17 | return str.replace(regexp, placeholder => { 18 | return `${isEmpty(subs) ? placeholder : subs.shift()}`; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /packages/vest-utils/src/tinyState.ts: -------------------------------------------------------------------------------- 1 | import optionalFunctionValue from 'optionalFunctionValue'; 2 | import { DynamicValue } from 'utilityTypes'; 3 | 4 | export function createTinyState( 5 | initialValue: SetValueInput, 6 | ): TinyState { 7 | let value: S; 8 | 9 | resetValue(); 10 | 11 | return () => [value, setValue, resetValue]; 12 | 13 | function setValue(nextValue: SetValueInput) { 14 | value = optionalFunctionValue(nextValue, value); 15 | } 16 | 17 | function resetValue() { 18 | setValue(optionalFunctionValue(initialValue)); 19 | } 20 | } 21 | 22 | export type TinyState = () => [ 23 | value: S, 24 | setValue: (next: SetValueInput) => void, 25 | resetValue: () => void, 26 | ]; 27 | 28 | type SetValueInput = DynamicValue; 29 | -------------------------------------------------------------------------------- /packages/vest-utils/src/utilityTypes.ts: -------------------------------------------------------------------------------- 1 | export type DropFirst = T extends [unknown, ...infer U] 2 | ? U 3 | : never; 4 | 5 | export type Stringable = string | CB; 6 | 7 | export type CB = (...args: Args) => T; 8 | 9 | export type ValueOf = T[keyof T]; 10 | 11 | export type Nullish = Nullable | Maybe; 12 | 13 | export type Nullable = T | null; 14 | 15 | export type Maybe = T | undefined; 16 | 17 | export type OneOrMoreOf = T | T[]; 18 | 19 | export type DynamicValue = T | CB; 20 | 21 | export type BlankValue = Maybe<''>; 22 | 23 | type TArgs = any[]; 24 | 25 | export type Predicate = boolean | ((value: T) => boolean); 26 | -------------------------------------------------------------------------------- /packages/vest-utils/src/valueIsObject.ts: -------------------------------------------------------------------------------- 1 | import { isNullish } from 'isNullish'; 2 | 3 | export function isObject(v: any): v is Record { 4 | return typeof v === 'object' && !isNullish(v); 5 | } 6 | -------------------------------------------------------------------------------- /packages/vest/.npmignore: -------------------------------------------------------------------------------- 1 | # Autogenerated section. Do not edit manually. 2 | node_modules 3 | src 4 | !types/ 5 | !dist/ 6 | tsconfig.json 7 | !promisify/ 8 | !parser/ 9 | !enforce@schema/ 10 | !enforce@isURL/ 11 | !enforce@email/ 12 | !enforce@date/ 13 | !enforce@compounds/ 14 | !enforce@compose/ 15 | !debounce/ 16 | !classnames/ 17 | !SuiteSerializer/ 18 | 19 | # Manual Section. Edit at will. -------------------------------------------------------------------------------- /packages/vest/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ealush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/vest/src/core/StateMachines/CommonStateMachine.ts: -------------------------------------------------------------------------------- 1 | import { StateMachine, TStateMachine, ValueOf } from 'vest-utils'; 2 | 3 | export const CommonStates = { 4 | PENDING: 'PENDING', 5 | INITIAL: 'INITIAL', 6 | DONE: 'DONE', 7 | }; 8 | 9 | const State = { 10 | [CommonStates.PENDING]: CommonStates.PENDING, 11 | [CommonStates.INITIAL]: CommonStates.INITIAL, 12 | [CommonStates.DONE]: CommonStates.DONE, 13 | }; 14 | 15 | export type State = ValueOf; 16 | 17 | const machine: TStateMachine = { 18 | initial: State.INITIAL, 19 | states: { 20 | [State.DONE]: {}, 21 | [State.INITIAL]: { 22 | [State.PENDING]: State.PENDING, 23 | [State.DONE]: State.DONE, 24 | }, 25 | [State.PENDING]: { 26 | [State.DONE]: State.DONE, 27 | }, 28 | }, 29 | }; 30 | 31 | export const CommonStateMachine = StateMachine(machine); 32 | -------------------------------------------------------------------------------- /packages/vest/src/core/VestBus/BusEvents.ts: -------------------------------------------------------------------------------- 1 | export type Events = 2 | | 'TEST_RUN_STARTED' 3 | | 'TEST_COMPLETED' 4 | | 'ALL_RUNNING_TESTS_FINISHED' 5 | | 'REMOVE_FIELD' 6 | | 'RESET_FIELD' 7 | | 'RESET_SUITE' 8 | | 'SUITE_RUN_STARTED' 9 | | 'SUITE_CALLBACK_RUN_FINISHED' 10 | | 'DONE_TEST_OMISSION_PASS'; 11 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/IsolateEach/IsolateEach.ts: -------------------------------------------------------------------------------- 1 | import { CB } from 'vest-utils'; 2 | import { TIsolate, Isolate } from 'vestjs-runtime'; 3 | 4 | import { VestIsolateType } from 'VestIsolateType'; 5 | 6 | type TIsolateEach = TIsolate; 7 | 8 | export function IsolateEach( 9 | callback: Callback, 10 | ): TIsolateEach { 11 | return Isolate.create(VestIsolateType.Each, callback, { 12 | allowReorder: true, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/IsolateReconciler.ts: -------------------------------------------------------------------------------- 1 | import { TIsolate } from 'vestjs-runtime'; 2 | 3 | export abstract class IsolateReconciler { 4 | static match(_currentNode: TIsolate, _historyNode: TIsolate): boolean { 5 | return false; 6 | } 7 | 8 | static reconcile(currentNode: TIsolate, historyNode: TIsolate): TIsolate { 9 | return (currentNode ?? historyNode) as TIsolate; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/IsolateSuite/IsolateSuite.ts: -------------------------------------------------------------------------------- 1 | import { CB, assign } from 'vest-utils'; 2 | import { Isolate, TIsolate } from 'vestjs-runtime'; 3 | 4 | import { OptionalFieldDeclaration, OptionalFields } from 'OptionalTypes'; 5 | import { TFieldName } from 'SuiteResultTypes'; 6 | import { VestIsolateType } from 'VestIsolateType'; 7 | 8 | export type TIsolateSuite = TIsolate<{ 9 | optional: OptionalFields; 10 | }>; 11 | 12 | export function IsolateSuite( 13 | callback: Callback, 14 | ): TIsolateSuite { 15 | return Isolate.create(VestIsolateType.Suite, callback, { 16 | optional: {}, 17 | }); 18 | } 19 | 20 | export class SuiteOptionalFields { 21 | static setOptionalField( 22 | suite: TIsolateSuite, 23 | fieldName: TFieldName, 24 | setter: (current: OptionalFieldDeclaration) => OptionalFieldDeclaration, 25 | ): void { 26 | const current = suite.data.optional; 27 | const currentField = current[fieldName]; 28 | 29 | assign(current, { 30 | [fieldName]: assign({}, currentField, setter(currentField)), 31 | }); 32 | } 33 | 34 | static getOptionalField( 35 | suite: TIsolateSuite, 36 | fieldName: TFieldName, 37 | ): OptionalFieldDeclaration { 38 | return SuiteOptionalFields.getOptionalFields(suite)[fieldName] ?? {}; 39 | } 40 | 41 | static getOptionalFields(suite: TIsolateSuite): OptionalFields { 42 | return suite.data?.optional ?? {}; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/IsolateTest/cancelOverriddenPendingTest.ts: -------------------------------------------------------------------------------- 1 | import { TIsolateTest } from 'IsolateTest'; 2 | import { VestTest } from 'VestTest'; 3 | import { isSameProfileTest } from 'isSameProfileTest'; 4 | 5 | export default function cancelOverriddenPendingTest( 6 | prevRunTestObject: TIsolateTest, 7 | currentRunTestObject: TIsolateTest, 8 | ): void { 9 | if ( 10 | currentRunTestObject !== prevRunTestObject && 11 | isSameProfileTest(prevRunTestObject, currentRunTestObject) && 12 | VestTest.isPending(prevRunTestObject) 13 | ) { 14 | VestTest.cancel(prevRunTestObject); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/IsolateTest/isSameProfileTest.ts: -------------------------------------------------------------------------------- 1 | import { TIsolateTest } from 'IsolateTest'; 2 | import { VestTest } from 'VestTest'; 3 | import matchingFieldName from 'matchingFieldName'; 4 | 5 | export function isSameProfileTest( 6 | testObject1: TIsolateTest, 7 | testObject2: TIsolateTest, 8 | ): boolean { 9 | const { groupName: gn1 } = VestTest.getData(testObject1); 10 | const { groupName: gn2, fieldName: fn2 } = VestTest.getData(testObject2); 11 | return ( 12 | matchingFieldName(VestTest.getData(testObject1), fn2) && 13 | gn1 === gn2 && 14 | // Specifically using == here. The reason is that when serializing 15 | // suite result, empty key gets removed, but it can also be null. 16 | testObject1.key == testObject2.key 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/VestIsolate.ts: -------------------------------------------------------------------------------- 1 | import { TStateMachineApi } from 'vest-utils'; 2 | import { TIsolate } from 'vestjs-runtime'; 3 | 4 | import { CommonStateMachine, CommonStates } from 'CommonStateMachine'; 5 | 6 | export class VestIsolate { 7 | static stateMachine: TStateMachineApi = CommonStateMachine; 8 | 9 | static getStatus(isolate: TIsolate): string { 10 | return isolate.status ?? CommonStates.INITIAL; 11 | } 12 | 13 | static setStatus(isolate: TIsolate, status: string, payload?: any): void { 14 | isolate.status = this.stateMachine.staticTransition( 15 | VestIsolate.getStatus(isolate), 16 | status, 17 | payload, 18 | ); 19 | } 20 | 21 | static statusEquals(isolate: TIsolate, status: string): boolean { 22 | return VestIsolate.getStatus(isolate) === status; 23 | } 24 | 25 | static setPending(isolate: TIsolate): void { 26 | this.setStatus(isolate, CommonStates.PENDING); 27 | } 28 | 29 | static setDone(isolate: TIsolate): void { 30 | this.setStatus(isolate, CommonStates.DONE); 31 | } 32 | 33 | static isPending(isolate: TIsolate): boolean { 34 | return VestIsolate.statusEquals(isolate, CommonStates.PENDING); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/VestIsolateType.ts: -------------------------------------------------------------------------------- 1 | export const VestIsolateType = { 2 | Each: 'Each', 3 | Focused: 'Focused', 4 | Group: 'Group', 5 | OmitWhen: 'OmitWhen', 6 | SkipWhen: 'SkipWhen', 7 | Suite: 'Suite', 8 | Test: 'Test', 9 | }; 10 | -------------------------------------------------------------------------------- /packages/vest/src/core/isolate/VestReconciler.ts: -------------------------------------------------------------------------------- 1 | import { Nullable } from 'vest-utils'; 2 | import { TIsolate } from 'vestjs-runtime'; 3 | 4 | import { IsolateTestReconciler } from 'IsolateTestReconciler'; 5 | 6 | const reconcilers: IsolateReconciler[] = [IsolateTestReconciler]; 7 | 8 | export function registerReconciler(reconciler: IsolateReconciler) { 9 | if (reconcilers.includes(reconciler)) { 10 | return; 11 | } 12 | 13 | reconcilers.push(reconciler); 14 | } 15 | 16 | export function VestReconciler( 17 | currentNode: TIsolate, 18 | historyNode: TIsolate, 19 | ): Nullable { 20 | return ( 21 | reconcilers 22 | .find(reconciler => reconciler.match(currentNode, historyNode)) 23 | ?.reconcile(currentNode as any, historyNode as any) ?? null 24 | ); 25 | } 26 | 27 | export type IsolateReconciler = { 28 | match(currentNode: TIsolate, historyNode: TIsolate): boolean; 29 | reconcile(elecurrentNode: TIsolate, historyNode: TIsolate): TIsolate; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/vest/src/core/test/TestTypes.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from 'vest-utils'; 2 | 3 | import { TFieldName } from 'SuiteResultTypes'; 4 | 5 | export type TestFnPayload = { signal: AbortSignal }; 6 | 7 | export type TestFn = (payload: TestFnPayload) => TestResult; 8 | export type AsyncTest = Promise; 9 | export type TestResult = Maybe | void; 10 | 11 | export type WithFieldName = { 12 | fieldName: F; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/vest/src/core/test/__tests__/__snapshots__/IsolateTest.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`IsolateTest > TestObject constructor 1`] = ` 4 | { 5 | "$type": "UnitTest", 6 | "children": [], 7 | "data": { 8 | "fieldName": "unicycle", 9 | "message": "I am Root.", 10 | "severity": "error", 11 | "testFn": [MockFunction spy], 12 | "type": "Test", 13 | }, 14 | "key": null, 15 | "keys": {}, 16 | "output": null, 17 | "parent": null, 18 | "status": "UNTESTED", 19 | } 20 | `; 21 | 22 | exports[`IsolateTest > testObject.warn > Should mark the test as warning 1`] = ` 23 | { 24 | "$type": "UnitTest", 25 | "children": [], 26 | "data": { 27 | "fieldName": "unicycle", 28 | "message": "I am Root.", 29 | "severity": "warning", 30 | "testFn": [MockFunction spy], 31 | "type": "Test", 32 | }, 33 | "key": null, 34 | "keys": {}, 35 | "output": null, 36 | "parent": null, 37 | "status": "UNTESTED", 38 | } 39 | `; 40 | -------------------------------------------------------------------------------- /packages/vest/src/core/test/helpers/matchingFieldName.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from 'vest-utils'; 2 | 3 | import { TFieldName } from 'SuiteResultTypes'; 4 | import { WithFieldName } from 'TestTypes'; 5 | 6 | export function nonMatchingFieldName( 7 | WithFieldName: WithFieldName, 8 | fieldName?: Maybe, 9 | ): boolean { 10 | return !!fieldName && !matchingFieldName(WithFieldName, fieldName); 11 | } 12 | 13 | export default function matchingFieldName( 14 | WithFieldName: WithFieldName, 15 | fieldName?: Maybe, 16 | ): boolean { 17 | return !!(fieldName && WithFieldName.fieldName === fieldName); 18 | } 19 | 20 | export function matchesOrHasNoFieldName( 21 | WithFieldName: WithFieldName, 22 | fieldName?: Maybe, 23 | ): boolean { 24 | if (fieldName) { 25 | return matchingFieldName(WithFieldName, fieldName); 26 | } 27 | return true; 28 | } 29 | -------------------------------------------------------------------------------- /packages/vest/src/core/test/helpers/matchingGroupName.ts: -------------------------------------------------------------------------------- 1 | import { Maybe, bindNot } from 'vest-utils'; 2 | 3 | import { TIsolateTest } from 'IsolateTest'; 4 | import { TGroupName } from 'SuiteResultTypes'; 5 | import { VestTest } from 'VestTest'; 6 | 7 | export const nonMatchingGroupName = bindNot(matchingGroupName); 8 | 9 | export function matchingGroupName( 10 | testObject: TIsolateTest, 11 | groupName: Maybe, 12 | ): boolean { 13 | return VestTest.getData(testObject).groupName === groupName; 14 | } 15 | -------------------------------------------------------------------------------- /packages/vest/src/core/test/helpers/nonMatchingSeverityProfile.ts: -------------------------------------------------------------------------------- 1 | import { either } from 'vest-utils'; 2 | 3 | import { TIsolateTest } from 'IsolateTest'; 4 | import { Severity } from 'Severity'; 5 | import { VestTest } from 'VestTest'; 6 | 7 | /** 8 | * Checks that a given test object matches the currently specified severity level 9 | */ 10 | export function nonMatchingSeverityProfile( 11 | severity: Severity, 12 | testObject: TIsolateTest, 13 | ): boolean { 14 | return either(severity === Severity.WARNINGS, VestTest.warns(testObject)); 15 | } 16 | -------------------------------------------------------------------------------- /packages/vest/src/core/test/helpers/shouldUseErrorMessage.ts: -------------------------------------------------------------------------------- 1 | import { Maybe, isStringValue, isUndefined } from 'vest-utils'; 2 | 3 | export function shouldUseErrorAsMessage( 4 | message: Maybe, 5 | error: unknown, 6 | ): error is Maybe { 7 | // kind of cheating with this safe guard, but it does the job 8 | return isUndefined(message) && isStringValue(error); 9 | } 10 | -------------------------------------------------------------------------------- /packages/vest/src/exports/__tests__/__snapshots__/SuiteSerializer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`SuiteSerializer > Should produce a valid serialized dump 1`] = `"[{"0":"Suite","1":{},"5":[{"0":"Focused","1":{}},{"0":"2","1":{"3":"field_1","4":"field_1_message"},"status":"FAILED"},{"0":"2","1":{"3":"field_2","4":"field_2_message"}},{"0":"Group","5":[{"0":"2","1":{"3":"8","4":"field_3_message_1","6":"7"}},{"0":"2","1":{"3":"8","4":"field_3_message_2","6":"7"}},{"0":"2","1":{"3":"field_4","4":"field_4_message","6":"7"}}]},{"0":"SkipWhen","5":[{"0":"2","1":{"3":"field_5","4":"field_5_message"}}]}]},{"0":"$type","1":"data","2":"Test","3":"fieldName","4":"message","5":"children","6":"groupName","7":"group_1","8":"field_3"}]"`; 4 | 5 | exports[`suite.resume > Running the suite after resuming > Sanity - suite should run as expected > Should have correct state after resuming 1`] = ` 6 | { 7 | "field_1": [ 8 | "field_1_message", 9 | ], 10 | "field_2": [ 11 | "field_2_message", 12 | ], 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /packages/vest/src/exports/classnames.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from 'vest-utils'; 2 | 3 | import { SuiteSummary, TFieldName, TGroupName } from 'SuiteResultTypes'; 4 | import { ParsedVestObject, parse } from 'parser'; 5 | 6 | /** 7 | * Creates a function that returns class names that match the validation result 8 | */ 9 | export default function classnames( 10 | res: SuiteSummary, 11 | classes: SupportedClasses = {}, 12 | ): (fieldName: F) => string { 13 | const selectors = parse(res); 14 | 15 | return function cn(fieldName: F): string { 16 | const classesArray: string[] = []; 17 | 18 | for (const selector in classes) { 19 | const sel = selector as keyof SupportedClasses; 20 | if (isFunction(selectors[sel]) && selectors[sel](fieldName)) { 21 | classesArray.push(classes[sel] as string); 22 | } 23 | } 24 | 25 | return classesArray.join(' '); 26 | }; 27 | } 28 | 29 | type SupportedClasses = { 30 | [K in keyof ParsedVestObject]?: string; 31 | }; 32 | -------------------------------------------------------------------------------- /packages/vest/src/exports/enforce@compose.ts: -------------------------------------------------------------------------------- 1 | export { default } from 'n4s/compose'; 2 | -------------------------------------------------------------------------------- /packages/vest/src/exports/enforce@compounds.ts: -------------------------------------------------------------------------------- 1 | export * as compounds from 'n4s/compounds'; 2 | -------------------------------------------------------------------------------- /packages/vest/src/exports/enforce@date.ts: -------------------------------------------------------------------------------- 1 | export * as date from 'n4s/date'; 2 | -------------------------------------------------------------------------------- /packages/vest/src/exports/enforce@email.ts: -------------------------------------------------------------------------------- 1 | export * as email from 'n4s/email'; 2 | -------------------------------------------------------------------------------- /packages/vest/src/exports/enforce@isURL.ts: -------------------------------------------------------------------------------- 1 | export * as isURL from 'n4s/isURL'; 2 | -------------------------------------------------------------------------------- /packages/vest/src/exports/enforce@schema.ts: -------------------------------------------------------------------------------- 1 | export * as schema from 'n4s/schema'; 2 | -------------------------------------------------------------------------------- /packages/vest/src/exports/promisify.ts: -------------------------------------------------------------------------------- 1 | import { invariant, isFunction } from 'vest-utils'; 2 | 3 | import { ErrorStrings } from 'ErrorStrings'; 4 | import { 5 | SuiteResult, 6 | SuiteRunResult, 7 | TFieldName, 8 | TGroupName, 9 | } from 'SuiteResultTypes'; 10 | 11 | function promisify( 12 | validatorFn: (...args: any[]) => SuiteRunResult, 13 | ) { 14 | return (...args: any[]): Promise> => { 15 | invariant(isFunction(validatorFn), ErrorStrings.PROMISIFY_REQUIRE_FUNCTION); 16 | 17 | return new Promise(resolve => validatorFn(...args).done(resolve)); 18 | }; 19 | } 20 | 21 | export default promisify; 22 | -------------------------------------------------------------------------------- /packages/vest/src/hooks/__tests__/warn.test.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ErrorStrings } from 'ErrorStrings'; 3 | import { VestTest } from 'VestTest'; 4 | import { describe, it, expect, vi } from 'vitest'; 5 | 6 | import * as vest from 'vest'; 7 | 8 | const { create, test, warn } = vest; 9 | 10 | describe('warn hook', () => { 11 | describe('When currentTest exists', () => { 12 | it('Should set warns to true', () => { 13 | let t; 14 | create(() => { 15 | t = test(faker.lorem.word(), faker.lorem.sentence(), () => { 16 | warn(); 17 | }); 18 | })(); 19 | 20 | expect(VestTest.warns(VestTest.cast(t))).toBe(true); 21 | }); 22 | }); 23 | 24 | describe('Error handling', () => { 25 | it('Should throw error when currentTest is not present', () => { 26 | const done = vi.fn(); 27 | create(() => { 28 | expect(warn).toThrow(ErrorStrings.WARN_MUST_BE_CALLED_FROM_TEST); 29 | done(); 30 | })(); 31 | expect(done).toHaveBeenCalled(); 32 | }); 33 | 34 | it('Should throw error when no suite present', () => { 35 | expect(warn).toThrow(ErrorStrings.HOOK_CALLED_OUTSIDE); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/vest/src/hooks/focused/FocusedKeys.ts: -------------------------------------------------------------------------------- 1 | export enum FocusModes { 2 | ONLY, 3 | SKIP, 4 | } 5 | -------------------------------------------------------------------------------- /packages/vest/src/hooks/focused/useHasOnliedTests.ts: -------------------------------------------------------------------------------- 1 | import { isNotNullish } from 'vest-utils'; 2 | import { TIsolate, Walker } from 'vestjs-runtime'; 3 | 4 | import { TIsolateTest } from 'IsolateTest'; 5 | import { TFieldName } from 'SuiteResultTypes'; 6 | import { FocusSelectors } from 'focused'; 7 | 8 | /** 9 | * Checks if context has included tests 10 | */ 11 | export function useHasOnliedTests( 12 | testObject: TIsolateTest, 13 | fieldName?: TFieldName, 14 | ): boolean { 15 | return isNotNullish( 16 | Walker.findClosest(testObject, (child: TIsolate) => { 17 | if (!FocusSelectors.isIsolateFocused(child)) return false; 18 | 19 | return FocusSelectors.isOnlyFocused(child, fieldName); 20 | }), 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/vest/src/hooks/optional/Modes.ts: -------------------------------------------------------------------------------- 1 | export enum Modes { 2 | EAGER = 'EAGER', 3 | ALL = 'ALL', 4 | ONE = 'ONE', 5 | } 6 | -------------------------------------------------------------------------------- /packages/vest/src/hooks/optional/OptionalTypes.ts: -------------------------------------------------------------------------------- 1 | import { DynamicValue, OneOrMoreOf } from 'vest-utils'; 2 | 3 | import { TFieldName } from 'SuiteResultTypes'; 4 | 5 | export type OptionalFields = Record; 6 | 7 | export type OptionalsInput = 8 | | OneOrMoreOf 9 | | OptionalsObject; 10 | 11 | type OptionalsObject = Record; 12 | 13 | type ImmediateOptionalFieldDeclaration = { 14 | type: OptionalFieldTypes.CUSTOM_LOGIC; 15 | rule: TOptionalRule; 16 | applied: boolean; 17 | }; 18 | 19 | type DelayedOptionalFieldDeclaration = { 20 | type: OptionalFieldTypes.AUTO; 21 | applied: boolean; 22 | rule: null; 23 | }; 24 | 25 | type TOptionalRule = DynamicValue; 26 | 27 | export type OptionalFieldDeclaration = 28 | | ImmediateOptionalFieldDeclaration 29 | | DelayedOptionalFieldDeclaration; 30 | 31 | export enum OptionalFieldTypes { 32 | CUSTOM_LOGIC, 33 | AUTO, 34 | } 35 | -------------------------------------------------------------------------------- /packages/vest/src/hooks/warn.ts: -------------------------------------------------------------------------------- 1 | import { invariant } from 'vest-utils'; 2 | 3 | import { ErrorStrings } from 'ErrorStrings'; 4 | import { useCurrentTest } from 'SuiteContext'; 5 | import { VestTest } from 'VestTest'; 6 | 7 | const ERROR_OUTSIDE_OF_TEST = ErrorStrings.WARN_MUST_BE_CALLED_FROM_TEST; 8 | 9 | /** 10 | * Sets the severity level of a test to `warn`, allowing it to fail without marking the suite as invalid. 11 | * Use this function within the body of a test to create warn-only tests. 12 | * 13 | * @returns {void} 14 | * 15 | * @example 16 | * test('password', 'Your password strength is: WEAK', () => { 17 | * warn(); 18 | * 19 | * enforce(data.password).matches(/0-9/); 20 | * }); 21 | * 22 | * @limitations 23 | * - The `warn` function should only be used within the body of a `test` function. 24 | * - When using `warn()` in an async test, it should be called in the synchronous portion of the test, not after an `await` call or in the Promise body. 25 | * - It is recommended to call `warn()` at the top of the test function. 26 | */ 27 | // @vx-allow use-use 28 | export function warn(): void { 29 | const currentTest = useCurrentTest(ErrorStrings.HOOK_CALLED_OUTSIDE); 30 | 31 | invariant(currentTest, ERROR_OUTSIDE_OF_TEST); 32 | 33 | VestTest.warn(currentTest); 34 | } 35 | -------------------------------------------------------------------------------- /packages/vest/src/isolates/__tests__/__snapshots__/each.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`each > When callback is not a function > should throw 1`] = `[Error: Each must be called with a function]`; 4 | -------------------------------------------------------------------------------- /packages/vest/src/isolates/each.ts: -------------------------------------------------------------------------------- 1 | import { invariant, isFunction } from 'vest-utils'; 2 | 3 | import { ErrorStrings } from 'ErrorStrings'; 4 | import { IsolateEach } from 'IsolateEach'; 5 | 6 | /** 7 | * Iterates over an array of items, allowing to run tests individually per item. 8 | * 9 | * Requires setting a "key" property on each item tested. 10 | * 11 | * @example 12 | * 13 | * each(itemsArray, (item) => { 14 | * test(item.name, 'Item value must not be empty', () => { 15 | * enforce(item.value).isNotEmpty(); 16 | * }, item.id) 17 | * }) 18 | */ 19 | export function each( 20 | list: T[], 21 | callback: (arg: T, index: number) => void, 22 | ): void { 23 | invariant( 24 | isFunction(callback), 25 | ErrorStrings.EACH_CALLBACK_MUST_BE_A_FUNCTION, 26 | ); 27 | 28 | IsolateEach(() => { 29 | list.forEach((arg, index) => { 30 | callback(arg, index); 31 | }); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/vest/src/isolates/group.ts: -------------------------------------------------------------------------------- 1 | import { CB } from 'vest-utils'; 2 | import { TIsolate, Isolate } from 'vestjs-runtime'; 3 | 4 | import { SuiteContext } from 'SuiteContext'; 5 | import { TGroupName } from 'SuiteResultTypes'; 6 | import { VestIsolateType } from 'VestIsolateType'; 7 | 8 | export function group( 9 | groupName: G, 10 | callback: CB, 11 | ): TIsolate; 12 | export function group(callback: CB): TIsolate; 13 | export function group( 14 | ...args: [groupName: G, callback: CB] | [callback: CB] 15 | ): TIsolate { 16 | const [callback, groupName] = args.reverse() as [CB, G]; 17 | 18 | return Isolate.create(VestIsolateType.Group, () => { 19 | return SuiteContext.run({ ...(groupName && { groupName }) }, callback); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/vest/src/isolates/omitWhen.ts: -------------------------------------------------------------------------------- 1 | import type { CB } from 'vest-utils'; 2 | import { optionalFunctionValue } from 'vest-utils'; 3 | import { Isolate } from 'vestjs-runtime'; 4 | 5 | import { LazyDraft } from 'LazyDraft'; 6 | import { SuiteContext, useOmitted } from 'SuiteContext'; 7 | import { TFieldName, TGroupName } from 'SuiteResultTypes'; 8 | import { VestIsolateType } from 'VestIsolateType'; 9 | import { TDraftCondition } from 'getTypedMethods'; 10 | 11 | /** 12 | * Conditionally omits tests from the suite. 13 | * 14 | * @example 15 | * 16 | * omitWhen(res => res.hasErrors('username'), () => { 17 | * test('username', 'User already taken', async () => await doesUserExist(username) 18 | * }); 19 | */ 20 | // @vx-allow use-use 21 | export function omitWhen( 22 | conditional: TDraftCondition, 23 | callback: CB, 24 | ): void { 25 | Isolate.create(VestIsolateType.OmitWhen, () => { 26 | SuiteContext.run( 27 | { 28 | omitted: 29 | useWithinActiveOmitWhen() || 30 | optionalFunctionValue(conditional, LazyDraft()), 31 | }, 32 | callback, 33 | ); 34 | }); 35 | } 36 | 37 | // Checks that we're currently in an active omitWhen block 38 | export function useWithinActiveOmitWhen(): boolean { 39 | return useOmitted(); 40 | } 41 | -------------------------------------------------------------------------------- /packages/vest/src/suite/SuiteTypes.ts: -------------------------------------------------------------------------------- 1 | import { CB } from 'vest-utils'; 2 | 3 | import { TIsolateSuite } from 'IsolateSuite'; 4 | import { 5 | SuiteResult, 6 | SuiteRunResult, 7 | TFieldName, 8 | TGroupName, 9 | } from 'SuiteResultTypes'; 10 | import { Subscribe } from 'VestBus'; 11 | import { StaticSuiteRunResult } from 'createSuite'; 12 | import { TTypedMethods } from 'getTypedMethods'; 13 | import { SuiteSelectors } from 'suiteSelectors'; 14 | 15 | export type Suite< 16 | F extends TFieldName, 17 | G extends TGroupName, 18 | T extends CB = CB, 19 | > = ((...args: Parameters) => SuiteRunResult) & SuiteMethods; 20 | 21 | export type SuiteMethods< 22 | F extends TFieldName, 23 | G extends TGroupName, 24 | T extends CB, 25 | > = { 26 | dump: CB; 27 | get: CB>; 28 | resume: CB; 29 | reset: CB; 30 | remove: CB; 31 | resetField: CB; 32 | runStatic: CB, Parameters>; 33 | subscribe: Subscribe; 34 | } & TTypedMethods & 35 | SuiteSelectors; 36 | -------------------------------------------------------------------------------- /packages/vest/src/suite/runCallbacks.ts: -------------------------------------------------------------------------------- 1 | import { isArray, callEach } from 'vest-utils'; 2 | 3 | import { useDoneCallbacks, useFieldCallbacks } from 'Runtime'; 4 | import { TFieldName } from 'SuiteResultTypes'; 5 | import { SuiteWalker } from 'SuiteWalker'; 6 | 7 | /** 8 | * Runs done callback per field when async tests are finished running. 9 | */ 10 | export function useRunFieldCallbacks(fieldName?: TFieldName): void { 11 | const [fieldCallbacks] = useFieldCallbacks(); 12 | 13 | if ( 14 | fieldName && 15 | !SuiteWalker.useHasRemainingWithTestNameMatching(fieldName) && 16 | isArray(fieldCallbacks[fieldName]) 17 | ) { 18 | callEach(fieldCallbacks[fieldName]); 19 | } 20 | } 21 | 22 | /** 23 | * Runs unlabelled done callback when async tests are finished running. 24 | */ 25 | export function useRunDoneCallbacks() { 26 | const [doneCallbacks] = useDoneCallbacks(); 27 | callEach(doneCallbacks); 28 | } 29 | -------------------------------------------------------------------------------- /packages/vest/src/suite/validateParams/validateSuiteParams.ts: -------------------------------------------------------------------------------- 1 | import { CB, invariant, isFunction } from 'vest-utils'; 2 | 3 | import { ErrorStrings } from 'ErrorStrings'; 4 | 5 | export function validateSuiteCallback( 6 | suiteCallback: T, 7 | ): asserts suiteCallback is T { 8 | invariant( 9 | isFunction(suiteCallback), 10 | ErrorStrings.SUITE_MUST_BE_INITIALIZED_WITH_FUNCTION, 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/vest/src/suiteResult/Severity.ts: -------------------------------------------------------------------------------- 1 | export enum Severity { 2 | WARNINGS = 'warnings', 3 | ERRORS = 'errors', 4 | } 5 | 6 | export enum SeverityCount { 7 | ERROR_COUNT = 'errorCount', 8 | WARN_COUNT = 'warnCount', 9 | } 10 | 11 | export function countKeyBySeverity(severity: Severity): SeverityCount { 12 | return severity === Severity.ERRORS 13 | ? SeverityCount.ERROR_COUNT 14 | : SeverityCount.WARN_COUNT; 15 | } 16 | 17 | export enum TestSeverity { 18 | Error = 'error', 19 | Warning = 'warning', 20 | } 21 | -------------------------------------------------------------------------------- /packages/vest/src/suiteResult/SummaryFailure.ts: -------------------------------------------------------------------------------- 1 | import { TIsolateTest } from 'IsolateTest'; 2 | import { TFieldName, TGroupName } from 'SuiteResultTypes'; 3 | import { WithFieldName } from 'TestTypes'; 4 | import { VestTest } from 'VestTest'; 5 | 6 | export class SummaryFailure 7 | implements WithFieldName 8 | { 9 | constructor( 10 | public fieldName: F, 11 | public message: string | undefined, 12 | public groupName: G | undefined, 13 | ) {} 14 | 15 | static fromTestObject( 16 | testObject: TIsolateTest, 17 | ) { 18 | const { fieldName, message, groupName } = VestTest.getData(testObject); 19 | 20 | return new SummaryFailure(fieldName, message, groupName); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/vest/src/suiteResult/done/deferDoneCallback.ts: -------------------------------------------------------------------------------- 1 | import { assign } from 'vest-utils'; 2 | 3 | import { DoneCallback, useDoneCallbacks, useFieldCallbacks } from 'Runtime'; 4 | import { TFieldName } from 'SuiteResultTypes'; 5 | 6 | export function useDeferDoneCallback( 7 | doneCallback: DoneCallback, 8 | fieldName?: TFieldName, 9 | ): void { 10 | const [, setFieldCallbacks] = useFieldCallbacks(); 11 | const [, setDoneCallbacks] = useDoneCallbacks(); 12 | 13 | if (fieldName) { 14 | setFieldCallbacks(fieldCallbacks => 15 | assign(fieldCallbacks, { 16 | [fieldName]: (fieldCallbacks[fieldName] || []).concat(doneCallback), 17 | }), 18 | ); 19 | 20 | return; 21 | } 22 | 23 | setDoneCallbacks(doneCallbacks => doneCallbacks.concat(doneCallback)); 24 | } 25 | -------------------------------------------------------------------------------- /packages/vest/src/suiteResult/done/shouldSkipDoneRegistration.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DONE is here and not in its own module to prevent circular dependency issues. 3 | */ 4 | 5 | import { Maybe, isFunction, numberEquals } from 'vest-utils'; 6 | 7 | import { 8 | SuiteResult, 9 | SuiteRunResult, 10 | TFieldName, 11 | TGroupName, 12 | } from 'SuiteResultTypes'; 13 | 14 | export function shouldSkipDoneRegistration< 15 | F extends TFieldName, 16 | G extends TGroupName, 17 | >( 18 | callback: (res: SuiteResult) => void, 19 | 20 | fieldName: Maybe, 21 | output: SuiteRunResult, 22 | ): boolean { 23 | // If we do not have any test runs for the current field 24 | return !!( 25 | !isFunction(callback) || 26 | (fieldName && numberEquals(output.tests[fieldName]?.testCount ?? 0, 0)) 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/vest/src/suiteResult/selectors/LazyDraft.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SuiteResult, 3 | SuiteSummary, 4 | TFieldName, 5 | TGroupName, 6 | } from 'SuiteResultTypes'; 7 | import { constructSuiteResultObject, useCreateSuiteResult } from 'suiteResult'; 8 | 9 | // @vx-allow use-use 10 | export function LazyDraft< 11 | F extends TFieldName, 12 | G extends TGroupName, 13 | >(): SuiteResult { 14 | const emptySummary = constructSuiteResultObject(new SuiteSummary()); 15 | 16 | return new Proxy(emptySummary, { 17 | get: (_, prop) => { 18 | // @vx-allow use-use 19 | const result = useCreateSuiteResult(); 20 | 21 | return result[prop as keyof SuiteResult]; 22 | }, 23 | }) as SuiteResult; 24 | } 25 | -------------------------------------------------------------------------------- /packages/vest/src/suiteResult/suiteResult.ts: -------------------------------------------------------------------------------- 1 | import { assign, Maybe } from 'vest-utils'; 2 | 3 | import { useSuiteName, useSuiteResultCache } from 'Runtime'; 4 | import { 5 | SuiteResult, 6 | SuiteSummary, 7 | TFieldName, 8 | TGroupName, 9 | } from 'SuiteResultTypes'; 10 | import { suiteSelectors } from 'suiteSelectors'; 11 | import { useProduceSuiteSummary } from 'useProduceSuiteSummary'; 12 | 13 | export function useCreateSuiteResult< 14 | F extends TFieldName, 15 | G extends TGroupName, 16 | >(): SuiteResult { 17 | return useSuiteResultCache(() => { 18 | // @vx-allow use-use 19 | const summary = useProduceSuiteSummary(); 20 | 21 | // @vx-allow use-use 22 | const suiteName = useSuiteName(); 23 | 24 | return Object.freeze(constructSuiteResultObject(summary, suiteName)); 25 | }); 26 | } 27 | 28 | export function constructSuiteResultObject< 29 | F extends TFieldName, 30 | G extends TGroupName, 31 | >(summary: SuiteSummary, suiteName?: Maybe): SuiteResult { 32 | return assign(summary, suiteSelectors(summary), { 33 | suiteName, 34 | }) as SuiteResult; 35 | } 36 | -------------------------------------------------------------------------------- /packages/vest/src/testUtils/TVestMock.ts: -------------------------------------------------------------------------------- 1 | import { TFieldName, TGroupName } from 'SuiteResultTypes'; 2 | import * as vest from 'vest'; 3 | 4 | export type TVestMock = typeof vest; 5 | 6 | export type TTestSuiteCallback = (..._args: any[]) => void; 7 | export type TTestSuite = vest.Suite; 8 | -------------------------------------------------------------------------------- /packages/vest/src/testUtils/__tests__/partition.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import partition from '../partition'; 4 | 5 | describe('partition', () => { 6 | it('Should correctly partition array', () => { 7 | expect(partition([300, 200, 10, 50, 0, -500], v => v <= 100)) 8 | .toMatchInlineSnapshot(` 9 | [ 10 | [ 11 | 10, 12 | 50, 13 | 0, 14 | -500, 15 | ], 16 | [ 17 | 300, 18 | 200, 19 | ], 20 | ] 21 | `); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/vest/src/testUtils/partition.ts: -------------------------------------------------------------------------------- 1 | export default function partition( 2 | array: T[], 3 | predicate: (_value: T, _index: number, _array: T[]) => boolean, 4 | ): [T[], T[]] { 5 | return array.reduce( 6 | (partitions: [T[], T[]], value, number) => { 7 | partitions[predicate(value, number, array) ? 0 : 1].push(value); 8 | return partitions; 9 | }, 10 | [[], []], 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/vest/src/testUtils/testPromise.ts: -------------------------------------------------------------------------------- 1 | export function TestPromise(cb: (_done: () => void) => void): Promise { 2 | return new Promise(done => cb(done)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/vest/src/testUtils/vestMocks.ts: -------------------------------------------------------------------------------- 1 | import { genTestIsolate } from 'vestjs-runtime/test-utils'; 2 | 3 | import { IsolateTestBase, IsolateTestPayload, TIsolateTest } from 'IsolateTest'; 4 | import { VestIsolateType } from 'VestIsolateType'; 5 | 6 | export function mockIsolateTest( 7 | payload: Partial = {}, 8 | ): TIsolateTest { 9 | const isolate = genTestIsolate({ 10 | ...IsolateTestBase(), 11 | testFn: vi.fn(), 12 | ...payload, 13 | type: VestIsolateType.Test, 14 | }) as TIsolateTest; 15 | 16 | return isolate; 17 | } 18 | -------------------------------------------------------------------------------- /packages/vest/src/vest.ts: -------------------------------------------------------------------------------- 1 | import { enforce } from 'n4s'; 2 | import { optional } from 'optional'; 3 | 4 | import { Modes } from 'Modes'; 5 | import type { 6 | SuiteResult, 7 | SuiteRunResult, 8 | SuiteSummary, 9 | } from 'SuiteResultTypes'; 10 | import type { Suite } from 'SuiteTypes'; 11 | import { registerReconciler } from 'VestReconciler'; 12 | import { createSuite, staticSuite, StaticSuite } from 'createSuite'; 13 | import { each } from 'each'; 14 | import { skip, only } from 'focused'; 15 | import { group } from 'group'; 16 | import { include } from 'include'; 17 | import { mode } from 'mode'; 18 | import { omitWhen } from 'omitWhen'; 19 | import { skipWhen } from 'skipWhen'; 20 | import { suiteSelectors } from 'suiteSelectors'; 21 | import { test } from 'test'; 22 | import { warn } from 'warn'; 23 | 24 | export { 25 | createSuite as create, 26 | test, 27 | group, 28 | optional, 29 | enforce, 30 | skip, 31 | skipWhen, 32 | omitWhen, 33 | only, 34 | warn, 35 | include, 36 | suiteSelectors, 37 | each, 38 | mode, 39 | staticSuite, 40 | Modes, 41 | registerReconciler, 42 | }; 43 | 44 | export type { SuiteResult, SuiteRunResult, SuiteSummary, Suite, StaticSuite }; 45 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/.npmignore: -------------------------------------------------------------------------------- 1 | # Autogenerated section. Do not edit manually. 2 | node_modules 3 | src 4 | !types/ 5 | !dist/ 6 | tsconfig.json 7 | !test-utils/ 8 | !IsolateSerializer/ 9 | 10 | # Manual Section. Edit at will. -------------------------------------------------------------------------------- /packages/vestjs-runtime/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ealush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/README.md: -------------------------------------------------------------------------------- 1 | # vestjs-runtime 2 | 3 | Internal Runtime module used by Vest. This module is not intended to be used directly, but rather used by the `vest` package. 4 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/Bus.ts: -------------------------------------------------------------------------------- 1 | import { isNullish } from 'vest-utils'; 2 | 3 | import { persist, useX } from 'VestRuntime'; 4 | 5 | export function useBus() { 6 | return useX().stateRef.Bus; 7 | } 8 | 9 | /* 10 | Returns an emitter, but it also has a shortcut for emitting an event immediately 11 | by passing an event name. 12 | */ 13 | export function useEmit(event?: string, data?: any) { 14 | const emit = useBus().emit; 15 | 16 | if (!isNullish(event)) { 17 | emit(event, data); 18 | } 19 | 20 | return persist(emit); 21 | } 22 | 23 | export function usePrepareEmitter(event: string): (arg: T) => void { 24 | const emit = useEmit(); 25 | 26 | return (arg: T) => emit(event, arg); 27 | } 28 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/Isolate/IsolateInspector.ts: -------------------------------------------------------------------------------- 1 | import { Nullable, isNotNullish, isNullish } from 'vest-utils'; 2 | 3 | import { TIsolate } from 'Isolate'; 4 | 5 | export class IsolateInspector { 6 | static at(isolate: Nullable, at: number): Nullable { 7 | if (isNullish(isolate)) { 8 | return null; 9 | } 10 | return isolate.children?.[at] ?? null; 11 | } 12 | 13 | static cursor(isolate: Nullable): number { 14 | if (isNullish(isolate)) { 15 | return 0; 16 | } 17 | return isolate.children?.length ?? 0; 18 | } 19 | 20 | static canReorder(isolate: Nullable): boolean { 21 | if (isNullish(isolate)) { 22 | return false; 23 | } 24 | 25 | return IsolateInspector.allowsReorder(isolate.parent); 26 | } 27 | 28 | static allowsReorder>( 29 | isolate: Nullable, 30 | ): boolean { 31 | return isolate?.allowReorder === true; 32 | } 33 | 34 | static usesKey(isolate: Nullable): boolean { 35 | if (isNullish(isolate)) { 36 | return false; 37 | } 38 | return isNotNullish(isolate.key); 39 | } 40 | 41 | static getChildByKey( 42 | isolate: Nullable, 43 | key: string, 44 | ): Nullable { 45 | if (isNullish(isolate)) { 46 | return null; 47 | } 48 | return isolate.keys?.[key] ?? null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/Isolate/IsolateKeys.ts: -------------------------------------------------------------------------------- 1 | export enum IsolateKeys { 2 | Type = '$type', 3 | Keys = 'keys', 4 | Key = 'key', 5 | Parent = 'parent', 6 | Data = 'data', 7 | AllowReorder = 'allowReorder', 8 | Status = 'status', 9 | AbortController = 'abortController', 10 | Children = 'children', 11 | } 12 | 13 | export const ExcludedFromDump = new Set([ 14 | IsolateKeys.AbortController, 15 | IsolateKeys.Parent, 16 | IsolateKeys.Keys, 17 | ]); 18 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/Isolate/IsolateSelectors.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from 'vest-utils'; 2 | 3 | import { TIsolate } from 'Isolate'; 4 | import { IsolateKeys } from 'IsolateKeys'; 5 | 6 | export function isIsolateType( 7 | node: Maybe, 8 | type: string, 9 | ): node is I { 10 | return node?.[IsolateKeys.Type] === type; 11 | } 12 | 13 | export function isSameIsolateType( 14 | a: A, 15 | b: B, 16 | ): boolean { 17 | return isIsolateType(a, b[IsolateKeys.Type]); 18 | } 19 | 20 | export function isSameIsolateIdentity( 21 | a: A, 22 | b: B, 23 | ): boolean { 24 | return Object.is(a, b) || (isSameIsolateType(a, b) && a.key === b.key); 25 | } 26 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/RuntimeEvents.ts: -------------------------------------------------------------------------------- 1 | export const RuntimeEvents = { 2 | ASYNC_ISOLATE_DONE: 'ASYNC_ISOLATE_DONE', 3 | ISOLATE_DONE: 'ISOLATE_DONE', 4 | ISOLATE_ENTER: 'ISOLATE_ENTER', 5 | ISOLATE_PENDING: 'ISOLATE_PENDING', 6 | }; 7 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/errors/ErrorStrings.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorStrings { 2 | NO_ACTIVE_ISOLATE = 'Not within an active isolate', 3 | UNABLE_TO_PICK_NEXT_ISOLATE = 'Unable to pick next isolate. This is a bug, please report it to the Vest maintainers.', 4 | ENCOUNTERED_THE_SAME_KEY_TWICE = `Encountered the same key "{key}" twice. This may lead to inconsistent or overriding of results.`, 5 | INVALID_ISOLATE_CANNOT_PARSE = `Invalid isolate was passed to IsolateSerializer. Cannot proceed.`, 6 | } 7 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/exports/__tests__/__snapshots__/IsolateSerializer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`IsolateSerializer > Custom Data Serialization > Should serialize data with custom keys 1`] = `"[{"0":"URoot","1":{"2":"3"},"children":[{"0":"UChild_1","1":{"2":"3"}},{"0":"UChild_2"},{"0":"UChild_3"}]},{"0":"$type","1":"data","2":"some_data","3":true}]"`; 4 | 5 | exports[`IsolateSerializer > serialize > Should produce serialized dump 1`] = `"[{"0":"URoot","1":{"2":"3"},"children":[{"0":"UChild_1","1":{"2":"3"}},{"0":"UChild_2"},{"0":"UChild_3"}]},{"0":"$type","1":"data","2":"some_data","3":true}]"`; 6 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/exports/test-utils.ts: -------------------------------------------------------------------------------- 1 | import { TIsolate } from 'Isolate'; 2 | import { IsolateKeys } from 'IsolateKeys'; 3 | 4 | export function genTestIsolate(payload: Record = {}): TIsolate { 5 | const { status, ...data } = payload; 6 | return { 7 | children: [], 8 | data, 9 | key: null, 10 | keys: {}, 11 | output: null, 12 | parent: null, 13 | [IsolateKeys.Type]: 'UnitTest', 14 | ...(status && { status }), 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/src/vestjs-runtime.ts: -------------------------------------------------------------------------------- 1 | export { RuntimeEvents } from 'RuntimeEvents'; 2 | export { IsolateKey, TIsolate, Isolate } from 'Isolate'; 3 | export { Reconciler, IRecociler } from 'Reconciler'; 4 | export * as Walker from 'IsolateWalker'; 5 | export { RuntimeApi as VestRuntime } from 'VestRuntime'; 6 | export { IsolateInspector } from 'IsolateInspector'; 7 | export { IsolateMutator } from 'IsolateMutator'; 8 | export * as Bus from 'Bus'; 9 | export * as IsolateSelectors from 'IsolateSelectors'; 10 | export { IsolateSerializer } from 'IsolateSerializer'; 11 | -------------------------------------------------------------------------------- /packages/vestjs-runtime/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "rootDir": ".", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "declarationDir": "./types", 7 | "declarationMap": true, 8 | "outDir": "./dist", 9 | "paths": { 10 | "vestjs-runtime": ["./src/vestjs-runtime.ts"], 11 | "VestRuntime": ["./src/VestRuntime.ts"], 12 | "RuntimeEvents": ["./src/RuntimeEvents.ts"], 13 | "Reconciler": ["./src/Reconciler.ts"], 14 | "IsolateWalker": ["./src/IsolateWalker.ts"], 15 | "Bus": ["./src/Bus.ts"], 16 | "test-utils": ["./src/exports/test-utils.ts"], 17 | "IsolateSerializer": ["./src/exports/IsolateSerializer.ts"], 18 | "ErrorStrings": ["./src/errors/ErrorStrings.ts"], 19 | "IsolateSelectors": ["./src/Isolate/IsolateSelectors.ts"], 20 | "IsolateMutator": ["./src/Isolate/IsolateMutator.ts"], 21 | "IsolateKeys": ["./src/Isolate/IsolateKeys.ts"], 22 | "IsolateInspector": ["./src/Isolate/IsolateInspector.ts"], 23 | "Isolate": ["./src/Isolate/Isolate.ts"] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "importHelpers": true, 9 | "lib": ["esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noImplicitAny": true, 15 | "noImplicitReturns": false, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "skipLibCheck": true, 20 | "sourceMap": true, 21 | "strict": true, 22 | "target": "ES2015" 23 | }, 24 | "files": ["./vx/config/vitest/vitest.d.ts"], 25 | "include": ["./packages/*/src/**/*.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import tsconfigPaths from 'vite-tsconfig-paths'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | globals: true, 7 | include: ['packages/**/__tests__/*.test.ts'], 8 | setupFiles: ['vx/config/vitest/customMatchers.ts/customMatchers.ts'], 9 | }, 10 | root: __dirname, 11 | plugins: [ 12 | tsconfigPaths({ 13 | loose: true, 14 | projects: [ 15 | 'packages/vest-utils', 16 | 'packages/context', 17 | 'packages/vestjs-runtime', 18 | 'packages/vast', 19 | 'packages/n4s', 20 | 'packages/vest', 21 | 'packages/anyone', 22 | ], 23 | }), 24 | ], 25 | }); 26 | -------------------------------------------------------------------------------- /vitest.workspace.js: -------------------------------------------------------------------------------- 1 | export default ['packages/*']; 2 | -------------------------------------------------------------------------------- /vx/commands/build/build.js: -------------------------------------------------------------------------------- 1 | const buildPackage = require('vx/scripts/build/buildPackage'); 2 | const runOnActivePackages = require('vx/util/runOnActivePackages'); 3 | 4 | function build(options = {}) { 5 | runOnActivePackages(buildPackage, options); 6 | } 7 | 8 | module.exports = build; 9 | -------------------------------------------------------------------------------- /vx/commands/dev/dev.js: -------------------------------------------------------------------------------- 1 | const exec = require('vx/exec'); 2 | const vxPath = require('vx/vxPath'); 3 | 4 | module.exports = () => { 5 | exec( 6 | `${vxPath.vxRoot()}/node_modules/.bin/onchange -d 5000 -i -k ${vxPath.packageSrc( 7 | '*', 8 | '**/*.ts', 9 | )} ${vxPath.packageSrc('*', '**/*.ts')} -- vx prepare`, 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /vx/commands/init/init.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const exec = require('vx/exec'); 4 | 5 | module.exports = function init({ cliOptions }) { 6 | exec(['node', path.resolve(__dirname, './prompt'), cliOptions]); 7 | }; 8 | -------------------------------------------------------------------------------- /vx/commands/init/prompt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /vx/commands/init/template/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) {{YEAR}} {{PACKAGE_AUTHOR}} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vx/commands/init/template/README.md: -------------------------------------------------------------------------------- 1 | # {{PACKAGE_NAME}} 2 | 3 | {{PACKAGE_DESCRIPTION}} 4 | -------------------------------------------------------------------------------- /vx/commands/init/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{PACKAGE_NAME}}", 3 | "version": "{{PACKAGE_VERSION}}", 4 | "description": "{{PACKAGE_DESCRIPTION}}", 5 | "license": "{{PACKAGE_LICENSE}}", 6 | "author": "{{PACKAGE_AUTHOR}}", 7 | "private": true 8 | } 9 | -------------------------------------------------------------------------------- /vx/commands/init/template/tsconfig.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "rootDir": ".", 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "declarationDir": "./types", 7 | "declarationMap": true, 8 | "outDir": "./dist", 9 | "paths": {} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vx/commands/npmignore/npmignore.js: -------------------------------------------------------------------------------- 1 | const genNpmIgnore = require('vx/scripts/genNpmIgnore'); 2 | 3 | module.exports = genNpmIgnore; 4 | -------------------------------------------------------------------------------- /vx/commands/precommit/precommit.js: -------------------------------------------------------------------------------- 1 | const exec = require('vx/exec'); 2 | 3 | module.exports = function precommit() { 4 | exec('npx pretty-quick --staged'); 5 | }; 6 | -------------------------------------------------------------------------------- /vx/commands/prepare/prepare.js: -------------------------------------------------------------------------------- 1 | const genVitestConfig = require('../../scripts/genVitestConfig'); 2 | 3 | const genNpmIgnore = require('vx/commands/npmignore/npmignore'); 4 | const genTsConfig = require('vx/commands/tsconfig/tsconfig'); 5 | const logger = require('vx/logger'); 6 | 7 | module.exports = () => { 8 | logger.info('Preparing packages...'); 9 | genNpmIgnore(); 10 | genTsConfig(); 11 | genVitestConfig(); 12 | }; 13 | -------------------------------------------------------------------------------- /vx/commands/test/test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const exec = require('vx/exec'); 4 | const { usePackage } = require('vx/vxContext'); 5 | const vxPath = require('vx/vxPath'); 6 | 7 | const configOpt = `--config ${path.resolve(vxPath.VITEST_CONFIG_FILE_PATH)}`; 8 | 9 | function test({ cliOptions }) { 10 | const pkgName = usePackage(); 11 | 12 | exec([ 13 | 'yarn vitest', 14 | pkgName && `--project ${vxPath.package(pkgName)}`, 15 | configOpt, 16 | cliOptions, 17 | ]); 18 | } 19 | 20 | module.exports = test; 21 | -------------------------------------------------------------------------------- /vx/commands/tsconfig/tsconfig.js: -------------------------------------------------------------------------------- 1 | const genTsConfig = require('vx/scripts/genTsConfig'); 2 | 3 | module.exports = genTsConfig; 4 | -------------------------------------------------------------------------------- /vx/config/rollup/format.js: -------------------------------------------------------------------------------- 1 | const yargs = require('yargs/yargs'); 2 | 3 | const opts = require('vx/opts'); 4 | 5 | const format = [].concat( 6 | yargs(process.argv).argv.format ?? [ 7 | opts.format.CJS, 8 | opts.format.ES, 9 | opts.format.UMD, 10 | ], 11 | ); 12 | 13 | const disallowExternals = format === opts.format.UMD; 14 | 15 | module.exports = { 16 | format, 17 | disallowExternals, 18 | }; 19 | -------------------------------------------------------------------------------- /vx/config/rollup/plugins/addCJSPackageJson.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const fse = require('fs-extra'); 4 | 5 | const opts = require('vx/opts'); 6 | 7 | module.exports = addEsPackageJson; 8 | 9 | function addEsPackageJson() { 10 | return { 11 | name: 'add-cjs-package-json', 12 | writeBundle: ({ format, file }) => { 13 | if (format !== opts.format.CJS) { 14 | return; 15 | } 16 | 17 | const packageJsonPath = path.join( 18 | path.dirname(file), 19 | opts.fileNames.PACKAGE_JSON, 20 | ); 21 | 22 | if (fse.existsSync(packageJsonPath)) { 23 | return; 24 | } 25 | 26 | fse.writeJSONSync(packageJsonPath, { 27 | type: 'commonjs', 28 | }); 29 | }, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /vx/config/rollup/plugins/addModulePackageJson.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const fse = require('fs-extra'); 4 | 5 | const opts = require('vx/opts'); 6 | 7 | module.exports = addEsPackageJson; 8 | 9 | function addEsPackageJson() { 10 | return { 11 | name: 'add-module-package-json', 12 | writeBundle: ({ format, file }) => { 13 | if (format !== opts.format.ES) { 14 | return; 15 | } 16 | 17 | const packageJsonPath = path.join( 18 | path.dirname(file), 19 | opts.fileNames.PACKAGE_JSON, 20 | ); 21 | 22 | if (fse.existsSync(packageJsonPath)) { 23 | return; 24 | } 25 | 26 | fse.writeJSONSync(packageJsonPath, { 27 | type: 'module', 28 | }); 29 | }, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /vx/config/vitest/vitest.d.ts: -------------------------------------------------------------------------------- 1 | import 'vitest'; 2 | 3 | interface CustomMatchers { 4 | isDeepCopyOf(clone: any): R; 5 | } 6 | 7 | declare module 'vitest' { 8 | interface Assertion extends CustomMatchers {} 9 | interface AsymmetricMatchersContaining extends CustomMatchers {} 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /vx/eslint-plugin-vest-internal/lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'use-use': require('./rules/use-use'), 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /vx/eslint-plugin-vest-internal/lib/rules/common/matchers.js: -------------------------------------------------------------------------------- 1 | const USE_MATCHER = /use.*/; 2 | const FUNC_DEC = 'FunctionDeclaration'; 3 | const VAR_DEC = 'VariableDeclarator'; 4 | const ARROW_EXP = 'ArrowFunctionExpression'; 5 | 6 | module.exports = { 7 | ARROW_EXP, 8 | FUNC_DEC, 9 | USE_MATCHER, 10 | VAR_DEC, 11 | }; 12 | -------------------------------------------------------------------------------- /vx/eslint-plugin-vest-internal/lib/rules/common/selectors.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | findAncestor, 3 | isType, 4 | getLoc, 5 | }; 6 | 7 | function findAncestor(context, type) { 8 | return context.getAncestors().find(isType(type)); 9 | } 10 | 11 | function isType(type) { 12 | return node => node.type === type; 13 | } 14 | 15 | function getLoc(node) { 16 | const { start, end } = node.loc; 17 | 18 | return { 19 | loc: { 20 | start: { 21 | line: start.line, 22 | column: start.column, 23 | }, 24 | end: { 25 | line: end.line, 26 | column: end.column, 27 | }, 28 | }, 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /vx/eslint-plugin-vest-internal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-vest-internal", 3 | "version": "0.1.3", 4 | "description": "Eslint plugin for vest internals.", 5 | "keywords": [ 6 | "eslint", 7 | "eslintplugin", 8 | "eslint-plugin" 9 | ], 10 | "author": "ealush", 11 | "main": "lib/index.js", 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /vx/eslint-plugin-vest-internal/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /vx/exec.js: -------------------------------------------------------------------------------- 1 | const execSync = require('child_process').execSync; 2 | 3 | const vxPath = require('./vxPath'); 4 | 5 | const logger = require('vx/logger'); 6 | const joinTruthy = require('vx/util/joinTruthy'); 7 | 8 | function exec( 9 | command, 10 | { 11 | exitOnFailure = true, 12 | throwOnFailure = false, 13 | silent = false, 14 | raw = false, 15 | } = {}, 16 | ) { 17 | const cmd = joinTruthy(command?.flat?.() ?? command, ' '); 18 | 19 | if (!raw && !silent) { 20 | logger.info(`🎬 Executing command: "${cmd}"`); 21 | } 22 | 23 | execCommand(cmd, { exitOnFailure, silent, throwOnFailure }); 24 | } 25 | 26 | module.exports = exec; 27 | 28 | function execCommand(command, { silent, throwOnFailure, exitOnFailure }) { 29 | try { 30 | run(command, silent); 31 | } catch (err) { 32 | if (throwOnFailure) { 33 | throw err; 34 | } 35 | 36 | logger.error(err.message); 37 | 38 | if (exitOnFailure) exit(); 39 | } 40 | } 41 | 42 | function run(command, silent) { 43 | execSync(command, { 44 | cwd: vxPath.VX_ROOT_PATH, 45 | stdio: silent ? 'ignore' : 'inherit', 46 | }); 47 | } 48 | 49 | function exit() { 50 | process.exit(1); 51 | } 52 | -------------------------------------------------------------------------------- /vx/getFailure: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealush/vest/8742d6f2145afe5e0cc4cfb449166e8349330c6d/vx/getFailure -------------------------------------------------------------------------------- /vx/logger.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | log: console.log, // eslint-disable-line no-console 3 | info: console.info, // eslint-disable-line no-console 4 | error: console.error, // eslint-disable-line no-console 5 | }; 6 | -------------------------------------------------------------------------------- /vx/opts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dir: { 3 | COMMANDS: 'commands', 4 | CONFIG: 'config', 5 | DIST: 'dist', 6 | EXPORTS: 'exports', 7 | PACKAGES: 'packages', 8 | ROLLUP: 'rollup', 9 | SCRIPTS: 'scripts', 10 | SRC: 'src', 11 | TESTS: '__tests__', 12 | TYPES: 'types', 13 | VITEST: 'vitest', 14 | VX: 'vx', 15 | }, 16 | env: { 17 | PRODUCTION: 'production', 18 | DEVELOPMENT: 'development', 19 | TEST: 'test', 20 | }, 21 | fileNames: { 22 | CHANGELOG: 'CHANGELOG.md', 23 | MAIN_EXPORT: 'index.js', 24 | NPM_IGNORE: '.npmignore', 25 | PACKAGE_JSON: 'package.json', 26 | ROLLUP_CONFIG: 'rollup.config.cjs', 27 | TSCONFIG_JSON: 'tsconfig.json', 28 | VITEST_CONFIG: 'vitest.config.ts', 29 | VX_BUILD: 'vx.build.js', 30 | }, 31 | format: { 32 | UMD: 'umd', 33 | CJS: 'cjs', 34 | ES: 'es', 35 | }, 36 | release_tags: { 37 | NIGHTLY: 'nightly', 38 | NEXT: 'next', 39 | DEV: 'dev', 40 | }, 41 | vx_config: { 42 | VX_ALLOW_RESOLVE: 'vxAllowResolve', 43 | VX_ROLLUP_BUILD_ENTRY: 'configbuildEntry', 44 | VX_ROLLUP_BUILD_ENTRY_EXPORTS: 'EXPORTS', 45 | VX_ROLLUP_BUILD_ENTRY_MAIN: 'MAIN', 46 | VX_ROLLUP_ENV: 'configEnv', 47 | VX_ROLLUP_FAST_BUILD: 'configfastBuild', 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /vx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "bin": "./cli.js", 3 | "dependencies": { 4 | "@rollup/plugin-node-resolve": "^15.2.3", 5 | "@rollup/plugin-replace": "^5.0.7", 6 | "context": "3.0.2", 7 | "date-fns": "^2.30.0", 8 | "dotenv": "^16.3.1", 9 | "fs-extra": "^11.2.0", 10 | "glob": "^10.3.10", 11 | "inquirer": "^9.2.12", 12 | "onchange": "^7.1.0", 13 | "prettier": "^3.3.1", 14 | "rollup": "^4.18.0", 15 | "rollup-plugin-terser": "^7.0.2", 16 | "rollup-plugin-ts": "^3.4.5", 17 | "vest-utils": "0.0.3", 18 | "vite-tsconfig-paths": "^4.3.2", 19 | "vitest": "^2.0.4", 20 | "yargs": "^17.7.2" 21 | }, 22 | "name": "vx", 23 | "private": true, 24 | "version": "0.0.0" 25 | } 26 | -------------------------------------------------------------------------------- /vx/packageExports.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const glob = require('glob'); 4 | 5 | const packageNames = require('vx/packageNames'); 6 | const vxPath = require('vx/vxPath'); 7 | 8 | module.exports = packageNames.list.reduce( 9 | (packageExports, packageName) => 10 | Object.assign(packageExports, { 11 | [packageName]: glob 12 | .sync(vxPath.packageSrcExports(packageName, '*.ts')) 13 | .map(packageExport => path.basename(packageExport, '.ts')), 14 | }), 15 | {}, 16 | ); 17 | -------------------------------------------------------------------------------- /vx/packageNames.js: -------------------------------------------------------------------------------- 1 | const { sortDependencies } = require('./scripts/release/depsTree'); 2 | 3 | const packageList = require('vx/util/packageList'); 4 | const { usePackage } = require('vx/vxContext'); 5 | 6 | module.exports = Object.defineProperty( 7 | { paths: {}, list: [], names: {} }, 8 | 'current', 9 | { 10 | get: () => { 11 | return usePackage(); 12 | }, 13 | }, 14 | ); 15 | 16 | packageList.pairs.forEach(([name, path]) => { 17 | module.exports.paths[name] = path; 18 | module.exports.names[name] = name; 19 | }); 20 | 21 | module.exports.list = sortDependencies(packageList.names); 22 | -------------------------------------------------------------------------------- /vx/scripts/build/cleanupDistFiles.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const fse = require('fs-extra'); 4 | const glob = require('glob'); 5 | 6 | const vxPath = require('vx/vxPath'); 7 | 8 | function cleanupDistFiles(packageName) { 9 | fse.removeSync(vxPath.packageDist(packageName)); 10 | 11 | const exportNames = glob 12 | .sync(vxPath.packageSrcExports(packageName, '*.ts')) 13 | .map(f => path.basename(f, '.ts')); 14 | 15 | exportNames.forEach(exportName => { 16 | fse.removeSync(vxPath.package(packageName, exportName)); 17 | }); 18 | } 19 | 20 | module.exports = cleanupDistFiles; 21 | -------------------------------------------------------------------------------- /vx/scripts/release/determineChangeLevel.js: -------------------------------------------------------------------------------- 1 | const { 2 | KEYWORD_MAJOR, 3 | KEYWORD_MINOR, 4 | KEYWORD_PATCH, 5 | KEYWORDS_MAJOR, 6 | KEYWORDS_MINOR, 7 | } = require('./releaseKeywords'); 8 | 9 | const REGEXP_MAJOR = new RegExp(`(${KEYWORDS_MAJOR.join('|')})((.+))?:`, 'i'); 10 | const REGEXP_MINOR = new RegExp(`(${KEYWORDS_MINOR.join('|')})((.+))?:`, 'i'); 11 | 12 | /** 13 | * Determines semver level 14 | * @param {string[]} messages 15 | * @return {string} change level 16 | */ 17 | const determineChangeLevel = (...messages) => { 18 | return messages.reduce((keyword, message) => { 19 | if (keyword === KEYWORD_MAJOR) { 20 | return keyword; 21 | } 22 | 23 | if (message.match(REGEXP_MAJOR)) { 24 | return KEYWORD_MAJOR; 25 | } 26 | 27 | if (keyword === KEYWORD_MINOR) { 28 | return keyword; 29 | } 30 | 31 | if (message.match(REGEXP_MINOR)) { 32 | return KEYWORD_MINOR; 33 | } 34 | 35 | return keyword; 36 | }, KEYWORD_PATCH); 37 | }; 38 | 39 | module.exports = determineChangeLevel; 40 | -------------------------------------------------------------------------------- /vx/scripts/release/github/commitIgnorePattern.js: -------------------------------------------------------------------------------- 1 | const IGNORE_KEYWORDS = ['docs', 'conf', 'ci', 'build']; 2 | const IGNORE_PATTERN = new RegExp( 3 | `${IGNORE_KEYWORDS.join('|')}:|dependabot`, 4 | 'i', 5 | ); 6 | 7 | module.exports = IGNORE_PATTERN; 8 | -------------------------------------------------------------------------------- /vx/scripts/release/github/createRelease.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | const logger = require('vx/logger'); 4 | 5 | const { GITHUB_REPOSITORY, PUBLIC_REPO_TOKEN } = process.env; 6 | 7 | async function postRelease({ tag, body, title }) { 8 | await fetch(`https://api.github.com/repos/${GITHUB_REPOSITORY}/releases`, { 9 | method: 'POST', 10 | headers: { Authorization: `token ${PUBLIC_REPO_TOKEN}` }, 11 | body: JSON.stringify({ 12 | tag_name: tag, 13 | name: title.replace(/#/g, ''), 14 | body, 15 | }), 16 | }); 17 | } 18 | 19 | async function release({ tag, release }) { 20 | logger.log(`💬 Creating github release: ${release.title}`); 21 | 22 | await postRelease({ 23 | tag, 24 | body: release.body, 25 | title: release.title, 26 | }); 27 | } 28 | 29 | module.exports = release; 30 | -------------------------------------------------------------------------------- /vx/scripts/release/github/getDiff.js: -------------------------------------------------------------------------------- 1 | const { dependsOn } = require('../depsTree'); 2 | 3 | const listAllChangesSinceStableBranch = require('./listAllChangesSinceStableBranch'); 4 | const matchPackageNameInCommit = require('./matchPackageNameInCommit'); 5 | 6 | const { usePackage } = require('vx/vxContext'); 7 | const vxPath = require('vx/vxPath'); 8 | 9 | // [{title: "...", files: ["..."]}] ... 10 | function getDiff(packageName = usePackage()) { 11 | const allChanges = listAllChangesSinceStableBranch(); 12 | const changesToPackage = filterCommitByPackage(packageName, allChanges); 13 | const changedByDependency = didChangeByDependency(packageName, allChanges); 14 | 15 | return { changesToPackage, changedByDependency }; 16 | } 17 | 18 | module.exports = getDiff; 19 | 20 | function filterCommitByPackage(packageName, commits) { 21 | return commits.filter(({ title, files }) => { 22 | if (title.match(matchPackageNameInCommit(packageName))) { 23 | return true; 24 | } 25 | 26 | return files.some(file => vxPath.packageNameFromPath(file) === packageName); 27 | }); 28 | } 29 | 30 | function didChangeByDependency(packageName, commits) { 31 | return commits.some(({ files }) => { 32 | return files.some(file => { 33 | const changedPackage = vxPath.packageNameFromPath(file); 34 | 35 | return dependsOn(packageName, changedPackage); 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /vx/scripts/release/github/listAllChangedPackages.js: -------------------------------------------------------------------------------- 1 | const listAllChangesSinceStableBranch = require('./listAllChangesSinceStableBranch'); 2 | 3 | const packageNames = require('vx/packageNames'); 4 | const vxPath = require('vx/vxPath'); 5 | 6 | function listAllChangedPackages() { 7 | const changes = listAllChangesSinceStableBranch(); 8 | 9 | return changes.reduce((packages, { files = [] }) => { 10 | return files.reduce((packages, file) => { 11 | const packageName = vxPath.packageNameFromPath(file); 12 | if (!packageNames.names[packageName]) { 13 | return packages; 14 | } 15 | 16 | packages.add(packageName); 17 | 18 | return packages; 19 | }, packages); 20 | }, new Set()); 21 | } 22 | 23 | module.exports = listAllChangedPackages; 24 | -------------------------------------------------------------------------------- /vx/scripts/release/github/listAllChangesSinceStableBranch.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').execSync; 2 | 3 | const IGNORE_PATTERN = require('./commitIgnorePattern'); 4 | 5 | const logger = require('vx/logger'); 6 | const { STABLE_BRANCH, CURRENT_BRANCH } = require('vx/util/taggedBranch'); 7 | 8 | /** 9 | * Lists all the commits and their changed files: 10 | * Returns an array of objects that look like this: 11 | * 12 | * [{title: "...", files: ["..."]}] 13 | */ 14 | function listAllChangesSinceStableBranch() { 15 | exec(`git fetch origin ${STABLE_BRANCH}`); 16 | 17 | const output = exec( 18 | `git log origin/${STABLE_BRANCH}..origin/${CURRENT_BRANCH} --name-only --pretty='format:%h %s (%an)'`, 19 | ); 20 | 21 | logger.log( 22 | `All changes between origin/${STABLE_BRANCH}..origin/${CURRENT_BRANCH}: `, 23 | output.toString(), 24 | ); 25 | 26 | return output 27 | .toString() 28 | .split('\n\n') // split each commit 29 | .map( 30 | commit => 31 | commit 32 | .split('\n') // split each line of each commit 33 | .filter(Boolean), // ignore empty lines 34 | ) 35 | .filter(([title]) => !title?.match(IGNORE_PATTERN) ?? false) // ignore excluded terms 36 | .map(([title, ...files]) => ({ 37 | title, 38 | files, 39 | })) 40 | .filter(({ title }) => Boolean(title)); 41 | } 42 | 43 | module.exports = listAllChangesSinceStableBranch; 44 | -------------------------------------------------------------------------------- /vx/scripts/release/github/matchPackageNameInCommit.js: -------------------------------------------------------------------------------- 1 | module.exports = function matchPackageNameInCommit(name) { 2 | return new RegExp(`\\[${name}\\]|\\(${name}\\)`, 'i'); 3 | }; 4 | -------------------------------------------------------------------------------- /vx/scripts/release/releaseKeywords.js: -------------------------------------------------------------------------------- 1 | const KEYWORD_PATCH = 'patch'; 2 | const KEYWORD_MINOR = 'minor'; 3 | const KEYWORD_FEAT = 'feat'; 4 | const KEYWORD_ADDED = 'added'; 5 | const KEYWORD_ADD = 'add'; 6 | const KEYWORD_MAJOR = 'major'; 7 | const KEYWORD_BREAKING = 'breaking'; 8 | 9 | const KEYWORDS_MINOR = [ 10 | KEYWORD_MINOR, 11 | KEYWORD_FEAT, 12 | KEYWORD_ADDED, 13 | KEYWORD_ADD, 14 | ]; 15 | const KEYWORDS_MAJOR = [KEYWORD_MAJOR, KEYWORD_BREAKING]; 16 | 17 | const CHANGELOG_TITLES = { 18 | [KEYWORD_MAJOR]: 'Changed or removed', 19 | [KEYWORD_MINOR]: 'Added', 20 | [KEYWORD_PATCH]: 'Fixed and improved', 21 | }; 22 | 23 | module.exports = { 24 | CHANGELOG_TITLES, 25 | KEYWORD_MAJOR, 26 | KEYWORD_MINOR, 27 | KEYWORD_PATCH, 28 | KEYWORDS_MAJOR, 29 | KEYWORDS_MINOR, 30 | }; 31 | -------------------------------------------------------------------------------- /vx/scripts/release/releasePackage.js: -------------------------------------------------------------------------------- 1 | const build = require('../build/buildPackage'); 2 | 3 | const genDiffData = require('./genDiffData'); 4 | const getDiff = require('./github/getDiff'); 5 | const publishPackage = require('./steps/publishPackage'); 6 | const setNextVersion = require('./steps/setNextVersion'); 7 | // const updateChangelog = require('./steps/updateChangelog'); 8 | const updateLocalDepsToLatest = require('./steps/updateLocalDepsToLatest'); 9 | 10 | const logger = require('vx/logger'); 11 | const { usePackage } = require('vx/vxContext'); 12 | 13 | function releasePackage({ isTopLevelChange }) { 14 | const pkgName = usePackage(); 15 | 16 | logger.info(`Releasing package: 📦 ${pkgName}`); 17 | 18 | logger.info(`🔍 Finding diffs for package: ${pkgName}`); 19 | const { changesToPackage, changedByDependency } = getDiff(pkgName); 20 | 21 | if (!changedByDependency && !changesToPackage.length && !isTopLevelChange) { 22 | logger.info('🛌 No Changes related to current package. Exiting.'); 23 | return; 24 | } 25 | 26 | const diffData = genDiffData(changesToPackage); 27 | 28 | logger.info('⚙️ Generated diff data:', JSON.stringify(diffData, null, 2)); 29 | 30 | setNextVersion(diffData); 31 | 32 | updateLocalDepsToLatest(); 33 | 34 | build(); 35 | 36 | // updateChangelog(diffData); 37 | 38 | publishPackage(diffData); 39 | } 40 | 41 | module.exports = releasePackage; 42 | -------------------------------------------------------------------------------- /vx/scripts/release/steps/create_git_tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git config --global user.email $EMAIL_ADDRESS --replace-all 4 | git config --global user.name $GIT_NAME 5 | 6 | echo "Creating Git Tag: $1" 7 | git tag -a "$1" -m "$1" 8 | 9 | echo "Pushing Git Tag: $1" 10 | git push origin $1 -------------------------------------------------------------------------------- /vx/util/concatTruthy.js: -------------------------------------------------------------------------------- 1 | module.exports = function concatTruthy(...values) { 2 | return [].concat(...values).filter(Boolean); 3 | }; 4 | -------------------------------------------------------------------------------- /vx/util/exportedModules.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const glob = require('glob'); 4 | 5 | const opts = require('vx/opts'); 6 | const { usePackage } = require('vx/vxContext'); 7 | const vxPath = require('vx/vxPath'); 8 | 9 | const namespaceDelimiter = '@'; 10 | 11 | function getExportedModuleNames(namespace, moduleName) { 12 | return [namespace, moduleName].filter(Boolean).join(namespaceDelimiter); 13 | } 14 | 15 | function listExportedModules(pkgName = usePackage()) { 16 | return ( 17 | glob.sync(vxPath.packageSrc(pkgName, opts.dir.EXPORTS, '*.ts')).map(f => { 18 | const [moduleName, namespace] = path 19 | .basename(f, '.ts') 20 | .split(namespaceDelimiter) 21 | .reverse(); 22 | return [moduleName, namespace]; 23 | }) ?? [] 24 | ); 25 | } 26 | 27 | module.exports = { 28 | listExportedModules, 29 | getExportedModuleNames, 30 | }; 31 | -------------------------------------------------------------------------------- /vx/util/joinTruthy.js: -------------------------------------------------------------------------------- 1 | const concatTruthy = require('vx/util/concatTruthy'); 2 | 3 | module.exports = function joinTruthy(values, delimiter) { 4 | return concatTruthy(values).join(delimiter); 5 | }; 6 | -------------------------------------------------------------------------------- /vx/util/once.js: -------------------------------------------------------------------------------- 1 | function once(callback) { 2 | let ran = false; 3 | 4 | return (...args) => { 5 | if (!ran) { 6 | try { 7 | callback(...args); 8 | } catch {} // eslint-disable-line no-empty 9 | 10 | ran = true; 11 | } 12 | }; 13 | } 14 | 15 | module.exports = once; 16 | -------------------------------------------------------------------------------- /vx/util/packageJson.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const { usePackage } = require('vx/vxContext'); 4 | const vxPath = require('vx/vxPath'); 5 | 6 | function packageJson(pkgName = usePackage()) { 7 | // Manually reading it instead of requiring to avoid caching 8 | const jsonString = fs.readFileSync(vxPath.packageJson(pkgName), 'utf8'); 9 | return JSON.parse(jsonString); 10 | } 11 | 12 | function getVxAllowResolve(pkgName = usePackage()) { 13 | return packageJson(pkgName).vxAllowResolve || []; 14 | } 15 | 16 | module.exports = packageJson; 17 | module.exports.getVxAllowResolve = getVxAllowResolve; 18 | -------------------------------------------------------------------------------- /vx/util/packageList.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { glob } = require('glob'); 4 | 5 | const vxPath = require('vx/vxPath'); 6 | 7 | // Unordered list of package names 8 | module.exports.pairs = glob 9 | .sync(vxPath.package('*')) 10 | .reduce((packages, packagePath) => { 11 | packages.push([path.basename(packagePath), packagePath]); 12 | return packages; 13 | }, []); 14 | 15 | module.exports.names = module.exports.pairs.map(([name]) => name); 16 | -------------------------------------------------------------------------------- /vx/util/rootPackageJson.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const opts = require('vx/opts'); 5 | const vxPath = require('vx/vxPath'); 6 | 7 | function rootPackageJson() { 8 | // Manually reading it instead of requiring to avoid caching 9 | const jsonString = fs.readFileSync( 10 | path.join(vxPath.ROOT_PATH, opts.fileNames.PACKAGE_JSON), 11 | 'utf8', 12 | ); 13 | return JSON.parse(jsonString); 14 | } 15 | 16 | module.exports = rootPackageJson; 17 | -------------------------------------------------------------------------------- /vx/util/runOnActivePackages.js: -------------------------------------------------------------------------------- 1 | const packageNames = require('vx/packageNames'); 2 | const { usePackage } = require('vx/vxContext'); 3 | const ctx = require('vx/vxContext'); 4 | 5 | module.exports = (callback, ...args) => { 6 | const packages = packageNames; 7 | const name = usePackage(); 8 | 9 | if (name) { 10 | return callback(...args); 11 | } 12 | packages.list.forEach(packageName => 13 | ctx.withPackage(packageName, () => callback(...args)), 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /vx/vxContext.js: -------------------------------------------------------------------------------- 1 | const { createContext } = require('context'); 2 | 3 | const ctx = createContext(); 4 | 5 | function withPackage(packageName, callback) { 6 | if (!packageName) { 7 | return callback(); 8 | } 9 | 10 | process.env.VX_PACKAGE_NAME = packageName; 11 | return ctx.run({ packageName }, () => callback()); 12 | } 13 | 14 | function usePackage() { 15 | return ctx.use()?.packageName ?? process.env.VX_PACKAGE_NAME; // VX_PACKAGE_NAME is only used by rollup (buildPackage.js); 16 | } 17 | 18 | module.exports = { 19 | withPackage, 20 | usePackage, 21 | }; 22 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | ``` 30 | $ GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/docs/community_resources/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Community Resources", 3 | "position": 11 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/community_resources/integrations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Custom Integrations 6 | 7 | ## ngx-vest-forms - Angular 8 | 9 | [ngx-vest-forms](https://github.com/simplifiedcourses/ngx-vest-forms) - 10 | A very lightweight adapter for Angular template-driven forms and Vest. This package gives us the ability to create unidirectional forms without any boilerplate. It is meant for complex forms with a high focus on complex validations and conditionals. 11 | 12 | ## React-Hook-Form Vest resolver 13 | 14 | [React Hook Form](https://react-hook-form.com/api/useform/#validationResolver) - One of the most popular libraries for form in React, has a resolver for integration with Vest. 15 | 16 | ## Felte/Vest 17 | 18 | [felte](https://felte.dev/docs/svelte/validators#using-vest) - An extensible form library for Svelte and Solid. The library has a pre-built integration with Vest. 19 | 20 | ## Ember-Vest 21 | 22 | [Ember-Vest](https://antonbavykin1991.github.io/ember-vest/) - An Ember library that integrates Vest with Ember.js ([antonbavykin1991](https://github.com/antonbavykin1991)) 23 | -------------------------------------------------------------------------------- /website/docs/community_resources/showcase.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Showcase by Community Members 6 | 7 | This is the place for the community to show off some of their custom Vest and Enforce use cases and solutions. Feel free to submit a pull request to add your own. 8 | 9 | - [Simple Laravel Enforcer](https://gist.github.com/Elliot-Alexander/c1c05b56df155c4010e996a4aa8e0201) - A simple custom enforcer for using Vest to serve Laravel error responses within forms. 10 | - [Vue `useSuite` Composable](https://gist.github.com/HappyTiptoe/b798357efc7c6bec59af01a5fa252f99) - A simple Vue composable for interacting with Vest validation suites. 11 | -------------------------------------------------------------------------------- /website/docs/community_resources/tutorials.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Tutorials and learning resources 6 | 7 | - [Advanced Template-driven forms course](https://www.simplified.courses/complex-angular-template-driven-forms) Stop writing boilerplate code in Angular forms, Angular course by [@brechtbilliet](https://twitter.com/brechtbilliet). 8 | 9 | - [Angular + Vest](https://www.youtube.com/watch?v=EMUAtQlh9Ko) = "Form validation Done Right" by Ward Bell in ng-cof 2022. [Github repo](https://github.com/wardbell/ngc-validate). 10 | 11 | - [AgnosticUI + Vest](https://developtodesign.com/agnosticui-examples) - Demo form using Svelte package of [AgnosticUI](https://agnosticui.com/) — a UI component library that works with React, Vue 3, and Svelte — with Vest for form validation. 12 | 13 | - [Svelte Forms: The Missing Manual](https://codechips.gumroad.com/l/svelte-forms) - An excellent book by [Ilia Mikhailov](https://twitter.com/codechips). The book contains several chapters of integration examples with Vest. 14 | 15 | - [Vue Form Validations With Vest (video)](https://portal.gitnation.org/contents/vue-form-validations-with-vest) - A Vue.JS London presentation on how to use Vest with Vue. 16 | 17 | - [Up your form validation game with Vest (video)](http://www.youtube.com/watch?v=X2PuiawaGV4) - A session from the Svelte Summit on how to use Vest with Svelte. 18 | -------------------------------------------------------------------------------- /website/docs/enforce/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Enforce", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/enforce/builtin-enforce-plugins/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Built In Enforce Plugins", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/enforce/builtin-enforce-plugins/plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | title: Builtin enforce plugins 4 | description: Builtin plugins that are included by default 5 | keywords: [Vest, enforce, plugins, n4s] 6 | --- 7 | 8 | # Builtin enforce plugins 9 | 10 | In order to save up on bundle size, enforce ships with a minimal set of rules. These rules are the most common ones, and are used in most projects. Some rules, such as isEmail, or other schema rules may be useful but less common. These are supported as plugins and can be consumed directly from the `vest/enforce` directory. 11 | 12 | The following documents in this section describe the builtin plugins that are included by default and are ready to use. 13 | 14 | To consume any of these plugins, simply import them in your project: 15 | 16 | ```js 17 | import 'vest/enforce/email'; 18 | ``` 19 | -------------------------------------------------------------------------------- /website/docs/enforce/failing_with_a_message.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | title: Failing with a message 4 | description: Sometimes we wish to fail with a message based on the validation result. Here's how we can do this. 5 | keywords: [Vest, custom, message, failing, with, message] 6 | --- 7 | 8 | # Failing with a message 9 | 10 | When running enforce you can specify a custom failure message to be thrown on failure. This is done via the `message` modifier. All you need to do is add the message before the rules it refers to. 11 | 12 | If a message is provided, it will override the default message of all rules that follow it. 13 | 14 | ```js 15 | enforce(value) 16 | .message('Value must be a number') 17 | .isNumber(); 18 | .message('Value must be positive') 19 | .isPositive(); 20 | ``` 21 | -------------------------------------------------------------------------------- /website/docs/recipes/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Recipes", 3 | "position": 8 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/recipes/typescript_enums.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Recipe- validating typescript enums 3 | description: How to validate typescript enums 4 | keywords: [Recipe, typescript, enums, inside, keys] 5 | --- 6 | 7 | Sometimes you might want to validate that a value is one of the keys or values of a typescript enum. Since typescript enums are compiled to objects, you can use the `inside` function to validate that the value is one of the keys or values of the enum. 8 | 9 | ```ts 10 | enum Fruits { 11 | APPLE = 'apple', 12 | BANANA = 'banana', 13 | CANTELOPE = 'cantelope', 14 | } 15 | 16 | // ... 17 | 18 | // If you need the enum by key: 19 | test('fruit', 'fruit is a key of fruits enum', () => { 20 | // data.fruit is a key of ["APPLE", "BANANA", "CANTELOPE"] 21 | enforce(data.fruit).inside(Object.keys(Fruits)); 22 | }); 23 | 24 | // If you need the enum by value: 25 | test('fruit', 'fruit is a value of fruits enum', () => { 26 | // data.fruit is a value of ["apple", "banana", "cantelope"] 27 | enforce(data.fruit).inside(Object.values(Fruits)); 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /website/docs/utilities/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Utilities", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/utilities/promisify.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Utility - Promisify 4 | description: Promisify is a utility function that allows you to convert your suite into a Promise. 5 | keywords: [Vest, Promisify] 6 | --- 7 | 8 | ## `promisify()` 9 | 10 | Promisify is a function that enables you to run your async validations as a [Javascript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 11 | This can be useful when running async validations on the server, or when you do not need the partial validation results. 12 | 13 | :::tip NOTE 14 | The Promise is resolved when all tests finish running. 15 | ::: 16 | 17 | ### Usage 18 | 19 | `promisify()` accepts a validation suite declaration, and returns a function that when called, returns a Promise. 20 | 21 | ```js 22 | import { create, test, skipWhen } from "vest"; 23 | import promisify from "vest/promisify"; 24 | 25 | const suite = promisify( 26 | create((data) => { 27 | test("email", "The email already exists", () => doesEmailExist(data.email)); 28 | test("username", "The username already exists", () => 29 | doesUsernameExist(data.username) 30 | ); 31 | }) 32 | ); 33 | 34 | suite(data).then((res) => { 35 | if(res.hasErrors("email")) { 36 | /* ... */ 37 | }); 38 | 39 | if(res.hasErrors("username")) { 40 | /* ... */ 41 | }); 42 | }); 43 | ``` 44 | -------------------------------------------------------------------------------- /website/docs/writing_tests/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Writing Tests", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/writing_tests/advanced_test_features/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Advanced Test Features", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/writing_tests/advanced_test_features/debounce.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: Debouncing Tests 4 | description: Debouncing tests to improve async test performance and flow control 5 | keywords: [Vest, debounce, async, test] 6 | --- 7 | 8 | ## `debounce()` 9 | 10 | The `debounce()` function in Vest helps you optimize function execution by introducing a delay. This is useful in scenarios where a function is called repeatedly due to user interaction, and you only want to execute the latest version after a period of inactivity. 11 | 12 | ### Usage 13 | 14 | **1. Import Debounce** 15 | 16 | ```js 17 | import debounce from 'vest/debounce'; 18 | ``` 19 | 20 | **2. Wrap your Test Callback:** 21 | 22 | ```js 23 | test( 24 | 'username', 25 | 'User already taken', 26 | debounce(async () => { 27 | await doesUserExist(); 28 | }, 2000), 29 | ); 30 | ``` 31 | 32 | In the above example, Vest will wait for two seconds before executing the test, and it will be run only once, no matter how many times the suite was invoked during this time period. 33 | 34 | :::caution IMPORTANT 35 | When using `debounce`, all debounced tests are treated as async, even if the test callback is synchronous. This is because the test will be executed after the debounce period, which is an async operation. 36 | ::: 37 | -------------------------------------------------------------------------------- /website/docs/writing_your_suite/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Writing your suite", 3 | "position": 4 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/writing_your_suite/including_and_excluding/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Including and Excluding Tests", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^3.4.0", 18 | "@docusaurus/plugin-google-gtag": "^3.4.0", 19 | "@docusaurus/preset-classic": "^3.4.0", 20 | "@easyops-cn/docusaurus-search-local": "^0.38.1", 21 | "@mdx-js/react": "^3.0.1", 22 | "@svgr/webpack": "^8.1.0", 23 | "clsx": "^2.1.1", 24 | "file-loader": "^6.2.0", 25 | "prism-react-renderer": "^2.3.1", 26 | "react": "^18.3.1", 27 | "react-dom": "^18.3.1", 28 | "url-loader": "^4.1.1" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /website/src/components/Common.module.css: -------------------------------------------------------------------------------- 1 | .main_section_centered { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures.module.css: -------------------------------------------------------------------------------- 1 | .emoji { 2 | font-size: 2rem; 3 | } 4 | -------------------------------------------------------------------------------- /website/src/components/RawExample.js: -------------------------------------------------------------------------------- 1 | import CodeBlock from '@theme/CodeBlock'; 2 | import clsx from 'clsx'; 3 | import React from 'react'; 4 | import commonStyles from './Common.module.css'; 5 | import styles from './RawExample.module.css'; 6 | 7 | export default () => { 8 | return ( 9 |
12 |

13 | Vest is a form validations framework that looks and feels like a unit 14 | testing framework. 15 |
16 | It allows you to express your validation logic in a simple and readable 17 | way that's also easy to maintain in the long run. 18 |

19 | {` 20 | test("username", "Username is required", () => { 21 | enforce(data.username).isNotBlank(); 22 | }); 23 | 24 | test("username", "Username must be at least 3 chars", () => { 25 | enforce(data.username).longerThanOrEquals(3); 26 | }); 27 | 28 | test('username', 'Username already taken', async () => { 29 | await doesUserExist(data.username); 30 | }); 31 | `} 32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /website/src/components/RawExample.module.css: -------------------------------------------------------------------------------- 1 | .code { 2 | margin-left: 4rem; 3 | } 4 | 5 | .section { 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | padding: 4rem; 10 | } 11 | 12 | .desc { 13 | max-width: 400px; 14 | jusify-self: flex-end; 15 | } 16 | 17 | @media screen and (max-width: 966px) { 18 | .code { 19 | margin-left: 0; 20 | } 21 | 22 | .section { 23 | flex-direction: column; 24 | } 25 | } 26 | 27 | @media screen and (max-width: 600px) { 28 | .code { 29 | display: none; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealush/vest/8742d6f2145afe5e0cc4cfb449166e8349330c6d/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealush/vest/8742d6f2145afe5e0cc4cfb449166e8349330c6d/website/static/favicon.ico -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealush/vest/8742d6f2145afe5e0cc4cfb449166e8349330c6d/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/img/og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealush/vest/8742d6f2145afe5e0cc4cfb449166e8349330c6d/website/static/img/og.jpg -------------------------------------------------------------------------------- /website/static/img/play_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/community_resources/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Community Resources", 3 | "position": 11 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/community_resources/integrations.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Custom Integrations 6 | 7 | ## React-Hook-Form Vest resolver 8 | 9 | [React Hook Form](https://react-hook-form.com/api/useform/#validationResolver) - One of the most popular libraries for form in React, has a resolver for integration with Vest. 10 | 11 | ## Felte/Vest 12 | 13 | [felte](https://felte.dev/docs/svelte/validators#using-vest) - An extensible form library for Svelte and Solid. The library has a pre-built integration with Vest. 14 | 15 | ## Ember-Vest 16 | 17 | [Ember-Vest](https://antonbavykin1991.github.io/ember-vest/) - An Ember library that integrates Vest with Ember.js ([antonbavykin1991](https://github.com/antonbavykin1991)) 18 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/community_resources/showcase.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Showcase by Community Members 6 | 7 | This is the place for the community to show off some of their custom Vest and Enforce use cases and solutions. Feel free to submit a pull request to add your own. 8 | 9 | - [Simple Laravel Enforcer](https://gist.github.com/Elliot-Alexander/c1c05b56df155c4010e996a4aa8e0201) - A simple custom enforcer for using Vest to serve Laravel error responses within forms. 10 | - [Vue `useSuite` Composable](https://gist.github.com/HappyTiptoe/b798357efc7c6bec59af01a5fa252f99) - A simple Vue composable for interacting with Vest validation suites. 11 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/community_resources/tutorials.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Tutorials and learning resources 6 | 7 | - [AgnosticUI + Vest](https://developtodesign.com/agnosticui-examples) - Demo form using Svelte package of [AgnosticUI](https://agnosticui.com/) — a UI component library that works with React, Vue 3, and Svelte — with Vest for form validation. 8 | 9 | - [Svelte Forms: The Missing Manual](https://codechips.gumroad.com/l/svelte-forms) - An excellent book by [Ilia Mikhailov](https://twitter.com/codechips). The book contains several chapters of integration examples with Vest. 10 | 11 | - [Vue Form Validations With Vest (video)](https://portal.gitnation.org/contents/vue-form-validations-with-vest) - A Vue.JS London presentation on how to use Vest with Vue. 12 | 13 | - [Up your form validation game with Vest (video)](http://www.youtube.com/watch?v=X2PuiawaGV4) - A session from the Svelte Summit on how to use Vest with Svelte. 14 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/enforce/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Enforce", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/enforce/builtin-enforce-plugins/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Built In Enforce Plugins", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/enforce/enforce.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: enforce 4 | description: Enforce is Vest's assertion library. It is used to validate values within a Vest test. 5 | keywords: [Vest, enforce, validation, validation library, assertions] 6 | --- 7 | 8 | # Enforce 9 | 10 | Enforce is Vest's assertion library. It is used to validate values within a Vest test. 11 | 12 | ```js 13 | import { enforce, test } from 'vest'; 14 | 15 | test('username', 'Must be at least three characters long', () => { 16 | enforce(username).longerThan(2); 17 | }); 18 | ``` 19 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/enforce/failing_with_a_message.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | title: Failing with a message 4 | description: Sometimes we wish to fail with a message based on the validation result. Here's how we can do this. 5 | keywords: [Vest, custom, message, failing, with, message] 6 | --- 7 | 8 | # Failing with a message 9 | 10 | When running enforce you can specify a custom failure message to be thrown on failure. This is done via the `message` modifier. All you need to do is add the message before the rules it refers to. 11 | 12 | If a message is provided, it will override the default message of all rules that follow it. 13 | 14 | ```js 15 | enforce(value) 16 | .message('Value must be a number') 17 | .isNumber(); 18 | .message('Value must be positive') 19 | .isPositive(); 20 | ``` 21 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/get_started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: Getting started 4 | description: Getting started with Vest is easy. Here are some examples. 5 | keywords: [Vest, Get started, Quickstart] 6 | --- 7 | 8 | # Getting Started 9 | 10 | ## Installation 11 | 12 | To install the stable version of Vest, run: 13 | 14 | ``` 15 | npm i vest 16 | ``` 17 | 18 | ## Writing your first suite 19 | 20 | A Vest suite is very similar to a unit testing suite in Jest or Mocha, so the following might look familiar: 21 | 22 | ```js 23 | // suite.js 24 | import { create, test, enforce } from 'vest'; 25 | 26 | const suite = create((data = {}) => { 27 | test('username', 'Username is required', () => { 28 | enforce(data.username).isNotBlank(); 29 | }); 30 | 31 | test('username', 'Username must be at least 3 characters long', () => { 32 | enforce(data.username).longerThan(2); 33 | }); 34 | }); 35 | 36 | export default suite; 37 | ``` 38 | 39 | Vest is a powerful framework, and it has quite a few features. In the following sections, you'll learn Vest's core concepts and how to make use of it. 40 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/recipes/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Recipes", 3 | "position": 8 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/recipes/typescript_enums.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Recipe- validating typescript enums 3 | description: How to validate typescript enums 4 | keywords: [Recipe, typescript, enums, inside, keys] 5 | --- 6 | 7 | Sometimes you might want to validate that a value is one of the keys or values of a typescript enum. Since typescript enums are compiled to objects, you can use the `inside` function to validate that the value is one of the keys or values of the enum. 8 | 9 | ```ts 10 | enum Fruits { 11 | APPLE = 'apple', 12 | BANANA = 'banana', 13 | CANTELOPE = 'cantelope', 14 | } 15 | 16 | // ... 17 | 18 | // If you need the enum by key: 19 | test('fruit', 'fruit is a key of fruits enum', () => { 20 | // data.fruit is a key of ["APPLE", "BANANA", "CANTELOPE"] 21 | enforce(data.fruit).inside(Object.keys(Fruits)); 22 | }); 23 | 24 | // If you need the enum by value: 25 | test('fruit', 'fruit is a value of fruits enum', () => { 26 | // data.fruit is a value of ["apple", "banana", "cantelope"] 27 | enforce(data.fruit).inside(Object.values(Fruits)); 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/using_with_node.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 11 3 | title: Using with Node.js 4 | description: Learn how to use the Vest with Node.js 5 | keywords: [Vest, Node, Server Side] 6 | --- 7 | 8 | # Using Vest in node 9 | 10 | Using Vest in node is mostly the same as it is in the browser, but you should consider your runtime. 11 | 12 | ## Validation state 13 | 14 | When running your validations in your api, you usually want to have stateless validations to prevent leakage between requests. 15 | 16 | Read more about [Vest's state](./understanding_state.md). 17 | 18 | ## require vs import 19 | 20 | Depending on your node version and the module system you support you can use different syntax to include Vest. 21 | 22 | ### Most compatible: commonjs 23 | 24 | To be on the safe side and compatible with all node versions, use a `require` statement. 25 | 26 | ```js 27 | const vest = require('vest'); 28 | const { test, enforce } = vest; 29 | ``` 30 | 31 | ### Node 14 32 | 33 | With node 14's support of [package entry points](https://nodejs.org/api/esm.html#esm_package_entry_points), node should be able to detect on its own which import style you use and load the correct bundle. 34 | 35 | Both of the following should work: 36 | 37 | ```js 38 | import { create, test } from 'vest'; 39 | ``` 40 | 41 | ```js 42 | const vest = require('vest'); 43 | ``` 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/utilities/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Utilities", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/utilities/promisify.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Utility - Promisify 4 | description: Promisify is a utility function that allows you to convert your suite into a Promise. 5 | keywords: [Vest, Promisify] 6 | --- 7 | 8 | ## `promisify()` 9 | 10 | Promisify is a function that enables you to run your async validations as a [Javascript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 11 | This can be useful when running async validations on the server, or when you do not need the partial validation results. 12 | 13 | :::tip NOTE 14 | The Promise is resolved when all tests finish running. 15 | ::: 16 | 17 | ### Usage 18 | 19 | `promisify()` accepts a validation suite declaration, and returns a function that when called, returns a Promise. 20 | 21 | ```js 22 | import { create, test, skipWhen } from "vest"; 23 | import promisify from "vest/promisify"; 24 | 25 | const suite = promisify( 26 | create((data) => { 27 | test("email", "The email already exists", () => doesEmailExist(data.email)); 28 | test("username", "The username already exists", () => 29 | doesUsernameExist(data.username) 30 | ); 31 | }) 32 | ); 33 | 34 | suite(data).then((res) => { 35 | if(res.hasErrors("email")) { 36 | /* ... */ 37 | }); 38 | 39 | if(res.hasErrors("username")) { 40 | /* ... */ 41 | }); 42 | }); 43 | ``` 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/writing_tests/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Writing Tests", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/writing_tests/advanced_test_features/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Advanced Test Features", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/writing_tests/async_tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: Async Validations 4 | description: Here's how to write async tests. 5 | keywords: [Vest, Async, Validations] 6 | --- 7 | 8 | # Writing Asynchronous Tests 9 | 10 | Sometimes you need to validate your data with information not present in your current context, for example - data from the server, such as username availability. In those cases, you need to go out to the server and fetch data as part of your validation logic. 11 | 12 | An async test is declared by returning a promise from your test body (or making it an async function). When the promise resolves, your test passes, and when your promise rejects, it fails. 13 | 14 | ```js 15 | // Example using a promise 16 | test('name', 'I always fail', () => Promise.reject()); 17 | 18 | // Example using async/await 19 | test('name', 'Already Taken', async () => { 20 | return await doesUserExist(user); 21 | }); 22 | ``` 23 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/writing_your_suite/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Writing your suite", 3 | "position": 4 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.x/writing_your_suite/including_and_excluding/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Including and Excluding Tests", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_sidebars/version-4.x-sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "tutorialSidebar": [ 3 | { 4 | "type": "autogenerated", 5 | "dirName": "." 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /website/versions.json: -------------------------------------------------------------------------------- 1 | ["4.x"] 2 | --------------------------------------------------------------------------------