feat: each support (#13)

* feat: add each support for test, describe

* docs: document test

* feat: label templating basic support

* refactor: group test exports together

* refactor: leverage each in repetitive tests

* chore: pinned node -> alias

* test: coverage for it, test
This commit is contained in:
Marc 2023-04-07 23:14:15 -04:00
parent 711d0097ce
commit 2bc6d94507
Signed by: marc
GPG key ID: 048E042F22B5DC79
10 changed files with 145 additions and 88 deletions

2
.nvmrc
View file

@ -1 +1 @@
18.15.0 lts/hydrogen

3
src/index.ts Normal file
View file

@ -0,0 +1,3 @@
export { default as test, it } from './testComponents/test'
export { default as describe } from './testComponents/describe'
export { default as expect } from './testComponents/expect'

View file

@ -1,36 +0,0 @@
import { promises as fs } from 'fs'
import expect from './expect'
import { greenText, redText } from './utils'
import { type TestCaseLabel, type TestCaseFunction, type TestCaseGroup } from './types'
function describe(label: TestCaseLabel, testGroup: TestCaseGroup) {
if (process.env.COLLECT) {
testGroup()
return
}
console.group(greenText(label))
testGroup()
console.groupEnd()
}
function test(label: TestCaseLabel, testCase: TestCaseFunction): void {
if (process.env.COLLECT) {
console.log(label)
return
}
try {
testCase()
console.log(greenText(`[PASSED] ${label}`))
} catch (e) {
console.group(redText(`[FAILED] ${label}`))
console.log(redText(String(e)))
console.groupEnd()
}
}
const it = test
export { it, test, expect, describe }

View file

@ -0,0 +1,43 @@
import { promises as fs } from 'fs'
import { greenText, redText } from '../utils'
import { type TestCaseLabel, type TestCaseFunction, type TestCaseGroup } from '../types'
/*
* `describe` facilitates grouping tests together.
*
* ```
* describe('My test group', () => {
* test('My first test', ...)
*
* test('My second test', ...)
* })
* ```
*/
function describe(label: TestCaseLabel, testGroup: TestCaseGroup) {
if (process.env.COLLECT) {
testGroup()
return
}
console.group(greenText(label))
testGroup()
console.groupEnd()
}
Object.defineProperty(describe, 'each', {
value: function (values: Array<unknown>) {
return (label: TestCaseLabel, testGroup: TestCaseGroup) => {
values.forEach((value: unknown, index: number) => {
describe(label.replace(/%s/g, String(value)), () => testGroup(value))
})
}
},
enumerable: true,
})
type extendedDescribe = typeof describe & { [key: string]: (...args: Array<unknown>) => extendedDescribe }
const extDescribe = describe as extendedDescribe
export default extDescribe

View file

@ -9,7 +9,7 @@ import {
type RawComparisonMatcher, type RawComparisonMatcher,
type RawMatchersMap, type RawMatchersMap,
type MatcherName, type MatcherName,
} from './types' } from '../types'
import matchers from './matchers' import matchers from './matchers'

View file

@ -6,7 +6,7 @@
*/ */
import assert from 'assert' import assert from 'assert'
import { type MatcherReport } from './types' import { type MatcherReport } from '../types'
/* /*
* Asserts whether value and other are strictly equal. * Asserts whether value and other are strictly equal.

View file

@ -0,0 +1,53 @@
import { promises as fs } from 'fs'
import { greenText, redText } from '../utils'
import { type TestCaseLabel, type TestCaseFunction, type TestCaseGroup } from '../types'
/*
* `test` defines a single test case.
*
* ```
* test('My test', () => {
* // Assert things.
* })
* ```
*/
function test(label: TestCaseLabel, testCase: TestCaseFunction): void {
if (process.env.COLLECT) {
console.log(label)
return
}
try {
testCase()
console.log(greenText(`[PASSED] ${label}`))
} catch (e) {
console.group(redText(`[FAILED] ${label}`))
console.log(redText(String(e)))
console.groupEnd()
}
}
Object.defineProperty(test, 'each', {
value: function (values: Array<unknown>) {
return (label: TestCaseLabel, testCase: TestCaseFunction) => {
values.forEach((value: unknown, index: number) => {
test(label.replace(/%s/g, String(value)), () => testCase(value))
})
}
},
enumerable: true,
})
type extendedTest = typeof test & { [key: string]: (...args: Array<unknown>) => extendedTest }
const extTest = test as extendedTest
/*
* `it` is an alias of `test`.
*/
const it = extTest
export { it }
export default extTest

