Skip to content

Commit

Permalink
Add support for overriding libtap's internal settings
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Feb 23, 2021
1 parent 29eed63 commit 8d7f62e
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 2 deletions.
24 changes: 24 additions & 0 deletions bin/jack.js
Expand Up @@ -647,6 +647,25 @@ Much more documentation available at: https://www.node-tap.org/
default: `${process.cwd()}/.taprc`,
}),

'libtap-settings': opt({
hint: 'module',
description: `A module which exports an object of fields to assign onto
'libtap/settings'. These are advanced configuration options
for modifying the behavior of tap's internal runtime.
Module path is resolved relative to the current working
directory.
Allowed fields: rmdirRecursive, rmdirRecursiveSync,
StackUtils, stackUtils, output, snapshotFile.
See libtap documentation for expected values and usage.
https://github.com/tapjs/libtap`,
envDefault: 'TAP_LIBTAP_SETTINGS',
default: null,
}),

'output-file': opt({
short: 'o',
hint: 'file',
Expand Down Expand Up @@ -715,6 +734,11 @@ Much more documentation available at: https://www.node-tap.org/
./.taprc`
}),

TAP_LIBTAP_SETTINGS: env({
description: `A path (relative to current working directory) of a file
that exports fields to override the default libtap settings`,
}),

TAP_TIMEOUT: env(num({
min: 0,
default: 30,
Expand Down
4 changes: 4 additions & 0 deletions bin/run.js
Expand Up @@ -3,6 +3,7 @@
// default to no color if requested via standard environment var
if (process.env.NO_COLOR === '1') {
process.env.TAP_COLORS = '0'
process.env.FORCE_COLOR = '0'
}

const signalExit = require('signal-exit')
Expand Down Expand Up @@ -209,6 +210,9 @@ const mainAsync = async options => {
throw er
})

if (options['libtap-settings'])
process.env.TAP_LIBTAP_SETTINGS = path.resolve(options['libtap-settings'])

// we test this directly, not from here.
/* istanbul ignore next */
if (options.watch)
Expand Down
27 changes: 25 additions & 2 deletions settings.js
@@ -1,7 +1,7 @@
'use strict'

const sourceMapSupport = require('source-map-support')
const settings = require('libtap/settings');
const settings = require('libtap/settings')

sourceMapSupport.install({environment:'node', hookRequire: true})

Expand All @@ -22,4 +22,27 @@ if (+process.env.TAP_DEV_LONGSTACK !== 1) {

settings.stackUtils.wrapCallSite = sourceMapSupport.wrapCallSite

module.exports = settings;
if (process.env.TAP_LIBTAP_SETTINGS) {
const overrides = require(process.env.TAP_LIBTAP_SETTINGS)
const type = typeof overrides
const isArray = Array.isArray(overrides)
if (!overrides || isArray || type !== 'object') {
throw new Error('invalid libtap settings: ' + (
isArray ? 'array'
: type === 'object' ? 'null'
: type
))
}

for (const [key, value] of Object.entries(overrides)) {
if (!Object.prototype.hasOwnProperty.call(settings, key))
throw new Error('Unrecognized libtap setting: ' + key)
if (typeof value !== typeof settings[key]) {
throw new Error(`Invalid type for libtap setting ${key}. Expected ${
typeof settings[key]}, received ${typeof value}.`)
}
settings[key] = value
}
}

module.exports = settings
34 changes: 34 additions & 0 deletions tap-snapshots/test/run/libtap-settings.js.test.cjs
@@ -0,0 +1,34 @@
/* IMPORTANT
* This snapshot file is auto-generated, but designed for humans.
* It should be checked into source control and tracked carefully.
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/run/libtap-settings.js TAP print out a different snapshot file location > must match snapshot 1`] = `
TAP version 13
ok 1 - {CWD}/test/run/tap-testdir-libtap-settings/test.js # SKIP {
# some-path/test.js.test.cjs
1..0
# {time}
}
1..1
# skip: 1
# {time}
`

exports[`test/run/libtap-settings.js TAP print out the normal snapshot file location > must match snapshot 1`] = `
TAP version 13
ok 1 - {CWD}/test/run/tap-testdir-libtap-settings/test.js # SKIP {
# {CWD}/test/run/tap-testdir-libtap-settings/tap-snapshots/test.js.test.cjs
1..0
# {time}
}
1..1
# skip: 1
# {time}
`
131 changes: 131 additions & 0 deletions test/run/libtap-settings.js
@@ -0,0 +1,131 @@
const {
tmpfile,
run,
tap,
t,
} = require('./')

const { resolve } = require('path')

const path = t.testdir({
settings: {
'ok.js': `
module.exports = {
snapshotFile: (cwd, main, argv) => 'some-path/' + main + argv + '.test.cjs'
}
`,
'ok-empty.js': `
module.exports = {}
`,
'unknown-field.js': `
module.exports = {
foo: 'bar'
}
`,
'wrong-type-field.js': `
module.exports = {
snapshotFile: 75
}
`,
'export-function.js': `
module.exports = () => {}
`,
'export-array.js': `
module.exports = [1, 2, 3]
`,
'export-false.js': `
module.exports = false
`,
'export-null.js': `
module.exports = null
`,
},
'test.js': `
const t = require(${tap})
t.comment(t.snapshotFile)
`,
})

const testFile = resolve(path, 'test.js')
const settings = n => `--libtap-settings=settings/${n}.js`

t.test('print out a different snapshot file location', t => {
run([settings('ok'), testFile], {cwd: path}, (er, o, e) => {
t.notOk(er)
t.equal(e, '')
t.matchSnapshot(o)
t.end()
})
})

t.test('print out the normal snapshot file location', t => {
run([settings('ok-empty'), testFile], {cwd: path}, (er, o, e) => {
t.notOk(er)
t.equal(e, '')
t.matchSnapshot(o)
t.end()
})
})

t.test('fails if module not found', t => {
run([settings('does-not-exist'), testFile], {cwd: path}, (er, o, e) => {
t.match(er, { code: 1 })
t.match(e, 'Error: ') // specific msg different across node versions
t.equal(o, '')
t.end()
})
})

t.test('adding an unknown field is invalid', t => {
run([settings('unknown-field'), testFile], {cwd: path}, (er, o, e) => {
t.match(er, { code: 1 })
t.match(e, 'Error: Unrecognized libtap setting: foo')
t.equal(o, '')
t.end()
})
})

t.test('fields must be same type as libtap defines', t => {
run([settings('wrong-type-field'), testFile], {cwd: path}, (er, o, e) => {
t.match(er, { code: 1 })
t.match(e, 'Error: Invalid type for libtap setting snapshotFile. Expected function, received number.')
t.equal(o, '')
t.end()
})
})

t.test('exporting function is invalid', t => {
run([settings('export-function'), testFile], {cwd: path}, (er, o, e) => {
t.match(er, { code: 1 })
t.match(e, 'Error: invalid libtap settings: function')
t.equal(o, '')
t.end()
})
})

t.test('exporting array is invalid', t => {
run([settings('export-array'), testFile], {cwd: path}, (er, o, e) => {
t.match(er, { code: 1 })
t.match(e, 'Error: invalid libtap settings: array')
t.equal(o, '')
t.end()
})
})

t.test('exporting false is invalid', t => {
run([settings('export-false'), testFile], {cwd: path}, (er, o, e) => {
t.match(er, { code: 1 })
t.match(e, 'Error: invalid libtap settings: boolean')
t.equal(o, '')
t.end()
})
})

t.test('exporting null is invalid', t => {
run([settings('export-null'), testFile], {cwd: path}, (er, o, e) => {
t.match(er, { code: 1 })
t.match(e, 'Error: invalid libtap settings: null')
t.equal(o, '')
t.end()
})
})
51 changes: 51 additions & 0 deletions test/settings/overrides.js
@@ -0,0 +1,51 @@
const t = require('../..')
const { resolve } = require('path')

t.afterEach(() => delete process.env.TAP_LIBTAP_SETTINGS)

t.test('override the snapshot location', t => {
process.env.TAP_LIBTAP_SETTINGS = resolve(t.testdir({
'settings.js': `
module.exports = {
snapshotFile: (cwd, main, argv) => 'some-path/' + main + argv + '.snap'
}
`
}), 'settings.js')
const settings = t.mock('../../settings.js')
t.equal(settings.snapshotFile('a', 'b', 'c'), 'some-path/bc.snap')
t.end()
})

t.test('no fields, thats ok', t => {
process.env.TAP_LIBTAP_SETTINGS = resolve(t.testdir({
'settings.js': `
module.exports = {}
`
}), 'settings.js')
const settings = t.mock('../../settings.js')
t.strictSame(settings, require('../../settings.js'))
t.end()
})

t.test('wrong export tests', t => {
const cases = Object.entries({
function: '() => {}',
boolean: 'false',
array: '[]',
null: 'null',
'unknown field': `{ foo: 'bar' }`,
'wrong field type': '{ snapshotFile: 1234 }',
})
t.plan(cases.length)
for (const [name, code] of cases) {
t.test(`export ${name}`, t => {
process.env.TAP_LIBTAP_SETTINGS = resolve(t.testdir({
'settings.js': `
module.exports = ${code}
`
}), 'settings.js')
t.throws(() => t.mock('../../settings.js'))
t.end()
})
}
})

0 comments on commit 8d7f62e

Please sign in to comment.