Skip to content

Commit

Permalink
Migrate testing suite to Vitest (ianstormtaylor#1242)
Browse files Browse the repository at this point in the history
  • Loading branch information
yeoffrey committed Jun 25, 2024
1 parent a316560 commit 599e7b3
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 130 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,23 @@ jobs:
- run: npm install && npm test && npm run lint
env:
CI: true

codecov:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x

- name: Install deps
run: npm install

- name: Generate coverage report
run: npm run test:coverage

- name: Upload coverage to Codecov
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/
npm-debug.log
package-lock.json
tmp/
coverage/
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,22 @@
"@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",
"@vitest/coverage-istanbul": "^1.6.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"is-email": "^1.0.0",
"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 +55,10 @@
"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",
"test:coverage": "vitest run --coverage",
"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
}
}
12 changes: 12 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
coverage: {
enabled: true,
include: ['src'],
provider: 'istanbul',
reporter: ['cobertura'],
},
},
})

0 comments on commit 599e7b3

Please sign in to comment.