Skip to content

Commit

Permalink
Migrate testing suite to Vitest (#1242)
Browse files Browse the repository at this point in the history
  • Loading branch information
yeoffrey committed Jun 5, 2024
1 parent a316560 commit f24c3cd
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 130 deletions.
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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": [
Expand Down
1 change: 1 addition & 0 deletions test/api/assert.ts → test/api/assert.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { throws, doesNotThrow } from 'assert'
import { describe, it } from 'vitest'
import { assert, string, StructError } from '../../src'

describe('assert', () => {
Expand Down
1 change: 1 addition & 0 deletions test/api/create.ts → test/api/create.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { strictEqual, deepEqual, deepStrictEqual, throws } from 'assert'
import { describe, it } from 'vitest'
import {
type,
optional,
Expand Down
1 change: 1 addition & 0 deletions test/api/is.ts → test/api/is.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { strictEqual } from 'assert'
import { describe, it } from 'vitest'
import { is, string } from '../../src'

describe('is', () => {
Expand Down
1 change: 1 addition & 0 deletions test/api/mask.ts → test/api/mask.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { deepStrictEqual, throws } from 'assert'
import { describe, it } from 'vitest'
import {
mask,
object,
Expand Down
1 change: 1 addition & 0 deletions test/api/validate.ts → test/api/validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { deepStrictEqual, strictEqual } from 'assert'
import { describe, it } from 'vitest'
import {
validate,
string,
Expand Down
116 changes: 116 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -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
}
124 changes: 0 additions & 124 deletions test/index.ts
Original file line number Diff line number Diff line change
@@ -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<T>(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
}
5 changes: 4 additions & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"extends": "../tsconfig.json",
"include": ["./**/*.ts"]
"include": ["./**/*.ts"],
"compilerOptions": {
"skipLibCheck": true
}
}

0 comments on commit f24c3cd

Please sign in to comment.