diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/lib/composite.js | 33 | ||||
| -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 | 7 | ||||
| -rw-r--r-- | test/lib/wiki-data.js | 156 | ||||
| -rw-r--r-- | test/test-lib.js (renamed from test/lib/strict-match-error.js) | 36 | ||||
| -rw-r--r-- | test/unit/content/dependencies/generateAlbumTrackList.js | 43 | ||||
| -rw-r--r-- | test/unit/content/dependencies/linkArtist.js | 31 | ||||
| -rw-r--r-- | test/unit/content/dependencies/linkContribution.js | 145 | ||||
| -rw-r--r-- | test/unit/data/cacheable-object.js | 12 | ||||
| -rw-r--r-- | test/unit/data/composite/control-flow/exposeConstant.js | 42 | ||||
| -rw-r--r-- | test/unit/data/composite/control-flow/exposeDependency.js | 64 | ||||
| -rw-r--r-- | test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js | 197 | ||||
| -rw-r--r-- | test/unit/data/composite/data/withPropertiesFromObject.js | 241 | ||||
| -rw-r--r-- | test/unit/data/composite/data/withPropertyFromObject.js | 195 | ||||
| -rw-r--r-- | test/unit/data/composite/data/withUniqueItemsOnly.js | 69 | ||||
| -rw-r--r-- | test/unit/data/validators.js | 16 |
17 files changed, 42 insertions, 1844 deletions
diff --git a/test/lib/composite.js b/test/lib/composite.js deleted file mode 100644 index 359d364d..00000000 --- a/test/lib/composite.js +++ /dev/null @@ -1,33 +0,0 @@ -import {compositeFrom} from '#composite'; - -export function quickCheckCompositeOutputs(t, dependencies) { - return (step, outputDict) => { - t.same( - Object.keys(step.toDescription().outputs), - Object.keys(outputDict)); - - const composite = compositeFrom({ - compose: false, - steps: [ - step, - - { - dependencies: Object.keys(outputDict), - - // Access all dependencies by their expected keys - - // the composition runner actually provides a proxy - // and is checking that *we* access the dependencies - // we've specified. - compute: dependencies => - Object.fromEntries( - Object.keys(outputDict) - .map(key => [key, dependencies[key]])), - }, - ], - }); - - t.same( - composite.expose.compute(dependencies), - outputDict); - }; -} 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 deleted file mode 100644 index 4c9ee23f..00000000 --- a/test/lib/index.js +++ /dev/null @@ -1,7 +0,0 @@ -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'; diff --git a/test/lib/wiki-data.js b/test/lib/wiki-data.js deleted file mode 100644 index f0ee0ef5..00000000 --- a/test/lib/wiki-data.js +++ /dev/null @@ -1,156 +0,0 @@ -import CacheableObject from '#cacheable-object'; -import find, {bindFind} from '#find'; -import {bindReverse} from '#reverse'; -import {withEntries} from '#sugar'; -import Thing from '#thing'; -import thingConstructors from '#things'; -import {linkWikiDataArrays} from '#yaml'; - -export function linkAndBindWikiData(wikiData, { - inferAlbumsOwnTrackData = true, -} = {}) { - function customLinkWikiDataArrays(wikiData, options = {}) { - if (options.XXX_decacheWikiData) { - wikiData = - withEntries(wikiData, entries => entries - .map(([key, value]) => [key, value.slice()])); - } - - linkWikiDataArrays(wikiData, {bindFind, bindReverse}); - } - - customLinkWikiDataArrays(wikiData); - - return { - // Mutate to make the below functions aware of new data objects, or of - // reordering the existing ones. Don't mutate arrays such as trackData - // in-place; assign completely new arrays to this wikiData object instead. - wikiData, - - // Use this after you've mutated wikiData to assign new data arrays. - // It'll automatically relink everything on wikiData so all the objects - // are caught up to date. - linkWikiDataArrays: - customLinkWikiDataArrays - .bind(null, wikiData), - - // Use this if you HAVEN'T mutated wikiData and just need to decache - // indirect dependencies on exposed properties of other data objects. - // - // XXX_decacheWikiData option should be used specifically to mark points - // where you *aren't* replacing any of the arrays under wikiData with - // new values, and are using linkWikiDataArrays to instead "decache" data - // properties which depend on any of them. It's currently not possible for - // a CacheableObject to depend directly on the value of a property exposed - // on some other CacheableObject, so when those values change, you have to - // manually decache before the object will realize its cache isn't valid - // anymore. - // - // The previous implementation for this involved overwriting the relevant - // wikiData properties with null, then replacing it with the original - // array, which effectively cleared a CacheableObject cache. But it isn't - // enough to clear other caches that depend on the identity of wikiData - // arrays, such as withReverseReferenceList, so now it replaces with fresh - // copies of the data arrays instead; the original identities don't get - // reused. - XXX_decacheWikiData: - customLinkWikiDataArrays - .bind(null, wikiData, {XXX_decacheWikiData: true}), - }; -} - -export function stubWikiData() { - return { - albumData: [], - artistData: [], - artTagData: [], - flashData: [], - flashActData: [], - flashSideData: [], - groupData: [], - groupCategoryData: [], - newsData: [], - staticPageData: [], - trackData: [], - trackSectionData: [], - }; -} - -export function stubThing(wikiData, constructor, properties = {}) { - const thing = Reflect.construct(constructor, []); - Object.assign(thing, properties); - - const wikiDataSpec = { - Album: 'albumData', - Artist: 'artistData', - ArtTag: 'artTagData', - Flash: 'flashData', - FlashAct: 'flashActData', - FlashSide: 'flashSideData', - Group: 'groupData', - GroupCategory: 'groupCategoryData', - NewsEntry: 'newsData', - StaticPage: 'staticPageData', - Track: 'trackData', - TrackSection: 'trackSectionData', - }; - - const wikiDataMap = - new Map( - Object.entries(wikiDataSpec) - .map(([thingKey, wikiDataKey]) => [ - thingConstructors[thingKey], - wikiData[wikiDataKey], - ])); - - const wikiDataArray = - wikiDataMap.get(constructor); - - wikiDataArray.push(thing); - - return thing; -} - -export function stubTrackAndAlbum(wikiData, trackDirectory = null, albumDirectory = null) { - const {Track, TrackSection, Album} = thingConstructors; - - const track = - stubThing(wikiData, Track, {directory: trackDirectory}); - - const section = - stubThing(wikiData, TrackSection, {tracks: [track]}); - - const album = - stubThing(wikiData, Album, {directory: albumDirectory, trackSections: [section]}); - - return {track, album, section}; -} - -export function stubArtistAndContribs(wikiData, artistName = `Test Artist`) { - const {Artist} = thingConstructors; - - const artist = - stubThing(wikiData, Artist, {name: artistName}); - - const contribs = - [{artist: artistName, annotation: null}]; - - const badContribs = - [{artist: `Figment of Your Imagination`, annotation: null}]; - - return {artist, contribs, badContribs}; -} - -export function stubFlashAndAct(wikiData, flashDirectory = null) { - const {Flash, FlashAct} = thingConstructors; - - const flash = - stubThing(wikiData, Flash, {directory: flashDirectory}); - - const flashAct = - stubThing(wikiData, FlashAct, { - flashes: [Thing.getReference(flash)], - }); - - return {flash, flashAct}; -} diff --git a/test/lib/strict-match-error.js b/test/test-lib.js index e3b36e93..a12974cd 100644 --- a/test/lib/strict-match-error.js +++ b/test/test-lib.js @@ -1,3 +1,39 @@ +import {compositeFrom} from '#composite'; + +Error.stackTraceLimit = Infinity; + +export function quickCheckCompositeOutputs(t, dependencies) { + return (step, outputDict) => { + t.same( + Object.keys(step.toDescription().outputs), + Object.keys(outputDict)); + + const composite = compositeFrom({ + compose: false, + steps: [ + step, + + { + dependencies: Object.keys(outputDict), + + // Access all dependencies by their expected keys - + // the composition runner actually provides a proxy + // and is checking that *we* access the dependencies + // we've specified. + compute: dependencies => + Object.fromEntries( + Object.keys(outputDict) + .map(key => [key, dependencies[key]])), + }, + ], + }); + + t.same( + composite.expose.compute(dependencies), + outputDict); + }; +} + export function strictlyThrows(t, fn, pattern) { const error = catchErrorOrNull(fn); diff --git a/test/unit/content/dependencies/generateAlbumTrackList.js b/test/unit/content/dependencies/generateAlbumTrackList.js deleted file mode 100644 index 988f8505..00000000 --- a/test/unit/content/dependencies/generateAlbumTrackList.js +++ /dev/null @@ -1,43 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'generateAlbumTrackList (unit)', async (t, evaluate) => { - await evaluate.load({ - mock: { - generateAlbumTrackListItem: { - extraDependencies: ['html'], - data: track => track.name, - generate: (name, {html}) => - html.tag('li', `Item: ${name}`), - }, - - image: - evaluate.stubContentFunction('image'), - }, - }); - - let readDuration = false; - - const track = (name, duration) => ({ - name, - get duration() { - readDuration = true; - return duration; - }, - }); - - const tracks = [ - track('Track 1', 30), - track('Track 2', 15), - ]; - - evaluate({ - name: 'generateAlbumTrackList', - args: [{ - trackSections: [{isDefaultTrackSection: true, tracks}], - tracks, - }], - }); - - t.notOk(readDuration, 'expect no access to track.duration property'); -}); diff --git a/test/unit/content/dependencies/linkArtist.js b/test/unit/content/dependencies/linkArtist.js deleted file mode 100644 index e6e19d2f..00000000 --- a/test/unit/content/dependencies/linkArtist.js +++ /dev/null @@ -1,31 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -testContentFunctions(t, 'linkArtist (unit)', async (t, evaluate) => { - const artistObject = {}; - const linkTemplate = {}; - - await evaluate.load({ - mock: evaluate.mock(mock => ({ - linkThing: { - relations: mock.function('linkThing.relations', () => ({})) - .args([undefined, 'localized.artist', artistObject]) - .once(), - - data: mock.function('linkThing.data', () => ({})) - .args(['localized.artist', artistObject]) - .once(), - - generate: mock.function('linkThing.data', () => linkTemplate) - .once(), - } - })), - }); - - const result = evaluate({ - name: 'linkArtist', - args: [artistObject], - }); - - t.equal(result, linkTemplate); -}); diff --git a/test/unit/content/dependencies/linkContribution.js b/test/unit/content/dependencies/linkContribution.js deleted file mode 100644 index 1baa80f8..00000000 --- a/test/unit/content/dependencies/linkContribution.js +++ /dev/null @@ -1,145 +0,0 @@ -import t from 'tap'; -import {testContentFunctions} from '#test-lib'; - -t.test('linkContribution (unit)', async t => { - const artist1 = { - name: 'Clark Powell', - directory: 'clark-powell', - urls: ['https://soundcloud.com/plazmataz'], - }; - - const artist2 = { - name: 'Grounder & Scratch', - directory: 'the-big-baddies', - urls: [], - }; - - const artist3 = { - name: 'Toby Fox', - directory: 'toby-fox', - urls: ['https://tobyfox.bandcamp.com/', 'https://toby.fox/'], - }; - - const annotation1 = null; - const annotation2 = 'Snooping'; - const annotation3 = 'Arrangement'; - - const thing1 = {}; - const thing2 = {}; - const thing3 = {}; - - const contribution1 = {artist: artist1, annotation: annotation1, thing: thing1}; - const contribution2 = {artist: artist2, annotation: annotation2, thing: thing2}; - const contribution3 = {artist: artist3, annotation: annotation3, thing: thing3}; - - await testContentFunctions(t, 'linkContribution (unit 1)', async (t, evaluate) => { - const slots = { - showAnnotation: true, - showExternalLinks: true, - }; - - await evaluate.load({ - mock: evaluate.mock(mock => ({ - linkArtist: { - relations: mock - .function('linkArtist.relations', () => ({})) - .args([undefined, artist1]).next() - .args([undefined, artist2]).next() - .args([undefined, artist3]), - - data: mock - .function('linkArtist.data', () => ({})) - .args([artist1]).next() - .args([artist2]).next() - .args([artist3]), - - // This can be tweaked to return a specific (mocked) template - // for each artist if we need to test for slots in the future. - generate: mock.function('linkArtist.generate', () => 'artist link') - .repeat(3), - }, - - generateExternalIcon: { - data: mock - .function('generateExternalIcon.data', () => ({})) - .args([artist1.urls[0]]).next() - .args([artist3.urls[0]]).next() - .args([artist3.urls[1]]), - - generate: mock - .function('generateExternalIcon.generate', () => ({ - toString: () => 'icon', - setSlot: () => {}, - })) - .repeat(3), - } - })), - }); - - evaluate({ - name: 'linkContribution', - multiple: [ - {args: [contribution1]}, - {args: [contribution2]}, - {args: [contribution3]}, - ], - slots, - }); - }); - - await testContentFunctions(t, 'linkContribution (unit 2)', async (t, evaluate) => { - const slots = { - showAnnotation: false, - showExternalLinks: false, - }; - - await evaluate.load({ - mock: evaluate.mock(mock => ({ - linkArtist: { - relations: mock - .function('linkArtist.relations', () => ({})) - .args([undefined, artist1]).next() - .args([undefined, artist2]).next() - .args([undefined, artist3]), - - data: mock - .function('linkArtist.data', () => ({})) - .args([artist1]).next() - .args([artist2]).next() - .args([artist3]), - - generate: mock - .function(() => 'artist link') - .repeat(3), - }, - - // Even though icons are hidden, these are still called! The dependency - // tree is the same since whether or not the external icon links are - // shown is dependent on a slot, which is undefined and arbitrary at - // relations/data time (it might change on a whim at generate time). - generateExternalIcon: { - data: mock - .function('generateExternalIcon.data', () => ({})) - .repeat(3), - - generate: mock - .function('generateExternalIcon.generate', () => ({ - toString: () => 'icon', - setSlot: () => {}, - })) - .repeat(3), - }, - })), - }); - - evaluate({ - name: 'linkContribution', - multiple: [ - {args: [contribution1]}, - {args: [contribution2]}, - {args: [contribution3]}, - ], - slots, - }); - }); -}); diff --git a/test/unit/data/cacheable-object.js b/test/unit/data/cacheable-object.js index 4be31788..d0448025 100644 --- a/test/unit/data/cacheable-object.js +++ b/test/unit/data/cacheable-object.js @@ -26,8 +26,8 @@ t.test(`CacheableObject simple separate update & expose`, t => { }, expose: { - dependencies: ['number'], - compute: ({ number }) => number * 2 + dependencies: ['_number'], + compute: ({ _number: number }) => number * 2 } } }); @@ -53,8 +53,8 @@ t.test(`CacheableObject basic cache behavior`, t => { }, expose: { - dependencies: ['string'], - compute: ({ string }) => { + dependencies: ['_string'], + compute: ({ _string: string }) => { computeCount++; return string.toUpperCase(); } @@ -136,8 +136,8 @@ t.test(`CacheableObject combined update & expose (transform with dependency)`, t }, expose: { - dependencies: ['times'], - transform: (value, { times }) => value.repeat(times) + dependencies: ['_times'], + transform: (value, { _times: times }) => value.repeat(times) } }, diff --git a/test/unit/data/composite/control-flow/exposeConstant.js b/test/unit/data/composite/control-flow/exposeConstant.js deleted file mode 100644 index 0c75894b..00000000 --- a/test/unit/data/composite/control-flow/exposeConstant.js +++ /dev/null @@ -1,42 +0,0 @@ -import t from 'tap'; - -import {compositeFrom, continuationSymbol, input} from '#composite'; -import {exposeConstant} from '#composite/control-flow'; - -t.test(`exposeConstant: basic behavior`, t => { - t.plan(2); - - const composite1 = compositeFrom({ - compose: false, - - steps: [ - exposeConstant({ - value: input.value('foo'), - }), - ], - }); - - t.match(composite1, { - expose: { - dependencies: [], - }, - }); - - t.equal(composite1.expose.compute(), 'foo'); -}); - -t.test(`exposeConstant: validate inputs`, t => { - t.plan(2); - - t.throws( - () => exposeConstant({}), - {message: `Errors in input options passed to exposeConstant`, errors: [ - {message: `Required these inputs: value`}, - ]}); - - t.throws( - () => exposeConstant({value: 'some dependency'}), - {message: `Errors in input options passed to exposeConstant`, errors: [ - {message: `value: Expected input.value() call, got dependency name`}, - ]}); -}); diff --git a/test/unit/data/composite/control-flow/exposeDependency.js b/test/unit/data/composite/control-flow/exposeDependency.js deleted file mode 100644 index 8f6bfd01..00000000 --- a/test/unit/data/composite/control-flow/exposeDependency.js +++ /dev/null @@ -1,64 +0,0 @@ -import t from 'tap'; - -import {compositeFrom, continuationSymbol, input} from '#composite'; -import {exposeDependency} from '#composite/control-flow'; - -t.test(`exposeDependency: basic behavior`, t => { - t.plan(4); - - const composite1 = compositeFrom({ - compose: false, - - steps: [ - exposeDependency({dependency: 'foo'}), - ], - }); - - t.match(composite1, { - expose: { - dependencies: ['foo'], - }, - }); - - t.equal(composite1.expose.compute({foo: 'bar'}), 'bar'); - - const composite2 = compositeFrom({ - compose: false, - - steps: [ - { - dependencies: ['foo'], - compute: (continuation, {foo}) => - continuation({'#bar': foo.toUpperCase()}), - }, - - exposeDependency({dependency: '#bar'}), - ], - }); - - t.match(composite2, { - expose: { - dependencies: ['foo'], - }, - }); - - t.equal(composite2.expose.compute({foo: 'bar'}), 'BAR'); -}); - -t.test(`exposeDependency: validate inputs`, t => { - t.plan(2); - - t.throws( - () => exposeDependency({}), - {message: `Errors in input options passed to exposeDependency`, errors: [ - {message: `Required these inputs: dependency`}, - ]}); - - t.throws( - () => exposeDependency({ - dependency: input.value('some static value'), - }), - {message: `Errors in input options passed to exposeDependency`, errors: [ - {message: `dependency: Expected dependency name, got input.value() call`}, - ]}); -}); diff --git a/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js b/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js deleted file mode 100644 index 9d588e4c..00000000 --- a/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js +++ /dev/null @@ -1,197 +0,0 @@ -import t from 'tap'; - -import {compositeFrom, continuationSymbol, input} from '#composite'; -import {withResultOfAvailabilityCheck} from '#composite/control-flow'; - -const composite = compositeFrom({ - compose: false, - - steps: [ - withResultOfAvailabilityCheck({ - from: 'from', - mode: 'mode', - }).outputs({ - ['#availability']: '#result', - }), - - { - dependencies: ['#result'], - compute: ({'#result': result}) => result, - }, - ], -}); - -t.test(`withResultOfAvailabilityCheck: basic behavior`, t => { - t.plan(1); - - t.match(composite, { - expose: { - dependencies: ['from', 'mode'], - }, - }); -}); - -const quickCompare = (t, expect, {from, mode}) => - t.equal(composite.expose.compute({from, mode}), expect); - -const quickThrows = (t, {from, mode}) => - t.throws(() => composite.expose.compute({from, mode})); - -t.test(`withResultOfAvailabilityCheck: mode = null`, t => { - t.plan(11); - - quickCompare(t, true, {mode: 'null', from: 'truthy string'}); - quickCompare(t, true, {mode: 'null', from: 123}); - quickCompare(t, true, {mode: 'null', from: true}); - - quickCompare(t, true, {mode: 'null', from: ''}); - quickCompare(t, true, {mode: 'null', from: 0}); - quickCompare(t, true, {mode: 'null', from: -1}); - quickCompare(t, true, {mode: 'null', from: false}); - - quickCompare(t, true, {mode: 'null', from: [1, 2, 3]}); - quickCompare(t, true, {mode: 'null', from: []}); - - quickCompare(t, false, {mode: 'null', from: null}); - quickCompare(t, false, {mode: 'null', from: undefined}); -}); - -t.test(`withResultOfAvailabilityCheck: mode = empty`, t => { - t.plan(11); - - quickThrows(t, {mode: 'empty', from: 'truthy string'}); - quickThrows(t, {mode: 'empty', from: 123}); - quickThrows(t, {mode: 'empty', from: true}); - - quickThrows(t, {mode: 'empty', from: ''}); - quickThrows(t, {mode: 'empty', from: 0}); - quickThrows(t, {mode: 'empty', from: -1}); - quickThrows(t, {mode: 'empty', from: false}); - - quickCompare(t, true, {mode: 'empty', from: [1, 2, 3]}); - quickCompare(t, false, {mode: 'empty', from: []}); - - quickCompare(t, false, {mode: 'empty', from: null}); - quickCompare(t, false, {mode: 'empty', from: undefined}); -}); - -t.test(`withResultOfAvailabilityCheck: mode = falsy`, t => { - t.plan(11); - - quickCompare(t, true, {mode: 'falsy', from: 'truthy string'}); - quickCompare(t, true, {mode: 'falsy', from: 123}); - quickCompare(t, true, {mode: 'falsy', from: true}); - - quickCompare(t, false, {mode: 'falsy', from: ''}); - quickCompare(t, false, {mode: 'falsy', from: 0}); - quickCompare(t, true, {mode: 'falsy', from: -1}); - quickCompare(t, false, {mode: 'falsy', from: false}); - - quickCompare(t, true, {mode: 'falsy', from: [1, 2, 3]}); - quickCompare(t, false, {mode: 'falsy', from: []}); - - quickCompare(t, false, {mode: 'falsy', from: null}); - quickCompare(t, false, {mode: 'falsy', from: undefined}); -}); - -t.test(`withResultOfAvailabilityCheck: mode = index`, t => { - t.plan(11); - - quickCompare(t, false, {mode: 'index', from: 'truthy string'}); - quickCompare(t, true, {mode: 'index', from: 123}); - quickCompare(t, false, {mode: 'index', from: true}); - - quickCompare(t, false, {mode: 'index', from: ''}); - quickCompare(t, true, {mode: 'index', from: 0}); - quickCompare(t, false, {mode: 'index', from: -1}); - quickCompare(t, false, {mode: 'index', from: false}); - - quickCompare(t, false, {mode: 'index', from: [1, 2, 3]}); - quickCompare(t, false, {mode: 'index', from: []}); - - quickCompare(t, false, {mode: 'index', from: null}); - quickCompare(t, false, {mode: 'index', from: undefined}); -}); - -t.test(`withResultOfAvailabilityCheck: default mode`, t => { - t.plan(1); - - const template = withResultOfAvailabilityCheck({ - from: 'foo', - }); - - t.match(template.toDescription(), { - inputMapping: { - from: input.dependency('foo'), - mode: input.value('null'), - }, - }); -}); - -t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { - t.plan(5); - - t.throws( - () => withResultOfAvailabilityCheck({}), - {message: `Errors in input options passed to withResultOfAvailabilityCheck`, errors: [ - {message: `Required these inputs: from`}, - ]}); - - t.doesNotThrow(() => - withResultOfAvailabilityCheck({ - from: 'dependency1', - mode: 'dependency2', - })); - - t.doesNotThrow(() => - withResultOfAvailabilityCheck({ - from: input.value('some static value'), - mode: input.value('null'), - })); - - t.throws( - () => withResultOfAvailabilityCheck({ - from: 'foo', - mode: input.value('invalid'), - }), - {message: `Errors in input options passed to withResultOfAvailabilityCheck`, errors: [ - {message: `mode: Expected one of null empty falsy index, got invalid`}, - ]}); - - t.throws(() => - withResultOfAvailabilityCheck({ - from: input.value(null), - mode: input.value(null), - }), - {message: `Errors in input options passed to withResultOfAvailabilityCheck`, errors: [ - {message: `mode: Expected a value, got null`}, - ]}); -}); - -t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { - t.plan(2); - - t.throws( - () => composite.expose.compute({ - from: 'apple', - mode: 'banana', - }), - {message: `Error computing composition`, cause: - {message: `Error in step 1 of 2, withResultOfAvailabilityCheck`, cause: - {message: `Error computing composition withResultOfAvailabilityCheck`, cause: - {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [ - {message: `mode: Expected one of null empty falsy index, got banana`}, - ]}}}}); - - t.throws( - () => composite.expose.compute({ - from: null, - mode: null, - }), - {message: `Error computing composition`, cause: - {message: `Error in step 1 of 2, withResultOfAvailabilityCheck`, cause: - {message: `Error computing composition withResultOfAvailabilityCheck`, cause: - {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [ - {message: `mode: Expected a value, got null`}, - ]}}}}); -}); diff --git a/test/unit/data/composite/data/withPropertiesFromObject.js b/test/unit/data/composite/data/withPropertiesFromObject.js deleted file mode 100644 index b81d51a5..00000000 --- a/test/unit/data/composite/data/withPropertiesFromObject.js +++ /dev/null @@ -1,241 +0,0 @@ -import t from 'tap'; -import {quickCheckCompositeOutputs} from '#test-lib'; - -import {compositeFrom, input} from '#composite'; -import {exposeDependency} from '#composite/control-flow'; -import {withPropertiesFromObject} from '#composite/data'; - -const composite = compositeFrom({ - compose: false, - - steps: [ - withPropertiesFromObject({ - object: 'object', - properties: 'properties', - }), - - exposeDependency({dependency: '#object'}), - ], -}); - -t.test(`withPropertiesFromObject: basic behavior`, t => { - t.plan(4); - - t.match(composite, { - expose: { - dependencies: ['object', 'properties'], - }, - }); - - t.same( - composite.expose.compute({ - object: {foo: 'bar', bim: 'BOOM', bam: 'baz'}, - properties: ['foo', 'bim'], - }), - {foo: 'bar', bim: 'BOOM'}); - - t.same( - composite.expose.compute({ - object: {value1: 'uwah', value2: 'arah'}, - properties: ['value1', 'value3'], - }), - {value1: 'uwah', value3: null}); - - t.same( - composite.expose.compute({ - object: null, - properties: ['ohMe', 'ohMy', 'ohDear'], - }), - {ohMe: null, ohMy: null, ohDear: null}); -}); - -t.test(`withPropertiesFromObject: output shapes & values`, t => { - t.plan(2 * 2 * 3 ** 2); - - const dependencies = { - ['object_dependency']: - {foo: 'apple', bar: 'banana', baz: 'orange'}, - [input('object_neither')]: - {foo: 'koala', bar: 'okapi', baz: 'mongoose'}, - ['properties_dependency']: - ['foo', 'bar', 'missing1'], - [input('properties_neither')]: - ['foo', 'baz', 'missing3'], - }; - - const qcco = quickCheckCompositeOutputs(t, dependencies); - - const mapLevel1 = [ - [input.value('prefix_value'), [ - ['object_dependency', [ - ['properties_dependency', { - '#object': {foo: 'apple', bar: 'banana', missing1: null}, - }], - [input.value(['bar', 'baz', 'missing2']), { - '#prefix_value.bar': 'banana', - '#prefix_value.baz': 'orange', - '#prefix_value.missing2': null, - }], - [input('properties_neither'), { - '#object': {foo: 'apple', baz: 'orange', missing3: null}, - }]]], - - [input.value({foo: 'ouh', bar: 'rah', baz: 'nyu'}), [ - ['properties_dependency', { - '#object': {foo: 'ouh', bar: 'rah', missing1: null}, - }], - [input.value(['bar', 'baz', 'missing2']), { - '#prefix_value.bar': 'rah', - '#prefix_value.baz': 'nyu', - '#prefix_value.missing2': null, - }], - [input('properties_neither'), { - '#object': {foo: 'ouh', baz: 'nyu', missing3: null}, - }]]], - - [input('object_neither'), [ - ['properties_dependency', { - '#object': {foo: 'koala', bar: 'okapi', missing1: null}, - }], - [input.value(['bar', 'baz', 'missing2']), { - '#prefix_value.bar': 'okapi', - '#prefix_value.baz': 'mongoose', - '#prefix_value.missing2': null, - }], - [input('properties_neither'), { - '#object': {foo: 'koala', baz: 'mongoose', missing3: null}, - }]]]]], - - [input.value(null), [ - ['object_dependency', [ - ['properties_dependency', { - '#object': {foo: 'apple', bar: 'banana', missing1: null}, - }], - [input.value(['bar', 'baz', 'missing2']), { - '#object_dependency.bar': 'banana', - '#object_dependency.baz': 'orange', - '#object_dependency.missing2': null, - }], - [input('properties_neither'), { - '#object': {foo: 'apple', baz: 'orange', missing3: null}, - }]]], - - [input.value({foo: 'ouh', bar: 'rah', baz: 'nyu'}), [ - ['properties_dependency', { - '#object': {foo: 'ouh', bar: 'rah', missing1: null}, - }], - [input.value(['bar', 'baz', 'missing2']), { - '#object.bar': 'rah', - '#object.baz': 'nyu', - '#object.missing2': null, - }], - [input('properties_neither'), { - '#object': {foo: 'ouh', baz: 'nyu', missing3: null}, - }]]], - - [input('object_neither'), [ - ['properties_dependency', { - '#object': {foo: 'koala', bar: 'okapi', missing1: null}, - }], - [input.value(['bar', 'baz', 'missing2']), { - '#object.bar': 'okapi', - '#object.baz': 'mongoose', - '#object.missing2': null, - }], - [input('properties_neither'), { - '#object': {foo: 'koala', baz: 'mongoose', missing3: null}, - }]]]]], - ]; - - for (const [prefixInput, mapLevel2] of mapLevel1) { - for (const [objectInput, mapLevel3] of mapLevel2) { - for (const [propertiesInput, outputDict] of mapLevel3) { - const step = withPropertiesFromObject({ - prefix: prefixInput, - object: objectInput, - properties: propertiesInput, - }); - - qcco(step, outputDict); - } - } - } -}); - -t.test(`withPropertiesFromObject: validate static inputs`, t => { - t.plan(3); - - t.throws( - () => withPropertiesFromObject({}), - {message: `Errors in input options passed to withPropertiesFromObject`, errors: [ - {message: `Required these inputs: object, properties`}, - ]}); - - t.throws( - () => withPropertiesFromObject({ - object: input.value('intriguing'), - properties: input.value('very'), - prefix: input.value({yes: 'yup'}), - }), - {message: `Errors in input options passed to withPropertiesFromObject`, errors: [ - {message: `object: Expected an object, got string`}, - {message: `properties: Expected an array, got string`}, - {message: `prefix: Expected a string, got object`}, - ]}); - - t.throws( - () => withPropertiesFromObject({ - object: input.value([['abc', 1], ['def', 2], [123, 3]]), - properties: input.value(['abc', 'def', 123]), - }), - {message: `Errors in input options passed to withPropertiesFromObject`, errors: [ - {message: `object: Expected an object, got array`}, - {message: `properties: Errors validating array items`, errors: [ - { - [Symbol.for('hsmusic.annotateError.indexInSourceArray')]: 2, - message: `Error at zero-index 2: 123`, - cause: { - message: `Expected a string, got number`, - }, - }, - ]}, - ]}); -}); - -t.test(`withPropertiesFromObject: validate dynamic inputs`, t => { - t.plan(2); - - t.throws( - () => composite.expose.compute({ - object: 'intriguing', - properties: 'onceMore', - }), - {message: `Error computing composition`, cause: - {message: `Error in step 1 of 2, withPropertiesFromObject`, cause: - {message: `Error computing composition withPropertiesFromObject`, cause: - {message: `Errors in input values provided to withPropertiesFromObject`, errors: [ - {message: `object: Expected an object, got string`}, - {message: `properties: Expected an array, got string`}, - ]}}}}); - - t.throws( - () => composite.expose.compute({ - object: [['abc', 1], ['def', 2], [123, 3]], - properties: ['abc', 'def', 123], - }), - {message: `Error computing composition`, cause: - {message: `Error in step 1 of 2, withPropertiesFromObject`, cause: - {message: `Error computing composition withPropertiesFromObject`, cause: - {message: `Errors in input values provided to withPropertiesFromObject`, errors: [ - {message: `object: Expected an object, got array`}, - {message: `properties: Errors validating array items`, errors: [ - { - [Symbol.for('hsmusic.annotateError.indexInSourceArray')]: 2, - message: `Error at zero-index 2: 123`, - cause: { - message: `Expected a string, got number`, - }, - }, - ]}, - ]}}}}); -}); diff --git a/test/unit/data/composite/data/withPropertyFromObject.js b/test/unit/data/composite/data/withPropertyFromObject.js deleted file mode 100644 index 068932e2..00000000 --- a/test/unit/data/composite/data/withPropertyFromObject.js +++ /dev/null @@ -1,195 +0,0 @@ -import t from 'tap'; -import {quickCheckCompositeOutputs} from '#test-lib'; - -import CacheableObject from '#cacheable-object'; -import {compositeFrom, input} from '#composite'; -import {exposeDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; - -t.test(`withPropertyFromObject: basic behavior`, t => { - t.plan(4); - - const composite = compositeFrom({ - compose: false, - - steps: [ - withPropertyFromObject({ - object: 'object', - property: 'property', - }), - - exposeDependency({dependency: '#value'}), - ], - }); - - t.match(composite, { - expose: { - dependencies: ['object', 'property'], - }, - }); - - t.equal(composite.expose.compute({ - object: {foo: 'bar', bim: 'BOOM'}, - property: 'bim', - }), 'BOOM'); - - t.equal(composite.expose.compute({ - object: {value1: 'uwah'}, - property: 'value2', - }), null); - - t.equal(composite.expose.compute({ - object: null, - property: 'oml where did me object go', - }), null); -}); - -t.test(`withPropertyFromObject: "internal" input`, t => { - t.plan(7); - - const composite = compositeFrom({ - compose: false, - - steps: [ - withPropertyFromObject({ - object: 'object', - property: 'property', - internal: 'internal', - }), - - exposeDependency({dependency: '#value'}), - ], - }); - - const constructor = class extends CacheableObject { - static [CacheableObject.propertyDescriptors] = { - foo: { - flags: {update: true, expose: false}, - }, - - bar: { - flags: {update: true, expose: true}, - }, - - baz: { - flags: {update: true, expose: true}, - expose: { - transform: baz => baz * 2, - }, - }, - }; - }; - - constructor.finalizeCacheableObjectPrototype(); - - const thing = Reflect.construct(constructor, []); - - thing.foo = 100; - thing.bar = 200; - thing.baz = 300; - - t.match(composite, { - expose: { - dependencies: ['object', 'property', 'internal'], - }, - }); - - t.equal(composite.expose.compute({ - object: thing, - property: 'foo', - internal: true, - }), 100); - - t.equal(composite.expose.compute({ - object: thing, - property: 'bar', - internal: true, - }), 200); - - t.equal(composite.expose.compute({ - object: thing, - property: 'baz', - internal: true, - }), 300); - - t.equal(composite.expose.compute({ - object: thing, - property: 'baz', - internal: false, - }), 600); - - t.equal(composite.expose.compute({ - object: thing, - property: 'bimbam', - internal: false, - }), null); - - t.equal(composite.expose.compute({ - object: null, - property: 'bambim', - internal: false, - }), null); -}); - -t.test(`withPropertyFromObject: output shapes & values`, t => { - t.plan(2 * 3 ** 2); - - const dependencies = { - ['object_dependency']: - {foo: 'apple', bar: 'banana', baz: 'orange'}, - [input('object_neither')]: - {foo: 'koala', bar: 'okapi', baz: 'mongoose'}, - ['property_dependency']: - 'foo', - [input('property_neither')]: - 'baz', - }; - - const qcco = quickCheckCompositeOutputs(t, dependencies); - - const mapLevel1 = [ - ['object_dependency', [ - ['property_dependency', { - '#value': 'apple', - }], - [input.value('bar'), { - '#object_dependency.bar': 'banana', - }], - [input('property_neither'), { - '#value': 'orange', - }]]], - - [input.value({foo: 'ouh', bar: 'rah', baz: 'nyu'}), [ - ['property_dependency', { - '#value': 'ouh', - }], - [input.value('bar'), { - '#value': 'rah', - }], - [input('property_neither'), { - '#value': 'nyu', - }]]], - - [input('object_neither'), [ - ['property_dependency', { - '#value': 'koala', - }], - [input.value('bar'), { - '#value': 'okapi', - }], - [input('property_neither'), { - '#value': 'mongoose', - }]]], - ]; - - for (const [objectInput, mapLevel2] of mapLevel1) { - for (const [propertyInput, outputDict] of mapLevel2) { - const step = withPropertyFromObject({ - object: objectInput, - property: propertyInput, - }); - - qcco(step, outputDict); - } - } -}); diff --git a/test/unit/data/composite/data/withUniqueItemsOnly.js b/test/unit/data/composite/data/withUniqueItemsOnly.js deleted file mode 100644 index 50b16f43..00000000 --- a/test/unit/data/composite/data/withUniqueItemsOnly.js +++ /dev/null @@ -1,69 +0,0 @@ -import t from 'tap'; -import {quickCheckCompositeOutputs} from '#test-lib'; - -import {compositeFrom, input} from '#composite'; -import {exposeDependency} from '#composite/control-flow'; -import {withUniqueItemsOnly} from '#composite/data'; - -t.test(`withUniqueItemsOnly: basic behavior`, t => { - t.plan(3); - - const composite = compositeFrom({ - compose: false, - - steps: [ - withUniqueItemsOnly({ - list: 'list', - }), - - exposeDependency({dependency: '#list'}), - ], - }); - - t.match(composite, { - expose: { - dependencies: ['list'], - }, - }); - - t.same(composite.expose.compute({ - list: ['apple', 'banana', 'banana', 'banana', 'apple', 'watermelon'], - }), ['apple', 'banana', 'watermelon']); - - t.same(composite.expose.compute({ - list: [], - }), []); -}); - -t.test(`withUniqueItemsOnly: output shapes & values`, t => { - t.plan(2 * 3 ** 1); - - const dependencies = { - ['list_dependency']: - [1, 1, 2, 3, 3, 4, 'foo', false, false, 4], - [input('list_neither')]: - [8, 8, 7, 6, 6, 5, 'bar', true, true, 5], - }; - - const qcco = quickCheckCompositeOutputs(t, dependencies); - - const mapLevel1 = [ - ['list_dependency', { - '#list_dependency': [1, 2, 3, 4, 'foo', false], - }], - [input.value([-1, -1, 'interesting', 'very', 'interesting']), { - '#uniqueItems': [-1, 'interesting', 'very'], - }], - [input('list_neither'), { - '#uniqueItems': [8, 7, 6, 5, 'bar', true], - }], - ]; - - for (const [listInput, outputDict] of mapLevel1) { - const step = withUniqueItemsOnly({ - list: listInput, - }); - - qcco(step, outputDict); - } -}); diff --git a/test/unit/data/validators.js b/test/unit/data/validators.js index 02f94866..6e37f170 100644 --- a/test/unit/data/validators.js +++ b/test/unit/data/validators.js @@ -17,7 +17,6 @@ import { // Wiki data isColor, - isCommentary, isContentString, isContribution, isContributionList, @@ -152,21 +151,6 @@ t.test('isColor', t => { t.throws(() => isColor('hsl(150deg 30% 60%)'), TypeError); }); -t.test('isCommentary', t => { - t.plan(9); - - // TODO: Test specific error messages. - t.ok(isCommentary(`<i>Toby Fox:</i>\ndogsong.mp3`)); - t.ok(isCommentary(`<i>Toby Fox:</i> (music)\ndogsong.mp3`)); - t.throws(() => isCommentary(`dogsong.mp3\n<i>Toby Fox:</i>\ndogsong.mp3`)); - t.throws(() => isCommentary(`<i>Toby Fox:</i> dogsong.mp3`)); - t.throws(() => isCommentary(`<i>Toby Fox:</i> (music) dogsong.mp3`)); - t.throws(() => isCommentary(`<i>I Have Nothing To Say:</i>`)); - t.throws(() => isCommentary(123)); - t.throws(() => isCommentary(``)); - t.throws(() => isCommentary(`Technically, ah, er:</i>\nCorrect`)); -}); - t.test('isContentString', t => { t.plan(12); |