View file

@ -2,8 +2,8 @@ import { type Server } from 'net'
export type TestCaseLabel = string export type TestCaseLabel = string
export type TestFilePath = string export type TestFilePath = string
export type TestCaseFunction = () => void export type TestCaseFunction = (...args: Array<unknown>) => void
export type TestCaseGroup = () => void export type TestCaseGroup = (...args: Array<unknown>) => void
export interface TestServer extends Server { export interface TestServer extends Server {
failure?: boolean failure?: boolean

View file

@ -1,55 +1,17 @@
import assert from 'assert' import assert from 'assert'
import { describe, test, expect } from '../src/testCaseUtils' import { describe, test, expect } from '../src'
describe('Equality', () => { describe('Equality', () => {
test('Equality (number)', () => { test.each([1, 'expectations', true])('Equality (value=%s)', (value: unknown) => {
assert.doesNotThrow(() => expect(1).toEqual(1)) assert.doesNotThrow(() => expect(value).toEqual(value))
}) })
test('Equality (string)', () => { test.each([
assert.doesNotThrow(() => expect('expectations').toEqual('expectations')) [1, 2],
}) ['expectation', 'something else'],
[true, false],
test('Equality (boolean)', () => { ])('Equality (failed - values=%s)', (...pair: Array<unknown>) => {
assert.doesNotThrow(() => expect(true).toEqual(true)) assert.throws(() => expect(pair[0]).toEqual(pair[1]))
})
test('Equality (failed - number)', () => {
assert.throws(() => expect(1).toEqual(2))
})
test('Equality (failed - string)', () => {
assert.throws(() => expect('expectation').toEqual('something else'))
})
test('Equality (failed - boolean)', () => {
assert.throws(() => expect(true).toEqual(false))
})
})
describe('Identity', () => {
test('Identity comparison (number)', () => {
assert.doesNotThrow(() => expect(1).toBe(1))
})
test('Identity comparison (boolean)', () => {
assert.doesNotThrow(() => expect(true).toBe(true))
})
test('Identity comparison (string)', () => {
assert.doesNotThrow(() => expect('identity').toBe('identity'))
})
test('Identity comparison (failed - number)', () => {
assert.throws(() => expect(1).toEqual(2))
})
test('Identity comparison (failed - boolean)', () => {
assert.throws(() => expect(false).toBe(true))
})
test('Identity comparison (failed - string)', () => {
assert.throws(() => expect('yes').toBe('no'))
}) })
test('Equality negation', () => { test('Equality negation', () => {
@ -57,6 +19,24 @@ describe('Identity', () => {
}) })
}) })
describe('Identity', () => {
test.each([1, true, 'identity'])('Identity comparison (value=%s)', (value: unknown) => {
assert.doesNotThrow(() => expect(value).toBe(value))
})
test.each([
[1, 2],
[false, true],
['yes', 'no'],
])('Identity comparison (failed - value=%s)', (...pair: Array<unknown>) => {
assert.throws(() => expect(pair[0]).toBe(pair[1]))
})
test('Identity negation', () => {
assert.doesNotThrow(() => expect('yes').not.toBe('no'))
})
})
describe('Exception expectation', () => { describe('Exception expectation', () => {
test('Expects error', () => { test('Expects error', () => {
const err = new Error('err') const err = new Error('err')

14
tests/it.test.ts Normal file
View file

@ -0,0 +1,14 @@
import assert from 'assert'
import { it, test, expect, describe } from '../src'
describe.each([it, test])('Runs tests', (fn: unknown) => {
const testFn = fn as typeof test
testFn('Runs a test', () => {
assert.doesNotThrow(() => expect(1).toEqual(1))
})
testFn.each([1, 2, 3])('Supports parametrization (value=%s)', (value: unknown) => {
assert.doesNotThrow(() => expect(value).toEqual(value))
})
})