From 578b42db683046e383f0dc261a12269e46d95a67 Mon Sep 17 00:00:00 2001 From: Geoff Date: Mon, 10 Jun 2024 17:38:44 -0400 Subject: [PATCH] Use vitest except where possible and organize tests --- test/api/assert.test.ts | 2 +- test/api/create.test.ts | 16 ++-- test/api/is.test.ts | 11 ++- test/api/mask.test.ts | 18 ++--- test/api/validate.test.ts | 38 +++++---- test/deprecated.test.ts | 33 ++++++++ test/index.test.ts | 157 ++++++++++++++------------------------ 7 files changed, 132 insertions(+), 143 deletions(-) create mode 100644 test/deprecated.test.ts diff --git a/test/api/assert.test.ts b/test/api/assert.test.ts index a0479982..b1a910d9 100644 --- a/test/api/assert.test.ts +++ b/test/api/assert.test.ts @@ -1,4 +1,4 @@ -import { throws, doesNotThrow } from 'assert' +import { doesNotThrow, throws } from 'assert' import { describe, it } from 'vitest' import { assert, string, StructError } from '../../src' diff --git a/test/api/create.test.ts b/test/api/create.test.ts index 5f88db94..753c4f8f 100644 --- a/test/api/create.test.ts +++ b/test/api/create.test.ts @@ -1,5 +1,4 @@ -import { strictEqual, deepEqual, deepStrictEqual, throws } from 'assert' -import { describe, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { type, optional, @@ -9,26 +8,27 @@ import { literal, coerce, } from '../../src' +import { throws } from 'assert' describe('create', () => { it('missing as helper', () => { const S = defaulted(string(), 'default') - strictEqual(create(undefined, S), 'default') + expect(create(undefined, S)).toBe('default') }) it('missing as method', () => { const S = defaulted(string(), 'default') - strictEqual(S.create(undefined), 'default') + expect(S.create(undefined)).toBe('default') }) it('not missing as helper', () => { const S = defaulted(string(), 'default') - strictEqual(create('string', S), 'string') + expect(create('string', S)).toBe('string') }) it('not missing as method', () => { const S = defaulted(string(), 'default') - strictEqual(S.create('string'), 'string') + expect(S.create('string')).toBe('string') }) it('missing optional fields remain missing', () => { @@ -37,7 +37,7 @@ describe('create', () => { b: optional(string()), c: optional(type({ d: string() })), }) - deepEqual(S.create({ a: 'a' }), { a: 'a' }) + expect(S.create({ a: 'a' })).toStrictEqual({ a: 'a' }) }) it('explicit undefined values are kept', () => { @@ -46,7 +46,7 @@ describe('create', () => { b: coerce(optional(string()), literal(null), () => undefined), c: optional(type({ d: string() })), }) - deepStrictEqual(S.create({ a: 'a', b: null, c: undefined }), { + expect(S.create({ a: 'a', b: null, c: undefined })).toStrictEqual({ a: 'a', b: undefined, c: undefined, diff --git a/test/api/is.test.ts b/test/api/is.test.ts index 8341a011..6ddaa815 100644 --- a/test/api/is.test.ts +++ b/test/api/is.test.ts @@ -1,21 +1,20 @@ -import { strictEqual } from 'assert' -import { describe, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { is, string } from '../../src' describe('is', () => { it('valid as helper', () => { - strictEqual(is('valid', string()), true) + expect(is('valid', string())).toBe(true) }) it('valid as method', () => { - strictEqual(string().is('valid'), true) + expect(string().is('valid')).toBe(true) }) it('invalid as helper', () => { - strictEqual(is(42, string()), false) + expect(is(42, string())).toBe(false) }) it('invalid as method', () => { - strictEqual(string().is(42), false) + expect(string().is(42)).toBe(false) }) }) diff --git a/test/api/mask.test.ts b/test/api/mask.test.ts index 7d360365..fbeb26da 100644 --- a/test/api/mask.test.ts +++ b/test/api/mask.test.ts @@ -1,5 +1,5 @@ -import { deepStrictEqual, throws } from 'assert' -import { describe, it } from 'vitest' +import { throws } from 'assert' +import { describe, expect, it } from 'vitest' import { mask, object, @@ -14,7 +14,7 @@ describe('mask', () => { it('object as helper', () => { const S = object({ id: string() }) const value = { id: '1', unknown: true } - deepStrictEqual(mask(value, S), { id: '1' }) + expect(mask(value, S)).toStrictEqual({ id: '1' }) }) it('non-object as helper', () => { @@ -28,7 +28,7 @@ describe('mask', () => { it('coercing', () => { const S = defaulted(object({ id: string() }), { id: '0' }) const value = { unknown: true } - deepStrictEqual(mask(value, S), { id: '0' }) + expect(mask(value, S)).toStrictEqual({ id: '0' }) }) it('deep masking of objects', () => { @@ -41,7 +41,7 @@ describe('mask', () => { unknown: true, sub: [{ prop: '2', unknown: true }], } - deepStrictEqual(mask(value, S), { id: '1', sub: [{ prop: '2' }] }) + expect(mask(value, S)).toStrictEqual({ id: '1', sub: [{ prop: '2' }] }) }) it('masking of a nested type', () => { @@ -54,7 +54,7 @@ describe('mask', () => { unknown: true, sub: [{ prop: '2', unknown: true }], } - deepStrictEqual(mask(value, S), { + expect(mask(value, S)).toStrictEqual({ id: '1', sub: [{ prop: '2', unknown: true }], }) @@ -70,7 +70,7 @@ describe('mask', () => { unknown: true, sub: [{ prop: '2', unknown: true }], } - deepStrictEqual(mask(value, S), { + expect(mask(value, S)).toStrictEqual({ id: '1', unknown: true, sub: [{ prop: '2' }], @@ -80,8 +80,8 @@ describe('mask', () => { it('masking does not change the original value', () => { const S = object({ id: string() }) const value = { id: '1', unknown: true } - deepStrictEqual(mask(value, S), { id: '1' }) - deepStrictEqual(value, { id: '1', unknown: true }) + expect(mask(value, S)).toStrictEqual({ id: '1' }) + expect(value).toStrictEqual({ id: '1', unknown: true }) }) it('custom error message', () => { diff --git a/test/api/validate.test.ts b/test/api/validate.test.ts index b87dda5f..9643a290 100644 --- a/test/api/validate.test.ts +++ b/test/api/validate.test.ts @@ -1,5 +1,4 @@ -import { deepStrictEqual, strictEqual } from 'assert' -import { describe, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { validate, string, @@ -13,20 +12,20 @@ import { describe('validate', () => { it('valid as helper', () => { const S = string() - deepStrictEqual(validate('valid', S), [undefined, 'valid']) + expect(validate('valid', S)).toStrictEqual([undefined, 'valid']) }) it('valid as method', () => { const S = string() - deepStrictEqual(S.validate('valid'), [undefined, 'valid']) + expect(S.validate('valid')).toStrictEqual([undefined, 'valid']) }) it('invalid as helper', () => { const S = string() const [err, value] = validate(42, S) - strictEqual(value, undefined) - strictEqual(err instanceof StructError, true) - deepStrictEqual(Array.from((err as StructError).failures()), [ + expect(value).toStrictEqual(undefined) + expect(err).toBeInstanceOf(StructError) + expect(Array.from((err as StructError).failures())).toStrictEqual([ { value: 42, key: undefined, @@ -43,9 +42,9 @@ describe('validate', () => { it('invalid as method', () => { const S = string() const [err, value] = S.validate(42) - strictEqual(value, undefined) - strictEqual(err instanceof StructError, true) - deepStrictEqual(Array.from((err as StructError).failures()), [ + expect(value).toStrictEqual(undefined) + expect(err).toBeInstanceOf(StructError) + expect(Array.from((err as StructError).failures())).toStrictEqual([ { value: 42, key: undefined, @@ -62,8 +61,7 @@ describe('validate', () => { it('error message path', () => { const S = object({ author: object({ name: string() }) }) const [err] = S.validate({ author: { name: 42 } }) - strictEqual( - (err as StructError).message, + expect(err?.message).toBe( 'At path: author.name -- Expected a string, but received: 42' ) }) @@ -71,13 +69,13 @@ describe('validate', () => { it('custom error message', () => { const S = string() const [err] = S.validate(42, { message: 'Validation failed!' }) - strictEqual(err?.message, 'Validation failed!') - strictEqual(err?.cause, 'Expected a string, but received: 42') + expect(err?.message).toBe('Validation failed!') + expect(err?.cause).toBe('Expected a string, but received: 42') }) it('early exit', () => { let ranA = false - let ranB = false + const ranB = false const A = define('A', (x) => { ranA = true @@ -91,8 +89,8 @@ describe('validate', () => { const S = object({ a: A, b: B }) S.validate({ a: null, b: null }) - strictEqual(ranA, true) - strictEqual(ranB, false) + expect(ranA).toBe(true) + expect(ranB).toBe(false) }) it('refiners after children', () => { @@ -109,7 +107,7 @@ describe('validate', () => { }) B.validate({ a: null }) - deepStrictEqual(order, ['validator', 'refiner']) + expect(order).toStrictEqual(['validator', 'refiner']) }) it('refiners even if nested refiners fail', () => { @@ -127,7 +125,7 @@ describe('validate', () => { const [error] = B.validate({ a: null }) // Collect all failures. Ensures all validation runs. error?.failures() - strictEqual(ranOuterRefiner, true) + expect(ranOuterRefiner).toBe(true) }) it('skips refiners if validators return errors', () => { @@ -145,6 +143,6 @@ describe('validate', () => { const [error] = B.validate({ a: null }) // Collect all failures. Ensures all validation runs. error?.failures() - strictEqual(ranRefiner, false) + expect(ranRefiner).toBe(false) }) }) diff --git a/test/deprecated.test.ts b/test/deprecated.test.ts new file mode 100644 index 00000000..6124542c --- /dev/null +++ b/test/deprecated.test.ts @@ -0,0 +1,33 @@ +import { describe, it } from 'vitest' +import { CallTracker } from 'assert' +import { any, assert, Context, deprecated } from '../src' + +describe('deprecated', () => { + it('does not log deprecated type if value is undefined', () => { + const tracker = new CallTracker() + const logSpy = buildSpyWithZeroCalls(tracker) + assert(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) + assert('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.test.ts b/test/index.test.ts index f61b9013..dcd66e51 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,116 +1,75 @@ -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' +import { describe, expect, it } from 'vitest' +import { assert, create as createValue, 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))) +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 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 + 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 + try { + if (create) { + actual = createValue(data, Struct) + } else { + assert(data, Struct) + actual = data + } + } catch (e) { + if (!(e instanceof StructError)) { + throw 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)) + err = e + } - assert.deepStrictEqual(actualFailures, failures) - assert.deepStrictEqual(pick(err, ...props), failures[0]) - } else { + if ('output' in module) { + if (err) { throw new Error( - `The "${name}" fixture did not define an \`output\` or \`failures\` export.` + `Expected "${name}" fixture not to throw an error but it did:\n\n${err}` ) } - }) - } - }) - } - }) - 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() - }) + expect(actual).toStrictEqual(output) + } else if ('failures' in module) { + if (!err) { + throw new Error( + `Expected "${name}" fixture to throw an error but it did not.` + ) + } - 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() + const props = ['type', 'path', 'refinement', 'value', 'branch'] + const actualFailures = err + .failures() + .map((failure) => pick(failure, ...props)) + expect(actualFailures).toStrictEqual(failures) + expect(pick(err, ...props)).toStrictEqual(failures[0]) + } else { + throw new Error( + `The "${name}" fixture did not define an \`output\` or \`failures\` export.` + ) + } + }) + } }) - }) + } }) - -/** - * 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 -}