diff --git a/package.json b/package.json index 79c46408..45c9fa3f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "@rollup/plugin-typescript": "^11.1.6", "@types/expect": "^24.3.0", "@types/lodash": "^4.14.144", - "@types/mocha": "^10.0.0", "@types/node": "^18.7.14", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", @@ -39,11 +38,11 @@ "is-url": "^1.2.4", "is-uuid": "^1.0.2", "lodash": "^4.17.15", - "mocha": "^10.0.0", "np": "^10.0.0", "prettier": "^3.2.5", "rollup": "^4.12.1", - "typescript": "^4.8.3" + "typescript": "^4.8.3", + "vitest": "^1.6.0" }, "scripts": { "build": "rm -rf ./{dist} && rollup --config ./rollup.config.js", @@ -55,9 +54,9 @@ "lint:eslint": "eslint '{src,test}/*.{js,ts}'", "lint:prettier": "prettier --list-different '**/*.{js,json,ts}'", "release": "npm run build && npm run lint && np", - "test": "npm run build && npm run test:types && npm run test:mocha", - "test:mocha": "mocha --require ./test/register.cjs --require source-map-support/register ./test/index.ts", + "test": "npm run build && npm run test:types && npm run test:vitest", "test:types": "tsc --noEmit && tsc --project ./test/tsconfig.json --noEmit", + "test:vitest": "vitest run", "watch": "npm run build -- --watch" }, "keywords": [ diff --git a/test/api/assert.ts b/test/api/assert.test.ts similarity index 95% rename from test/api/assert.ts rename to test/api/assert.test.ts index 65d55a56..a0479982 100644 --- a/test/api/assert.ts +++ b/test/api/assert.test.ts @@ -1,4 +1,5 @@ import { throws, doesNotThrow } from 'assert' +import { describe, it } from 'vitest' import { assert, string, StructError } from '../../src' describe('assert', () => { diff --git a/test/api/create.ts b/test/api/create.test.ts similarity index 97% rename from test/api/create.ts rename to test/api/create.test.ts index 873d30ec..5f88db94 100644 --- a/test/api/create.ts +++ b/test/api/create.test.ts @@ -1,4 +1,5 @@ import { strictEqual, deepEqual, deepStrictEqual, throws } from 'assert' +import { describe, it } from 'vitest' import { type, optional, diff --git a/test/api/is.ts b/test/api/is.test.ts similarity index 91% rename from test/api/is.ts rename to test/api/is.test.ts index 46789f95..8341a011 100644 --- a/test/api/is.ts +++ b/test/api/is.test.ts @@ -1,4 +1,5 @@ import { strictEqual } from 'assert' +import { describe, it } from 'vitest' import { is, string } from '../../src' describe('is', () => { diff --git a/test/api/mask.ts b/test/api/mask.test.ts similarity index 98% rename from test/api/mask.ts rename to test/api/mask.test.ts index c054d978..7d360365 100644 --- a/test/api/mask.ts +++ b/test/api/mask.test.ts @@ -1,4 +1,5 @@ import { deepStrictEqual, throws } from 'assert' +import { describe, it } from 'vitest' import { mask, object, diff --git a/test/api/validate.ts b/test/api/validate.test.ts similarity index 98% rename from test/api/validate.ts rename to test/api/validate.test.ts index cb6d878f..b87dda5f 100644 --- a/test/api/validate.ts +++ b/test/api/validate.test.ts @@ -1,4 +1,5 @@ import { deepStrictEqual, strictEqual } from 'assert' +import { describe, it } from 'vitest' import { validate, string, diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 00000000..f61b9013 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,116 @@ +import assert, { CallTracker } from 'assert' +import fs from 'fs' +import { pick } from 'lodash' +import { basename, extname, resolve } from 'path' +import { describe, it } from 'vitest' +import { + any, + assert as assertValue, + Context, + create as createValue, + deprecated, + StructError, +} from '../src' + +describe('superstruct', () => { + describe('validation', () => { + const kindsDir = resolve(__dirname, 'validation') + const kinds = fs + .readdirSync(kindsDir) + .filter((t) => t[0] !== '.') + .map((t) => basename(t, extname(t))) + + for (const kind of kinds) { + describe(kind, async () => { + const testsDir = resolve(kindsDir, kind) + const tests = fs + .readdirSync(testsDir) + .filter((t) => t[0] !== '.') + .map((t) => basename(t, extname(t))) + + for (const name of tests) { + const module = await import(resolve(testsDir, name)) + const { Struct, data, create, only, skip, output, failures } = module + const run = only ? it.only : skip ? it.skip : it + run(name, () => { + let actual + let err + + try { + if (create) { + actual = createValue(data, Struct) + } else { + assertValue(data, Struct) + actual = data + } + } catch (e) { + if (!(e instanceof StructError)) { + throw e + } + + err = e + } + + if ('output' in module) { + if (err) { + throw new Error( + `Expected "${name}" fixture not to throw an error but it did:\n\n${err}` + ) + } + + assert.deepStrictEqual(actual, output) + } else if ('failures' in module) { + if (!err) { + throw new Error( + `Expected "${name}" fixture to throw an error but it did not.` + ) + } + + const props = ['type', 'path', 'refinement', 'value', 'branch'] + const actualFailures = err + .failures() + .map((failure) => pick(failure, ...props)) + + assert.deepStrictEqual(actualFailures, failures) + assert.deepStrictEqual(pick(err, ...props), failures[0]) + } else { + throw new Error( + `The "${name}" fixture did not define an \`output\` or \`failures\` export.` + ) + } + }) + } + }) + } + }) + + describe('deprecated', () => { + it('does not log deprecated type if value is undefined', () => { + const tracker = new CallTracker() + const logSpy = buildSpyWithZeroCalls(tracker) + assertValue(undefined, deprecated(any(), logSpy)) + tracker.verify() + }) + + it('logs deprecated type to passed function if value is present', () => { + const tracker = new CallTracker() + const fakeLog = (value: unknown, ctx: Context) => {} + const logSpy = tracker.calls(fakeLog, 1) + assertValue('present', deprecated(any(), logSpy)) + tracker.verify() + }) + }) +}) + +/** + * This emulates `tracker.calls(0)`. + * + * `CallTracker.calls` doesn't support passing `0`, therefore we expect it + * to be called once which is our call in this test. This proves that + * the following action didn't call it. + */ +function buildSpyWithZeroCalls(tracker: CallTracker) { + const logSpy = tracker.calls(1) + logSpy() + return logSpy +} diff --git a/test/index.ts b/test/index.ts index 2d4bc819..d28f0685 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1,129 +1,5 @@ -import assert, { CallTracker } from 'assert' -import fs from 'fs' -import { pick } from 'lodash' -import { basename, extname, resolve } from 'path' -import { - any, - assert as assertValue, - Context, - create as createValue, - deprecated, - StructError, -} from '../src' - -describe('superstruct', () => { - describe('api', () => { - require('./api/assert') - require('./api/create') - require('./api/is') - require('./api/mask') - require('./api/validate') - }) - - describe('validation', () => { - const kindsDir = resolve(__dirname, 'validation') - const kinds = fs - .readdirSync(kindsDir) - .filter((t) => t[0] !== '.') - .map((t) => basename(t, extname(t))) - - for (const kind of kinds) { - describe(kind, () => { - const testsDir = resolve(kindsDir, kind) - const tests = fs - .readdirSync(testsDir) - .filter((t) => t[0] !== '.') - .map((t) => basename(t, extname(t))) - - for (const name of tests) { - const module = require(resolve(testsDir, name)) - const { Struct, data, create, only, skip, output, failures } = module - const run = only ? it.only : skip ? it.skip : it - run(name, () => { - let actual - let err - - try { - if (create) { - actual = createValue(data, Struct) - } else { - assertValue(data, Struct) - actual = data - } - } catch (e) { - if (!(e instanceof StructError)) { - throw e - } - - err = e - } - - if ('output' in module) { - if (err) { - throw new Error( - `Expected "${name}" fixture not to throw an error but it did:\n\n${err}` - ) - } - - assert.deepStrictEqual(actual, output) - } else if ('failures' in module) { - if (!err) { - throw new Error( - `Expected "${name}" fixture to throw an error but it did not.` - ) - } - - const props = ['type', 'path', 'refinement', 'value', 'branch'] - const actualFailures = err - .failures() - .map((failure) => pick(failure, ...props)) - - assert.deepStrictEqual(actualFailures, failures) - assert.deepStrictEqual(pick(err, ...props), failures[0]) - } else { - throw new Error( - `The "${name}" fixture did not define an \`output\` or \`failures\` export.` - ) - } - }) - } - }) - } - }) - - describe('deprecated', () => { - it('does not log deprecated type if value is undefined', () => { - const tracker = new CallTracker() - const logSpy = buildSpyWithZeroCalls(tracker) - assertValue(undefined, deprecated(any(), logSpy)) - tracker.verify() - }) - - it('logs deprecated type to passed function if value is present', () => { - const tracker = new CallTracker() - const fakeLog = (value: unknown, ctx: Context) => {} - const logSpy = tracker.calls(fakeLog, 1) - assertValue('present', deprecated(any(), logSpy)) - tracker.verify() - }) - }) -}) - /** * A helper for testing type signatures. */ export function test(fn: (x: unknown) => T) {} - -/** - * This emulates `tracker.calls(0)`. - * - * `CallTracker.calls` doesn't support passing `0`, therefore we expect it - * to be called once which is our call in this test. This proves that - * the following action didn't call it. - */ -function buildSpyWithZeroCalls(tracker: CallTracker) { - const logSpy = tracker.calls(1) - logSpy() - return logSpy -} diff --git a/test/tsconfig.json b/test/tsconfig.json index 5197ce27..25d841e7 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../tsconfig.json", - "include": ["./**/*.ts"] + "include": ["./**/*.ts"], + "compilerOptions": { + "skipLibCheck": true + } }