diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/lib/content-function.js | 285 | ||||
| -rw-r--r-- | test/lib/generic-mock.js | 314 | ||||
| -rw-r--r-- | test/lib/index.js | 2 |
3 files changed, 0 insertions, 601 deletions
diff --git a/test/lib/content-function.js b/test/lib/content-function.js deleted file mode 100644 index 49fe5c95..00000000 --- a/test/lib/content-function.js +++ /dev/null @@ -1,285 +0,0 @@ -import * as path from 'node:path'; -import {fileURLToPath} from 'node:url'; -import {inspect} from 'node:util'; - -import chroma from 'chroma-js'; - -import {showAggregate} from '#aggregate'; -import {getColors} from '#colors'; -import {quickLoadContentDependencies} from '#content-dependencies'; -import {quickEvaluate} from '#content-function'; -import * as html from '#html'; -import {internalDefaultStringsFile, processLanguageFile} from '#language'; -import {empty} from '#sugar'; - -import { - applyLocalizedWithBaseDirectory, - generateURLs, - internalDefaultURLSpecFile, - processURLSpecFromFileSync, - thumb, -} from '#urls'; - -import mock from './generic-mock.js'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -function cleanURLSpec(urlSpec) { - for (const spec of Object.values(urlSpec)) { - if (spec.prefix) { - // Strip out STATIC_VERSION. This updates fairly regularly and we - // don't want it to affect snapshot tests. - spec.prefix = spec.prefix - .replace(/static-\d+[a-z]\d+/i, 'static'); - } - } -} - -function urlsPlease() { - const {aggregate: urlsAggregate, result: urlSpec} = - processURLSpecFromFileSync(internalDefaultURLSpecFile); - - urlsAggregate.close(); - - applyLocalizedWithBaseDirectory(urlSpec); - - cleanURLSpec(urlSpec); - - return generateURLs(urlSpec); -} - -export function testContentFunctions(t, message, fn) { - const urls = urlsPlease(); - - t.test(message, async t => { - let loadedContentDependencies; - - const language = await processLanguageFile(internalDefaultStringsFile); - const mocks = []; - - const evaluate = ({ - from = 'localized.home', - contentDependencies = {}, - extraDependencies = {}, - ...opts - }) => { - if (!loadedContentDependencies) { - throw new Error(`Await .load() before performing tests`); - } - - const {to} = urls.from(from); - - return cleanCatchAggregate(() => { - return quickEvaluate({ - ...opts, - contentDependencies: { - ...contentDependencies, - ...loadedContentDependencies, - }, - extraDependencies: { - html, - language, - thumb, - to, - urls, - - pagePath: ['home'], - appendIndexHTML: false, - getColors: c => getColors(c, {chroma}), - - wikiData: { - wikiInfo: {}, - }, - - ...extraDependencies, - }, - }); - }); - }; - - evaluate.load = async (opts) => { - if (loadedContentDependencies) { - throw new Error(`Already loaded!`); - } - - loadedContentDependencies = await asyncCleanCatchAggregate(() => - quickLoadContentDependencies({ - logging: false, - ...opts, - })); - }; - - evaluate.snapshot = (...args) => { - if (!loadedContentDependencies) { - throw new Error(`Await .load() before performing tests`); - } - - const [description, opts] = - (typeof args[0] === 'string' - ? args - : ['output', ...args]); - - let result = evaluate(opts); - - if (opts.multiple) { - result = result.map(item => item.toString()).join('\n'); - } else { - result = result.toString(); - } - - t.matchSnapshot(result, description); - }; - - evaluate.stubTemplate = name => - // Creates a particularly permissable template, allowing any slot values - // to be stored and just outputting the contents of those slots as-are. - _stubTemplate(name, false); - - evaluate.stubContentFunction = name => - // Like stubTemplate, but instead of a template directly, returns - // an object describing a content function - suitable for passing - // into evaluate.mock. - _stubTemplate(name, true); - - const _stubTemplate = (name, mockContentFunction) => { - const inspectNicely = (value, opts = {}) => - inspect(value, { - ...opts, - colors: false, - sort: true, - }); - - const makeTemplate = formatContentFn => - new (class extends html.Template { - #slotValues = {}; - - constructor() { - super({ - content: () => this.#getContent(formatContentFn), - }); - } - - setSlots(slotNamesToValues) { - Object.assign(this.#slotValues, slotNamesToValues); - } - - setSlot(slotName, slotValue) { - this.#slotValues[slotName] = slotValue; - } - - #getContent(formatContentFn) { - const toInspect = - Object.fromEntries( - Object.entries(this.#slotValues) - .filter(([key, value]) => value !== null)); - - const inspected = - inspectNicely(toInspect, { - breakLength: Infinity, - compact: true, - depth: Infinity, - }); - - return formatContentFn(inspected); `${name}: ${inspected}`; - } - }); - - if (mockContentFunction) { - return { - data: (...args) => ({args}), - generate: (data) => - makeTemplate(slots => { - const argsLines = - (empty(data.args) - ? [] - : inspectNicely(data.args, {depth: Infinity}) - .split('\n')); - - return (`[mocked: ${name}` + - - (empty(data.args) - ? `` - : argsLines.length === 1 - ? `\n args: ${argsLines[0]}` - : `\n args: ${argsLines[0]}\n` + - argsLines.slice(1).join('\n').replace(/^/gm, ' ')) + - - (!empty(data.args) - ? `\n ` - : ` - `) + - - (slots - ? `slots: ${slots}]` - : `slots: none]`)); - }), - }; - } else { - return makeTemplate(slots => `${name}: ${slots}`); - } - }; - - evaluate.mock = (...opts) => { - const {value, close} = mock(...opts); - mocks.push({close}); - return value; - }; - - evaluate.mock.transformContent = { - transformContent: { - extraDependencies: ['html'], - data: content => ({content}), - slots: {mode: {type: 'string'}}, - generate: ({content}) => content, - }, - }; - - await fn(t, evaluate); - - if (!empty(mocks)) { - cleanCatchAggregate(() => { - const errors = []; - for (const {close} of mocks) { - try { - close(); - } catch (error) { - errors.push(error); - } - } - if (!empty(errors)) { - throw new AggregateError(errors, `Errors closing mocks`); - } - }); - } - }); -} - -function printAggregate(error) { - if (error instanceof AggregateError) { - const message = showAggregate(error, { - showTraces: true, - print: false, - pathToFileURL: f => path.relative(path.join(__dirname, '../..'), fileURLToPath(f)), - }); - for (const line of message.split('\n')) { - console.error(line); - } - } -} - -function cleanCatchAggregate(fn) { - try { - return fn(); - } catch (error) { - printAggregate(error); - throw error; - } -} - -async function asyncCleanCatchAggregate(fn) { - try { - return await fn(); - } catch (error) { - printAggregate(error); - throw error; - } -} diff --git a/test/lib/generic-mock.js b/test/lib/generic-mock.js deleted file mode 100644 index 28309ab0..00000000 --- a/test/lib/generic-mock.js +++ /dev/null @@ -1,314 +0,0 @@ -import {same} from 'tcompare'; - -import {empty} from '#sugar'; - -export default function mock(callback) { - const mocks = []; - - const track = callback => (...args) => { - const {value, close} = callback(...args); - mocks.push({close}); - return value; - }; - - const mock = { - function: track(mockFunction), - }; - - return { - value: callback(mock), - close: () => { - const errors = []; - for (const mock of mocks) { - try { - mock.close(); - } catch (error) { - errors.push(error); - } - } - if (!empty(errors)) { - throw new AggregateError(errors, `Errors closing sub-mocks`); - } - }, - }; -} - -export function mockFunction(...args) { - let name = '(anonymous)'; - let behavior = null; - - if (args.length === 2) { - if ( - typeof args[0] === 'string' && - typeof args[1] === 'function' - ) { - name = args[0]; - behavior = args[1]; - } else { - throw new TypeError(`Expected name to be a string`); - } - } else if (args.length === 1) { - if (typeof args[0] === 'string') { - name = args[0]; - } else if (typeof args[0] === 'function') { - behavior = args[0]; - } else if (args[0] !== null) { - throw new TypeError(`Expected string (name), function (behavior), both, or null / no arguments`); - } - } else if (args.length > 2) { - throw new TypeError(`Expected string (name), function (behavior), both, or null / no arguments`); - } - - let currentCallDescription = newCallDescription(); - const allCallDescriptions = [currentCallDescription]; - - const topLevelErrors = []; - let runningCallCount = 0; - let limitCallCount = false; - let markedAsOnce = false; - - const fn = (...args) => { - const description = processCall(...args); - return description.behavior(...args); - }; - - fn.behavior = value => { - if (!(value === null || ( - typeof value === 'function' - ))) { - throw new TypeError(`Expected function or null`); - } - - currentCallDescription.behavior = behavior; - currentCallDescription.described = true; - - return fn; - } - - fn.argumentCount = value => { - if (!(value === null || ( - typeof value === 'number' && - value === parseInt(value) && - value >= 0 - ))) { - throw new TypeError(`Expected whole number or null`); - } - - if (currentCallDescription.argsPattern) { - throw new TypeError(`Unexpected .argumentCount() when .args() has been called`); - } - - currentCallDescription.argsPattern = {length: value}; - currentCallDescription.described = true; - - return fn; - }; - - fn.args = (...args) => { - const value = args[0]; - - if (args.length > 1 || !(value === null || Array.isArray(value))) { - throw new TypeError(`Expected one array or null`); - } - - currentCallDescription.argsPattern = Object.fromEntries( - value - .map((v, i) => v === undefined ? false : [i, v]) - .filter(Boolean) - .concat([['length', value.length]])); - - currentCallDescription.described = true; - - return fn; - }; - - fn.neverCalled = (...args) => { - if (!empty(args)) { - throw new TypeError(`Didn't expect any arguments`); - } - - if (allCallDescriptions[0].described) { - throw new TypeError(`Unexpected .neverCalled() when any descriptions provided`); - } - - limitCallCount = true; - allCallDescriptions.splice(0, allCallDescriptions.length); - - currentCallDescription = new Proxy({}, { - set() { - throw new Error(`Unexpected description when .neverCalled() has been called`); - }, - }); - - return fn; - }; - - fn.once = (...args) => { - if (!empty(args)) { - throw new TypeError(`Didn't expect any arguments`); - } - - if (allCallDescriptions.length > 1) { - throw new TypeError(`Unexpected .once() when providing multiple descriptions`); - } - - currentCallDescription.described = true; - limitCallCount = true; - markedAsOnce = true; - - return fn; - }; - - fn.next = (...args) => { - if (!empty(args)) { - throw new TypeError(`Didn't expect any arguments`); - } - - if (markedAsOnce) { - throw new TypeError(`Unexpected .next() when .once() has been called`); - } - - currentCallDescription = newCallDescription(); - allCallDescriptions.push(currentCallDescription); - - limitCallCount = true; - - return fn; - }; - - fn.repeat = times => { - // Note: This function should be called AFTER filling out the - // call description which is being repeated. - - if (!( - typeof times === 'number' && - times === parseInt(times) && - times >= 2 - )) { - throw new TypeError(`Expected whole number of at least 2`); - } - - if (markedAsOnce) { - throw new TypeError(`Unexpected .repeat() when .once() has been called`); - } - - // The current call description is already in the full list, - // so skip the first push. - for (let i = 2; i <= times; i++) { - allCallDescriptions.push(currentCallDescription); - } - - // Prep a new description like when calling .next(). - currentCallDescription = newCallDescription(); - allCallDescriptions.push(currentCallDescription); - - limitCallCount = true; - - return fn; - }; - - return { - value: fn, - close: () => { - const totalCallCount = runningCallCount; - const expectedCallCount = countDescribedCalls(); - - if (limitCallCount && totalCallCount !== expectedCallCount) { - if (expectedCallCount > 1) { - topLevelErrors.push(new Error(`Expected ${expectedCallCount} calls, got ${totalCallCount}`)); - } else if (expectedCallCount === 1) { - topLevelErrors.push(new Error(`Expected 1 call, got ${totalCallCount}`)); - } else { - topLevelErrors.push(new Error(`Expected no calls, got ${totalCallCount}`)); - } - } - - if (topLevelErrors.length) { - throw new AggregateError(topLevelErrors, `Errors in mock ${name}`); - } - }, - }; - - function newCallDescription() { - return { - described: false, - behavior: behavior ?? null, - argumentCount: null, - argsPattern: null, - }; - } - - function processCall(...args) { - const callErrors = []; - - runningCallCount++; - - // No further processing, this indicates the function shouldn't have been - // called at all and there aren't any descriptions to match this call with. - if (empty(allCallDescriptions)) { - return newCallDescription(); - } - - const currentCallNumber = runningCallCount; - const currentDescription = selectCallDescription(currentCallNumber); - - const { - argumentCount, - argsPattern, - } = currentDescription; - - if (argumentCount !== null && args.length !== argumentCount) { - callErrors.push( - new Error(`Argument count mismatch: expected ${argumentCount}, got ${args.length}`)); - } - - if (argsPattern !== null) { - const keysToCheck = Object.keys(argsPattern); - const argsAsObject = Object.fromEntries( - args - .map((v, i) => [i.toString(), v]) - .filter(([i]) => keysToCheck.includes(i)) - .concat([['length', args.length]])); - - const {match, diff} = same(argsAsObject, argsPattern); - if (!match) { - callErrors.push(new Error(`Argument pattern mismatch:\n` + diff)); - } - } - - if (!empty(callErrors)) { - const aggregate = new AggregateError(callErrors, `Errors in call #${currentCallNumber}`); - topLevelErrors.push(aggregate); - } - - return currentDescription; - } - - function selectCallDescription(currentCallNumber) { - if (currentCallNumber > countDescribedCalls()) { - const lastDescription = lastCallDescription(); - if (lastDescription.described) { - return newCallDescription(); - } else { - return lastDescription; - } - } else { - return allCallDescriptions[currentCallNumber - 1]; - } - } - - function countDescribedCalls() { - if (empty(allCallDescriptions)) { - return 0; - } - - return ( - (lastCallDescription().described - ? allCallDescriptions.length - : allCallDescriptions.length - 1)); - } - - function lastCallDescription() { - return allCallDescriptions[allCallDescriptions.length - 1]; - } -} diff --git a/test/lib/index.js b/test/lib/index.js index 4c9ee23f..f9bf71e7 100644 --- a/test/lib/index.js +++ b/test/lib/index.js @@ -1,7 +1,5 @@ Error.stackTraceLimit = Infinity; export * from './composite.js'; -export * from './content-function.js'; -export * from './generic-mock.js'; export * from './wiki-data.js'; export * from './strict-match-error.js'; |