From 029210cc329a015a939472a688209d3f3423242b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 30 May 2023 09:51:26 -0300 Subject: thumbs, content: integrate cached thumb sizes into content --- test/lib/content-function.js | 7 +++++++ test/snapshot/image.js | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/lib/content-function.js b/test/lib/content-function.js index bb12be82..cd86e9bc 100644 --- a/test/lib/content-function.js +++ b/test/lib/content-function.js @@ -49,8 +49,15 @@ export function testContentFunctions(t, message, fn) { thumb, to, urls, + appendIndexHTML: false, + getColors: c => getColors(c, {chroma}), + getDimensionsOfImagePath: () => [600, 600], + getThumbnailEqualOrSmaller: () => 'medium', + getThumbnailsAvailableForDimensions: () => + [['large', 800], ['medium', 400], ['small', 250]], + ...extraDependencies, }, }); diff --git a/test/snapshot/image.js b/test/snapshot/image.js index 6bec1cca..5e12cc25 100644 --- a/test/snapshot/image.js +++ b/test/snapshot/image.js @@ -8,7 +8,7 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { evaluate.snapshot(message, { name: 'image', extraDependencies: { - getSizeOfImageFile: () => 0, + getSizeOfImagePath: () => 0, }, ...opts, }); @@ -79,7 +79,7 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { quickSnapshot('link with file size', { extraDependencies: { - getSizeOfImageFile: () => 10 ** 6, + getSizeOfImagePath: () => 10 ** 6, }, slots: { path: ['media.albumCover', 'pingas', 'png'], -- cgit 1.3.0-6-gf8a5 From 145a4a292dea7dccb2bb9b58a760db32948c3918 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 30 May 2023 10:01:00 -0300 Subject: test: snapshot how thumbnail details are exposed in images --- test/snapshot/image.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'test') diff --git a/test/snapshot/image.js b/test/snapshot/image.js index 5e12cc25..a8796e11 100644 --- a/test/snapshot/image.js +++ b/test/snapshot/image.js @@ -98,4 +98,19 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { path: ['media.albumCover', 'beyond-canon', 'png'], }, }); + + evaluate.snapshot('thumbnail details', { + name: 'image', + extraDependencies: { + getSizeOfImagePath: () => 0, + getDimensionsOfImagePath: () => [900, 1200], + getThumbnailsAvailableForDimensions: () => + [['voluminous', 1200], ['middling', 900], ['petite', 20]], + getThumbnailEqualOrSmaller: () => 'voluminous', + }, + slots: { + thumb: 'gargantuan', + path: ['media.albumCover', 'beyond-canon', 'png'], + }, + }); }); -- cgit 1.3.0-6-gf8a5 From 8dd2a2fd71fe0e1643201aff87acda8bbcc41295 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 21 Aug 2023 09:37:17 -0300 Subject: test: move thumb-related utilities into image.js snapshot --- test/lib/content-function.js | 7 ------- test/snapshot/image.js | 4 ++++ 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/test/lib/content-function.js b/test/lib/content-function.js index cd86e9bc..bb12be82 100644 --- a/test/lib/content-function.js +++ b/test/lib/content-function.js @@ -49,15 +49,8 @@ export function testContentFunctions(t, message, fn) { thumb, to, urls, - appendIndexHTML: false, - getColors: c => getColors(c, {chroma}), - getDimensionsOfImagePath: () => [600, 600], - getThumbnailEqualOrSmaller: () => 'medium', - getThumbnailsAvailableForDimensions: () => - [['large', 800], ['medium', 400], ['small', 250]], - ...extraDependencies, }, }); diff --git a/test/snapshot/image.js b/test/snapshot/image.js index a8796e11..48dcfb69 100644 --- a/test/snapshot/image.js +++ b/test/snapshot/image.js @@ -9,6 +9,10 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { name: 'image', extraDependencies: { getSizeOfImagePath: () => 0, + getDimensionsOfImagePath: () => [600, 600], + getThumbnailEqualOrSmaller: () => 'medium', + getThumbnailsAvailableForDimensions: () => + [['large', 800], ['medium', 400], ['small', 250]], }, ...opts, }); -- cgit 1.3.0-6-gf8a5 From d194fc4f537ee79b0558b54ff2e1fdc3e9cbf4d9 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 4 Sep 2023 20:48:54 -0300 Subject: test: update & fix-up image snapshot tests --- test/snapshot/image.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/snapshot/image.js b/test/snapshot/image.js index 48dcfb69..8608ab69 100644 --- a/test/snapshot/image.js +++ b/test/snapshot/image.js @@ -4,15 +4,17 @@ import {testContentFunctions} from '#test-lib'; testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { await evaluate.load(); - const quickSnapshot = (message, opts) => + const quickSnapshot = (message, {extraDependencies, ...opts}) => evaluate.snapshot(message, { name: 'image', extraDependencies: { + checkIfImagePathHasCachedThumbnails: path => !path.endsWith('.gif'), getSizeOfImagePath: () => 0, getDimensionsOfImagePath: () => [600, 600], getThumbnailEqualOrSmaller: () => 'medium', getThumbnailsAvailableForDimensions: () => [['large', 800], ['medium', 400], ['small', 250]], + ...extraDependencies, }, ...opts, }); @@ -106,6 +108,7 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { evaluate.snapshot('thumbnail details', { name: 'image', extraDependencies: { + checkIfImagePathHasCachedThumbnails: () => true, getSizeOfImagePath: () => 0, getDimensionsOfImagePath: () => [900, 1200], getThumbnailsAvailableForDimensions: () => @@ -117,4 +120,11 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { path: ['media.albumCover', 'beyond-canon', 'png'], }, }); + + quickSnapshot('thumb requested but source is gif', { + slots: { + thumb: 'medium', + path: ['media.flashArt', '5426', 'gif'], + }, + }); }); -- cgit 1.3.0-6-gf8a5 From ddaf34d6e97269719107398569716fc1f1e98073 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 5 Sep 2023 19:03:44 -0300 Subject: infra, test: cleaner output for stubTemplate --- test/lib/content-function.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/lib/content-function.js b/test/lib/content-function.js index bb12be82..b706cd8c 100644 --- a/test/lib/content-function.js +++ b/test/lib/content-function.js @@ -1,5 +1,6 @@ import * as path from 'node:path'; import {fileURLToPath} from 'node:url'; +import {inspect} from 'node:util'; import chroma from 'chroma-js'; @@ -99,7 +100,7 @@ export function testContentFunctions(t, message, fn) { constructor() { super({ - content: () => `${name}: ${JSON.stringify(this.#slotValues)}`, + content: () => this.#getContent(this), }); } @@ -110,6 +111,24 @@ export function testContentFunctions(t, message, fn) { setSlot(slotName, slotValue) { this.#slotValues[slotName] = slotValue; } + + #getContent() { + const toInspect = + Object.fromEntries( + Object.entries(this.#slotValues) + .filter(([key, value]) => value !== null)); + + const inspected = + inspect(toInspect, { + breakLength: Infinity, + colors: false, + compact: true, + depth: Infinity, + sort: true, + }); + + return `${name}: ${inspected}`; + } }); }; -- cgit 1.3.0-6-gf8a5 From ae1131e54280da63a678eb3489b02a9fb292ee8b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 5 Sep 2023 19:39:51 -0300 Subject: infra, test: new stubContentFunction utility Just like stubTemplate, but the result is ready for passing to evaluate.load's {mock} option, and the template's content is formatted to include the content function's provided arguments as well. --- test/lib/content-function.js | 109 +++++++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 31 deletions(-) (limited to 'test') diff --git a/test/lib/content-function.js b/test/lib/content-function.js index b706cd8c..5cb499b1 100644 --- a/test/lib/content-function.js +++ b/test/lib/content-function.js @@ -91,45 +91,92 @@ export function testContentFunctions(t, message, fn) { t.matchSnapshot(result, description); }; - evaluate.stubTemplate = name => { + 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); - return new (class extends html.Template { - #slotValues = {}; + 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); - constructor() { - super({ - content: () => this.#getContent(this), - }); - } - - setSlots(slotNamesToValues) { - Object.assign(this.#slotValues, slotNamesToValues); - } + const _stubTemplate = (name, mockContentFunction) => { + const inspectNicely = (value, opts = {}) => + inspect(value, { + ...opts, + colors: false, + sort: true, + }); - setSlot(slotName, slotValue) { - this.#slotValues[slotName] = slotValue; - } + const makeTemplate = formatContentFn => + new (class extends html.Template { + #slotValues = {}; - #getContent() { - const toInspect = - Object.fromEntries( - Object.entries(this.#slotValues) - .filter(([key, value]) => value !== null)); - - const inspected = - inspect(toInspect, { - breakLength: Infinity, - colors: false, - compact: true, - depth: Infinity, - sort: true, + constructor() { + super({ + content: () => this.#getContent(formatContentFn), }); + } - return `${name}: ${inspected}`; - } - }); + 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) => { -- cgit 1.3.0-6-gf8a5 From de0b85d81e6392597a35196bde523b9642d7e016 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 5 Sep 2023 20:09:03 -0300 Subject: test: update snapshot tests to always mock image dependency --- test/snapshot/generateAlbumCoverArtwork.js | 14 +++++++------- test/snapshot/generateCoverArtwork.js | 12 +++++------- test/snapshot/generateTrackCoverArtwork.js | 14 +++++--------- test/snapshot/transformContent.js | 17 +++++++++++------ 4 files changed, 28 insertions(+), 29 deletions(-) (limited to 'test') diff --git a/test/snapshot/generateAlbumCoverArtwork.js b/test/snapshot/generateAlbumCoverArtwork.js index 98632d39..b1c7885f 100644 --- a/test/snapshot/generateAlbumCoverArtwork.js +++ b/test/snapshot/generateAlbumCoverArtwork.js @@ -1,12 +1,14 @@ import t from 'tap'; + +import contentFunction from '#content-function'; import {testContentFunctions} from '#test-lib'; testContentFunctions(t, 'generateAlbumCoverArtwork (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - const extraDependencies = { - getSizeOfImageFile: () => 0, - }; + await evaluate.load({ + mock: { + image: evaluate.stubContentFunction('image'), + }, + }); const album = { directory: 'bee-forus-seatbelt-safebee', @@ -23,13 +25,11 @@ testContentFunctions(t, 'generateAlbumCoverArtwork (snapshot)', async (t, evalua name: 'generateAlbumCoverArtwork', args: [album], slots: {mode: 'primary'}, - extraDependencies, }); evaluate.snapshot('display: thumbnail', { name: 'generateAlbumCoverArtwork', args: [album], slots: {mode: 'thumbnail'}, - extraDependencies, }); }); diff --git a/test/snapshot/generateCoverArtwork.js b/test/snapshot/generateCoverArtwork.js index 21c91454..e35dd8d0 100644 --- a/test/snapshot/generateCoverArtwork.js +++ b/test/snapshot/generateCoverArtwork.js @@ -2,11 +2,11 @@ import t from 'tap'; import {testContentFunctions} from '#test-lib'; testContentFunctions(t, 'generateCoverArtwork (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - const extraDependencies = { - getSizeOfImageFile: () => 0, - }; + await evaluate.load({ + mock: { + image: evaluate.stubContentFunction('image', {mock: true}), + }, + }); const artTags = [ {name: 'Damara', directory: 'damara', isContentWarning: false}, @@ -21,13 +21,11 @@ testContentFunctions(t, 'generateCoverArtwork (snapshot)', async (t, evaluate) = name: 'generateCoverArtwork', args: [artTags], slots: {path, mode: 'primary'}, - extraDependencies, }); evaluate.snapshot('display: thumbnail', { name: 'generateCoverArtwork', args: [artTags], slots: {path, mode: 'thumbnail'}, - extraDependencies, }); }); diff --git a/test/snapshot/generateTrackCoverArtwork.js b/test/snapshot/generateTrackCoverArtwork.js index 9e154703..03a181e7 100644 --- a/test/snapshot/generateTrackCoverArtwork.js +++ b/test/snapshot/generateTrackCoverArtwork.js @@ -2,11 +2,11 @@ import t from 'tap'; import {testContentFunctions} from '#test-lib'; testContentFunctions(t, 'generateTrackCoverArtwork (snapshot)', async (t, evaluate) => { - await evaluate.load(); - - const extraDependencies = { - getSizeOfImageFile: () => 0, - }; + await evaluate.load({ + mock: { + image: evaluate.stubContentFunction('image'), + }, + }); const album = { directory: 'bee-forus-seatbelt-safebee', @@ -37,27 +37,23 @@ testContentFunctions(t, 'generateTrackCoverArtwork (snapshot)', async (t, evalua name: 'generateTrackCoverArtwork', args: [track1], slots: {mode: 'primary'}, - extraDependencies, }); evaluate.snapshot('display: thumbnail - unique art', { name: 'generateTrackCoverArtwork', args: [track1], slots: {mode: 'thumbnail'}, - extraDependencies, }); evaluate.snapshot('display: primary - no unique art', { name: 'generateTrackCoverArtwork', args: [track2], slots: {mode: 'primary'}, - extraDependencies, }); evaluate.snapshot('display: thumbnail - no unique art', { name: 'generateTrackCoverArtwork', args: [track2], slots: {mode: 'thumbnail'}, - extraDependencies, }); }); diff --git a/test/snapshot/transformContent.js b/test/snapshot/transformContent.js index 25952856..b05beac1 100644 --- a/test/snapshot/transformContent.js +++ b/test/snapshot/transformContent.js @@ -2,7 +2,11 @@ import t from 'tap'; import {testContentFunctions} from '#test-lib'; testContentFunctions(t, 'transformContent (snapshot)', async (t, evaluate) => { - await evaluate.load(); + await evaluate.load({ + mock: { + image: evaluate.stubContentFunction('image'), + }, + }); const extraDependencies = { wikiData: { @@ -11,8 +15,6 @@ testContentFunctions(t, 'transformContent (snapshot)', async (t, evaluate) => { ], }, - getSizeOfImageFile: () => 0, - to: (key, ...args) => `to-${key}/${args.join('/')}`, }; @@ -50,15 +52,18 @@ testContentFunctions(t, 'transformContent (snapshot)', async (t, evaluate) => { quickSnapshot( 'non-inline image #2', - `Rad.\n`); + `Rad.\n` + + ``); quickSnapshot( 'non-inline image #3', - `\nBaller.`); + `\n` + + `Baller.`); quickSnapshot( 'dates', - `[[date:2023-04-13]] Yep!\nVery nice: [[date:25 October 2413]]`); + `[[date:2023-04-13]] Yep!\n` + + `Very nice: [[date:25 October 2413]]`); quickSnapshot( 'super basic string', -- cgit 1.3.0-6-gf8a5 From aea700dc531b5183ad20c6fcbf8643aef3f102df Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 5 Sep 2023 20:24:08 -0300 Subject: test: fix & update generateAlbumSecondaryNav snapshot test --- test/snapshot/generateAlbumSecondaryNav.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/snapshot/generateAlbumSecondaryNav.js b/test/snapshot/generateAlbumSecondaryNav.js index a5cb2e91..709b062e 100644 --- a/test/snapshot/generateAlbumSecondaryNav.js +++ b/test/snapshot/generateAlbumSecondaryNav.js @@ -6,8 +6,8 @@ testContentFunctions(t, 'generateAlbumSecondaryNav (snapshot)', async (t, evalua let album, group1, group2; - group1 = {name: 'VCG', directory: 'vcg'}; - group2 = {name: 'Bepis', directory: 'bepis'}; + group1 = {name: 'VCG', directory: 'vcg', color: '#abcdef'}; + group2 = {name: 'Bepis', directory: 'bepis', color: '#123456'}; album = { date: new Date('2010-04-13'), -- cgit 1.3.0-6-gf8a5 From 218a99a3164e8ae6967335190b72fd36275d1892 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 21 Aug 2023 10:58:55 -0300 Subject: data, test: track: inherit album props more declaratively --- test/unit/data/things/track.js | 55 +++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 22 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 383e3e3f..d0e50c7f 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -3,6 +3,7 @@ import thingConstructors from '#things'; const { Album, + Artist, Thing, Track, TrackGroup, @@ -20,55 +21,65 @@ function stubAlbum(tracks) { } t.test(`Track.coverArtDate`, t => { - t.plan(5); + t.plan(6); - // Priority order is as follows, with the last (trackCoverArtDate) being - // greatest priority. - const albumDate = new Date('2010-10-10'); const albumTrackArtDate = new Date('2012-12-12'); - const trackDateFirstReleased = new Date('2008-08-08'); const trackCoverArtDate = new Date('2009-09-09'); + const dummyContribs = [{who: 'Test Artist', what: null}] const track = new Track(); track.directory = 'foo'; + track.coverArtistContribsByRef = dummyContribs; const album = stubAlbum([track]); + const artist = new Artist(); + artist.name = 'Test Artist'; + track.albumData = [album]; + track.artistData = [artist]; + + const XXX_CLEAR_TRACK_ALBUM_CACHE = () => { + // XXX clear cache so change in album's property is reflected + track.albumData = []; + track.albumData = [album]; + }; // 1. coverArtDate defaults to null t.equal(track.coverArtDate, null); - // 2. coverArtDate inherits album release date + // 2. coverArtDate inherits album trackArtDate - album.date = albumDate; + album.trackArtDate = albumTrackArtDate; - // XXX clear cache so change in album's property is reflected - track.albumData = []; - track.albumData = [album]; + XXX_CLEAR_TRACK_ALBUM_CACHE(); - t.equal(track.coverArtDate, albumDate); + t.equal(track.coverArtDate, albumTrackArtDate); - // 3. coverArtDate inherits album trackArtDate + // 3. coverArtDate is own value - album.trackArtDate = albumTrackArtDate; + track.coverArtDate = trackCoverArtDate; - // XXX clear cache again - track.albumData = []; - track.albumData = [album]; + t.equal(track.coverArtDate, trackCoverArtDate); - t.equal(track.coverArtDate, albumTrackArtDate); + // 4. coverArtDate is null if track is missing coverArtists - // 4. coverArtDate is overridden dateFirstReleased + track.coverArtistContribsByRef = []; - track.dateFirstReleased = trackDateFirstReleased; + t.equal(track.coverArtDate, null); - t.equal(track.coverArtDate, trackDateFirstReleased); + // 5. coverArtDate is not null if album specifies trackCoverArtistContribs - // 5. coverArtDate is overridden coverArtDate + album.trackCoverArtistContribsByRef = dummyContribs; - track.coverArtDate = trackCoverArtDate; + XXX_CLEAR_TRACK_ALBUM_CACHE(); t.equal(track.coverArtDate, trackCoverArtDate); + + // 6. coverArtDate is null if track disables unique cover artwork + + track.disableUniqueCoverArt = true; + + t.equal(track.coverArtDate, null); }); -- cgit 1.3.0-6-gf8a5 From 13914b9f07f60d6d8aaaddc7df675d41950320c3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 23 Aug 2023 12:22:34 -0300 Subject: test: Track.{color,date,hasUniqueCoverArt} (unit) --- test/unit/data/things/track.js | 157 ++++++++++++++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 32 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index d0e50c7f..37aa7b9f 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -20,66 +20,159 @@ function stubAlbum(tracks) { return album; } -t.test(`Track.coverArtDate`, t => { - t.plan(6); - - const albumTrackArtDate = new Date('2012-12-12'); - const trackCoverArtDate = new Date('2009-09-09'); - const dummyContribs = [{who: 'Test Artist', what: null}] - +function stubTrack() { const track = new Track(); track.directory = 'foo'; - track.coverArtistContribsByRef = dummyContribs; + return track; +} +function stubTrackAndAlbum() { + const track = stubTrack(); const album = stubAlbum([track]); + track.albumData = [album]; + return {track, album}; +} +function stubArtistAndContribs() { const artist = new Artist(); - artist.name = 'Test Artist'; + artist.name = `Test Artist`; + + const contribs = [{who: `Test Artist`, what: null}]; + const badContribs = [{who: `Figment of Your Imagination`, what: null}]; + return {artist, contribs, badContribs}; +} +function XXX_CLEAR_TRACK_ALBUM_CACHE(track, album) { + // XXX clear cache so change in album's property is reflected + track.albumData = []; track.albumData = [album]; - track.artistData = [artist]; +} - const XXX_CLEAR_TRACK_ALBUM_CACHE = () => { - // XXX clear cache so change in album's property is reflected - track.albumData = []; - track.albumData = [album]; - }; +t.test(`Track.color`, t => { + t.plan(3); - // 1. coverArtDate defaults to null + const {track, album} = stubTrackAndAlbum(); - t.equal(track.coverArtDate, null); + t.equal(track.color, null, + `color #1: defaults to null`); - // 2. coverArtDate inherits album trackArtDate + album.color = '#abcdef'; + XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); - album.trackArtDate = albumTrackArtDate; + t.equal(track.color, '#abcdef', + `color #2: inherits from album`); - XXX_CLEAR_TRACK_ALBUM_CACHE(); + track.color = '#123456'; - t.equal(track.coverArtDate, albumTrackArtDate); + t.equal(track.color, '#123456', + `color #3: is own value`); +}); - // 3. coverArtDate is own value +t.test(`Track.coverArtDate`, t => { + t.plan(6); + + const {track, album} = stubTrackAndAlbum(); + const {artist, contribs} = stubArtistAndContribs(); + + track.coverArtistContribsByRef = contribs; + track.artistData = [artist]; + + t.equal(track.coverArtDate, null, + `coverArtDate #1: defaults to null`); - track.coverArtDate = trackCoverArtDate; + album.trackArtDate = new Date('2012-12-12'); + XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); - t.equal(track.coverArtDate, trackCoverArtDate); + t.same(track.coverArtDate, new Date('2012-12-12'), + `coverArtDate #2: inherits album trackArtDate`); - // 4. coverArtDate is null if track is missing coverArtists + track.coverArtDate = new Date('2009-09-09'); + + t.same(track.coverArtDate, new Date('2009-09-09'), + `coverArtDate #3: is own value`); track.coverArtistContribsByRef = []; - t.equal(track.coverArtDate, null); + t.equal(track.coverArtDate, null, + `coverArtDate #4: is null if track is missing coverArtists`); + + album.trackCoverArtistContribsByRef = contribs; + XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + + t.same(track.coverArtDate, new Date('2009-09-09'), + `coverArtDate #5: is not null if album specifies trackCoverArtistContribs`); + + track.disableUniqueCoverArt = true; + + t.equal(track.coverArtDate, null, + `coverArtDate #6: is null if track disables unique cover artwork`); +}); + +t.test(`Track.date`, t => { + t.plan(3); + + const {track, album} = stubTrackAndAlbum(); + + t.equal(track.date, null, + `date #1: defaults to null`); + + album.date = new Date('2012-12-12'); + XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); - // 5. coverArtDate is not null if album specifies trackCoverArtistContribs + t.same(track.date, album.date, + `date #2: inherits from album`); - album.trackCoverArtistContribsByRef = dummyContribs; + track.dateFirstReleased = new Date('2009-09-09'); + + t.same(track.date, new Date('2009-09-09'), + `date #3: is own dateFirstReleased`); +}); + +t.test(`Track.hasUniqueCoverArt`, t => { + t.plan(7); + + const {track, album} = stubTrackAndAlbum(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + track.artistData = [artist]; + album.artistData = [artist]; - XXX_CLEAR_TRACK_ALBUM_CACHE(); + t.equal(track.hasUniqueCoverArt, false, + `hasUniqueCoverArt #1: defaults to false`); - t.equal(track.coverArtDate, trackCoverArtDate); + album.trackCoverArtistContribsByRef = contribs; + XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); - // 6. coverArtDate is null if track disables unique cover artwork + t.equal(track.hasUniqueCoverArt, true, + `hasUniqueCoverArt #2: is true if album specifies trackCoverArtistContribs`); track.disableUniqueCoverArt = true; - t.equal(track.coverArtDate, null); + t.equal(track.hasUniqueCoverArt, false, + `hasUniqueCoverArt #3: is false if disableUniqueCoverArt is true (1/2)`); + + track.disableUniqueCoverArt = false; + + album.trackCoverArtistContribsByRef = badContribs; + XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + + t.equal(track.hasUniqueCoverArt, false, + `hasUniqueCoverArt #4: is false if album's trackCoverArtistContribsByRef resolve empty`); + + track.coverArtistContribsByRef = contribs; + + t.equal(track.hasUniqueCoverArt, true, + `hasUniqueCoverArt #5: is true if track specifies coverArtistContribs`); + + track.disableUniqueCoverArt = true; + + t.equal(track.hasUniqueCoverArt, false, + `hasUniqueCoverArt #6: is false if disableUniqueCoverArt is true (2/2)`); + + track.disableUniqueCoverArt = false; + + track.coverArtistContribsByRef = badContribs; + + t.equal(track.hasUniqueCoverArt, false, + `hasUniqueCoverArt #7: is false if track's coverArtistContribsByRef resolve empty`); }); -- cgit 1.3.0-6-gf8a5 From 087564095ff06fed25a0c21fab01ed9d849937d0 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 25 Aug 2023 13:30:08 -0300 Subject: test: Track.otherReleases (unit) --- test/unit/data/things/track.js | 76 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 37aa7b9f..9d4ba2c3 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -20,14 +20,14 @@ function stubAlbum(tracks) { return album; } -function stubTrack() { +function stubTrack(directory = 'foo') { const track = new Track(); - track.directory = 'foo'; + track.directory = directory; return track; } -function stubTrackAndAlbum() { - const track = stubTrack(); +function stubTrackAndAlbum(trackDirectory = 'foo') { + const track = stubTrack(trackDirectory); const album = stubAlbum([track]); track.albumData = [album]; return {track, album}; @@ -42,10 +42,30 @@ function stubArtistAndContribs() { return {artist, contribs, badContribs}; } +function assignWikiData({ + XXX_DECACHE = false, + albumData, + trackData, +}) { + for (const album of albumData ?? []) { + if (XXX_DECACHE) { + album.trackData = []; + } + album.trackData = trackData ?? []; + } + + for (const track of trackData ?? []) { + if (XXX_DECACHE) { + track.albumData = []; + track.trackData = []; + } + track.albumData = albumData ?? []; + track.trackData = trackData ?? []; + } +} + function XXX_CLEAR_TRACK_ALBUM_CACHE(track, album) { - // XXX clear cache so change in album's property is reflected - track.albumData = []; - track.albumData = [album]; + assignWikiData({XXX_DECACHE: true, albumData: [album], trackData: [track]}); } t.test(`Track.color`, t => { @@ -176,3 +196,45 @@ t.test(`Track.hasUniqueCoverArt`, t => { t.equal(track.hasUniqueCoverArt, false, `hasUniqueCoverArt #7: is false if track's coverArtistContribsByRef resolve empty`); }); + +t.test(`Track.otherReleases`, t => { + t.plan(6); + + const {track: track1, album: album1} = stubTrackAndAlbum('track1'); + const {track: track2, album: album2} = stubTrackAndAlbum('track2'); + const {track: track3, album: album3} = stubTrackAndAlbum('track3'); + const {track: track4, album: album4} = stubTrackAndAlbum('track4'); + + let trackData = [track1, track2, track3, track4]; + let albumData = [album1, album2, album3, album4]; + assignWikiData({trackData, albumData}); + + t.same(track1.otherReleases, [], + `otherReleases #1: defaults to empty array`); + + track2.originalReleaseTrackByRef = 'track:track1'; + track3.originalReleaseTrackByRef = 'track:track1'; + track4.originalReleaseTrackByRef = 'track:track1'; + assignWikiData({trackData, albumData, XXX_DECACHE: true}); + + t.same(track1.otherReleases, [track2, track3, track4], + `otherReleases #2: otherReleases of original release are its rereleases`); + + trackData = [track1, track3, track2, track4]; + assignWikiData({trackData, albumData}); + + t.same(track1.otherReleases, [track3, track2, track4], + `otherReleases #3: otherReleases matches trackData order`); + + trackData = [track3, track2, track1, track4]; + assignWikiData({trackData, albumData}); + + t.same(track2.otherReleases, [track1, track3, track4], + `otherReleases #4: otherReleases of rerelease are original track then other rereleases (1/3)`); + + t.same(track3.otherReleases, [track1, track2, track4], + `otherReleases #5: otherReleases of rerelease are original track then other rereleases (2/3)`); + + t.same(track4.otherReleases, [track1, track3, track2], + `otherReleases #6: otherReleases of rerelease are original track then other rereleases (1/3)`); +}); -- cgit 1.3.0-6-gf8a5 From f562896d4d67558a32726f7086beebf29019a44d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 25 Aug 2023 14:06:00 -0300 Subject: yaml, test: mutate/decache wikiData in more reusable ways --- test/lib/index.js | 1 + test/lib/wiki-data.js | 24 ++++++++++ test/unit/data/things/track.js | 105 ++++++++++++++++++++--------------------- 3 files changed, 76 insertions(+), 54 deletions(-) create mode 100644 test/lib/wiki-data.js (limited to 'test') diff --git a/test/lib/index.js b/test/lib/index.js index b9cc82f8..6eaaa656 100644 --- a/test/lib/index.js +++ b/test/lib/index.js @@ -1,3 +1,4 @@ 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 new file mode 100644 index 00000000..c4083a56 --- /dev/null +++ b/test/lib/wiki-data.js @@ -0,0 +1,24 @@ +import {linkWikiDataArrays} from '#yaml'; + +export function linkAndBindWikiData(wikiData) { + linkWikiDataArrays(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: + linkWikiDataArrays.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. + // See documentation on linkWikiDataArarys (in yaml.js) for more info. + XXX_decacheWikiData: + linkWikiDataArrays.bind(null, wikiData, {XXX_decacheWikiData: true}), + }; +} diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 9d4ba2c3..218353c8 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -1,4 +1,6 @@ import t from 'tap'; + +import {linkAndBindWikiData} from '#test-lib'; import thingConstructors from '#things'; const { @@ -6,30 +8,28 @@ const { Artist, Thing, Track, - TrackGroup, } = thingConstructors; -function stubAlbum(tracks) { +function stubAlbum(tracks, directory = 'bar') { const album = new Album(); - album.trackSections = [ - { - tracksByRef: tracks.map(t => Thing.getReference(t)), - }, - ]; - album.trackData = tracks; + album.directory = directory; + + const tracksByRef = tracks.map(t => Thing.getReference(t)); + album.trackSections = [{tracksByRef}]; + return album; } function stubTrack(directory = 'foo') { const track = new Track(); track.directory = directory; + return track; } -function stubTrackAndAlbum(trackDirectory = 'foo') { +function stubTrackAndAlbum(trackDirectory = 'foo', albumDirectory = 'bar') { const track = stubTrack(trackDirectory); - const album = stubAlbum([track]); - track.albumData = [album]; + const album = stubAlbum([track], albumDirectory); return {track, album}; } @@ -39,33 +39,8 @@ function stubArtistAndContribs() { const contribs = [{who: `Test Artist`, what: null}]; const badContribs = [{who: `Figment of Your Imagination`, what: null}]; - return {artist, contribs, badContribs}; -} - -function assignWikiData({ - XXX_DECACHE = false, - albumData, - trackData, -}) { - for (const album of albumData ?? []) { - if (XXX_DECACHE) { - album.trackData = []; - } - album.trackData = trackData ?? []; - } - - for (const track of trackData ?? []) { - if (XXX_DECACHE) { - track.albumData = []; - track.trackData = []; - } - track.albumData = albumData ?? []; - track.trackData = trackData ?? []; - } -} -function XXX_CLEAR_TRACK_ALBUM_CACHE(track, album) { - assignWikiData({XXX_DECACHE: true, albumData: [album], trackData: [track]}); + return {artist, contribs, badContribs}; } t.test(`Track.color`, t => { @@ -73,11 +48,16 @@ t.test(`Track.color`, t => { const {track, album} = stubTrackAndAlbum(); + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album], + trackData: [track], + }); + t.equal(track.color, null, `color #1: defaults to null`); album.color = '#abcdef'; - XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + XXX_decacheWikiData(); t.equal(track.color, '#abcdef', `color #2: inherits from album`); @@ -94,14 +74,20 @@ t.test(`Track.coverArtDate`, t => { const {track, album} = stubTrackAndAlbum(); const {artist, contribs} = stubArtistAndContribs(); + const {XXX_decacheWikiData} = linkAndBindWikiData({ + trackData: [track], + albumData: [album], + artistData: [artist], + }); + track.coverArtistContribsByRef = contribs; - track.artistData = [artist]; t.equal(track.coverArtDate, null, `coverArtDate #1: defaults to null`); album.trackArtDate = new Date('2012-12-12'); - XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + + XXX_decacheWikiData(); t.same(track.coverArtDate, new Date('2012-12-12'), `coverArtDate #2: inherits album trackArtDate`); @@ -117,7 +103,8 @@ t.test(`Track.coverArtDate`, t => { `coverArtDate #4: is null if track is missing coverArtists`); album.trackCoverArtistContribsByRef = contribs; - XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + + XXX_decacheWikiData(); t.same(track.coverArtDate, new Date('2009-09-09'), `coverArtDate #5: is not null if album specifies trackCoverArtistContribs`); @@ -133,11 +120,16 @@ t.test(`Track.date`, t => { const {track, album} = stubTrackAndAlbum(); + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album], + trackData: [track], + }); + t.equal(track.date, null, `date #1: defaults to null`); album.date = new Date('2012-12-12'); - XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + XXX_decacheWikiData(); t.same(track.date, album.date, `date #2: inherits from album`); @@ -154,14 +146,17 @@ t.test(`Track.hasUniqueCoverArt`, t => { const {track, album} = stubTrackAndAlbum(); const {artist, contribs, badContribs} = stubArtistAndContribs(); - track.artistData = [artist]; - album.artistData = [artist]; + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + trackData: [track], + }); t.equal(track.hasUniqueCoverArt, false, `hasUniqueCoverArt #1: defaults to false`); album.trackCoverArtistContribsByRef = contribs; - XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + XXX_decacheWikiData(); t.equal(track.hasUniqueCoverArt, true, `hasUniqueCoverArt #2: is true if album specifies trackCoverArtistContribs`); @@ -174,7 +169,7 @@ t.test(`Track.hasUniqueCoverArt`, t => { track.disableUniqueCoverArt = false; album.trackCoverArtistContribsByRef = badContribs; - XXX_CLEAR_TRACK_ALBUM_CACHE(track, album); + XXX_decacheWikiData(); t.equal(track.hasUniqueCoverArt, false, `hasUniqueCoverArt #4: is false if album's trackCoverArtistContribsByRef resolve empty`); @@ -205,9 +200,10 @@ t.test(`Track.otherReleases`, t => { const {track: track3, album: album3} = stubTrackAndAlbum('track3'); const {track: track4, album: album4} = stubTrackAndAlbum('track4'); - let trackData = [track1, track2, track3, track4]; - let albumData = [album1, album2, album3, album4]; - assignWikiData({trackData, albumData}); + const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({ + trackData: [track1, track2, track3, track4], + albumData: [album1, album2, album3, album4], + }); t.same(track1.otherReleases, [], `otherReleases #1: defaults to empty array`); @@ -215,19 +211,20 @@ t.test(`Track.otherReleases`, t => { track2.originalReleaseTrackByRef = 'track:track1'; track3.originalReleaseTrackByRef = 'track:track1'; track4.originalReleaseTrackByRef = 'track:track1'; - assignWikiData({trackData, albumData, XXX_DECACHE: true}); + + XXX_decacheWikiData(); t.same(track1.otherReleases, [track2, track3, track4], `otherReleases #2: otherReleases of original release are its rereleases`); - trackData = [track1, track3, track2, track4]; - assignWikiData({trackData, albumData}); + wikiData.trackData = [track1, track3, track2, track4]; + linkWikiDataArrays(); t.same(track1.otherReleases, [track3, track2, track4], `otherReleases #3: otherReleases matches trackData order`); - trackData = [track3, track2, track1, track4]; - assignWikiData({trackData, albumData}); + wikiData.trackData = [track3, track2, track1, track4]; + linkWikiDataArrays(); t.same(track2.otherReleases, [track1, track3, track4], `otherReleases #4: otherReleases of rerelease are original track then other rereleases (1/3)`); -- cgit 1.3.0-6-gf8a5 From 25beb8731d756bfa4fe6babb9e4b0a707c7823e0 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 26 Aug 2023 19:22:38 -0300 Subject: data, test: misc. additions * Thing.composite.expose * Thing.composite.exposeUpdateValueOrContinue * Track.composite.withAlbumProperty * refactor: Track.color, Track.album, Track.date * refactor: Track.coverArtistContribs * test: Track.album (unit) --- test/unit/data/things/track.js | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 218353c8..08e91732 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -43,6 +43,59 @@ function stubArtistAndContribs() { return {artist, contribs, badContribs}; } +t.test(`Track.album`, t => { + t.plan(6); + + // Note: These asserts use manual albumData/trackData relationships + // to illustrate more specifically the properties which are expected to + // be relevant for this case. Other properties use the same underlying + // get-album behavior as Track.album so aren't tested as aggressively. + + const track1 = stubTrack('track1'); + const track2 = stubTrack('track2'); + const album1 = new Album(); + const album2 = new Album(); + + t.equal(track1.album, null, + `album #1: defaults to null`); + + track1.albumData = [album1, album2]; + track2.albumData = [album1, album2]; + album1.trackData = [track1, track2]; + album2.trackData = [track1, track2]; + album1.trackSections = [{tracksByRef: ['track:track1']}]; + album2.trackSections = [{tracksByRef: ['track:track2']}]; + + t.equal(track1.album, album1, + `album #2: is album when album's trackSections matches track`); + + track1.albumData = [album2, album1]; + + t.equal(track1.album, album1, + `album #3: is album when albumData is in different order`); + + track1.albumData = []; + + t.equal(track1.album, null, + `album #4: is null when track missing albumData`); + + album1.trackData = []; + track1.albumData = [album1, album2]; + + t.equal(track1.album, null, + `album #5: is null when album missing trackData`); + + album1.trackData = [track1, track2]; + album1.trackSections = [{tracksByRef: ['track:track2']}]; + + // XXX_decacheWikiData + track1.albumData = []; + track1.albumData = [album1, album2]; + + t.equal(track1.album, null, + `album #6: is null when album's trackSections don't match track`); +}); + t.test(`Track.color`, t => { t.plan(3); -- cgit 1.3.0-6-gf8a5 From 12b8040b05e81a523ef59ba583dde751206f2e1d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 26 Aug 2023 20:38:27 -0300 Subject: data, test: retain validator for Track.color --- test/unit/data/things/track.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 08e91732..dbc8434f 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -97,7 +97,7 @@ t.test(`Track.album`, t => { }); t.test(`Track.color`, t => { - t.plan(3); + t.plan(4); const {track, album} = stubTrackAndAlbum(); @@ -119,6 +119,9 @@ t.test(`Track.color`, t => { t.equal(track.color, '#123456', `color #3: is own value`); + + t.throws(() => { track.color = '#aeiouw'; }, TypeError, + `color #4: must be set to valid color`); }); t.test(`Track.coverArtDate`, t => { -- cgit 1.3.0-6-gf8a5 From e6038d8c07971447f444cf597328ca8d9863f8fd Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 26 Aug 2023 21:18:43 -0300 Subject: data, test: Track.color inherits from track section --- test/unit/data/things/track.js | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index dbc8434f..9132376c 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -97,11 +97,11 @@ t.test(`Track.album`, t => { }); t.test(`Track.color`, t => { - t.plan(4); + t.plan(5); const {track, album} = stubTrackAndAlbum(); - const {XXX_decacheWikiData} = linkAndBindWikiData({ + const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({ albumData: [album], trackData: [track], }); @@ -110,18 +110,42 @@ t.test(`Track.color`, t => { `color #1: defaults to null`); album.color = '#abcdef'; + album.trackSections = [{ + color: '#beeeef', + tracksByRef: [Thing.getReference(track)], + }]; XXX_decacheWikiData(); + t.equal(track.color, '#beeeef', + `color #2: inherits from track section before album`); + + // Replace the album with a completely fake one. This isn't realistic, since + // in correct data, Album.tracks depends on Albums.trackSections and so the + // track's album will always have a corresponding track section. But if that + // connection breaks for some future reason (with the album still present), + // Track.color should still inherit directly from the album. + wikiData.albumData = [ + new Proxy({ + color: '#abcdef', + tracks: [track], + trackSections: [ + {color: '#baaaad', tracks: []}, + ], + }, {getPrototypeOf: () => Album.prototype}), + ]; + + linkWikiDataArrays(); + t.equal(track.color, '#abcdef', - `color #2: inherits from album`); + `color #3: inherits from album without matching track section`); track.color = '#123456'; t.equal(track.color, '#123456', - `color #3: is own value`); + `color #4: is own value`); t.throws(() => { track.color = '#aeiouw'; }, TypeError, - `color #4: must be set to valid color`); + `color #5: must be set to valid color`); }); t.test(`Track.coverArtDate`, t => { -- cgit 1.3.0-6-gf8a5 From f874ea879e8b9555baaaa3a38ec6a00432721846 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 31 Aug 2023 11:00:07 -0300 Subject: data, test: update & test Track.originalReleaseTrack --- test/unit/data/things/track.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 9132376c..97d664ee 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -272,6 +272,31 @@ t.test(`Track.hasUniqueCoverArt`, t => { `hasUniqueCoverArt #7: is false if track's coverArtistContribsByRef resolve empty`); }); +t.only(`Track.originalReleaseTrack`, t => { + t.plan(3); + + const {track: track1, album: album1} = stubTrackAndAlbum('track1'); + const {track: track2, album: album2} = stubTrackAndAlbum('track2'); + + const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({ + trackData: [track1, track2], + albumData: [album1, album2], + }); + + t.equal(track2.originalReleaseTrack, null, + `originalReleaseTrack #1: defaults to null`); + + track2.originalReleaseTrackByRef = 'track:track1'; + + t.equal(track2.originalReleaseTrack, track1, + `originalReleaseTrack #2: is resolved from originalReleaseTrackByRef`); + + track2.trackData = []; + + t.equal(track2.originalReleaseTrack, null, + `originalReleaseTrack #3: is null when track missing trackData`); +}); + t.test(`Track.otherReleases`, t => { t.plan(6); -- cgit 1.3.0-6-gf8a5 From 6f54b1211b5b07fe747ce4ebafdf917ce7851324 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 31 Aug 2023 19:22:54 -0300 Subject: test: Track.coverArtFileExtension (unit) --- test/unit/data/things/track.js | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 97d664ee..16162fb7 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -195,6 +195,66 @@ t.test(`Track.coverArtDate`, t => { `coverArtDate #6: is null if track disables unique cover artwork`); }); +t.test(`Track.coverArtFileExtension`, t => { + t.plan(8); + + const {track, album} = stubTrackAndAlbum(); + const {artist, contribs} = stubArtistAndContribs(); + + const {XXX_decacheWikiData} = linkAndBindWikiData({ + trackData: [track], + albumData: [album], + artistData: [artist], + }); + + t.equal(track.coverArtFileExtension, null, + `coverArtFileExtension #1: defaults to null`); + + track.coverArtistContribsByRef = contribs; + + t.equal(track.coverArtFileExtension, 'jpg', + `coverArtFileExtension #2: is jpg if has cover art and not further specified`); + + track.coverArtistContribsByRef = []; + + album.coverArtistContribsByRef = contribs; + XXX_decacheWikiData(); + + t.equal(track.coverArtFileExtension, null, + `coverArtFileExtension #3: only has value for unique cover art`); + + track.coverArtistContribsByRef = contribs; + + album.trackCoverArtFileExtension = 'png'; + XXX_decacheWikiData(); + + t.equal(track.coverArtFileExtension, 'png', + `coverArtFileExtension #4: inherits album trackCoverArtFileExtension (1/2)`); + + track.coverArtFileExtension = 'gif'; + + t.equal(track.coverArtFileExtension, 'gif', + `coverArtFileExtension #5: is own value (1/2)`); + + track.coverArtistContribsByRef = []; + + album.trackCoverArtistContribsByRef = contribs; + XXX_decacheWikiData(); + + t.equal(track.coverArtFileExtension, 'gif', + `coverArtFileExtension #6: is own value (2/2)`); + + track.coverArtFileExtension = null; + + t.equal(track.coverArtFileExtension, 'png', + `coverArtFileExtension #7: inherits album trackCoverArtFileExtension (2/2)`); + + track.disableUniqueCoverArt = true; + + t.equal(track.coverArtFileExtension, null, + `coverArtFileExtension #8: is null if track disables unique cover art`); +}); + t.test(`Track.date`, t => { t.plan(3); -- cgit 1.3.0-6-gf8a5 From 6325a70991396412eb8e93cee5f17bdb2859ae9d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 31 Aug 2023 19:52:42 -0300 Subject: data, test: update & test misc. Track reverse reference lists * update & test Track.referencedByTracks * update & test Track.sampledByTracks * update & test Track.featuredInFlashes * update Thing.common.reverseReferenceList * add Thing.composite.withReverseReferenceList * add Track.composite.trackReverseReferenceList --- test/unit/data/things/track.js | 119 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 16162fb7..8939d964 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -6,6 +6,8 @@ import thingConstructors from '#things'; const { Album, Artist, + Flash, + FlashAct, Thing, Track, } = thingConstructors; @@ -43,6 +45,16 @@ function stubArtistAndContribs() { return {artist, contribs, badContribs}; } +function stubFlashAndAct(directory = 'zam') { + const flash = new Flash(); + flash.directory = directory; + + const flashAct = new FlashAct(); + flashAct.flashesByRef = [Thing.getReference(flash)]; + + return {flash, flashAct}; +} + t.test(`Track.album`, t => { t.plan(6); @@ -155,9 +167,9 @@ t.test(`Track.coverArtDate`, t => { const {artist, contribs} = stubArtistAndContribs(); const {XXX_decacheWikiData} = linkAndBindWikiData({ - trackData: [track], albumData: [album], artistData: [artist], + trackData: [track], }); track.coverArtistContribsByRef = contribs; @@ -202,9 +214,9 @@ t.test(`Track.coverArtFileExtension`, t => { const {artist, contribs} = stubArtistAndContribs(); const {XXX_decacheWikiData} = linkAndBindWikiData({ - trackData: [track], albumData: [album], artistData: [artist], + trackData: [track], }); t.equal(track.coverArtFileExtension, null, @@ -280,6 +292,32 @@ t.test(`Track.date`, t => { `date #3: is own dateFirstReleased`); }); +t.test(`Track.featuredInFlashes`, t => { + t.plan(2); + + const {track, album} = stubTrackAndAlbum('track1'); + + const {flash: flash1, flashAct: flashAct1} = stubFlashAndAct('flash1'); + const {flash: flash2, flashAct: flashAct2} = stubFlashAndAct('flash2'); + + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album], + trackData: [track], + flashData: [flash1, flash2], + flashActData: [flashAct1, flashAct2], + }); + + t.same(track.featuredInFlashes, [], + `featuredInFlashes #1: defaults to empty array`); + + flash1.featuredTracksByRef = ['track:track1']; + flash2.featuredTracksByRef = ['track:track1']; + XXX_decacheWikiData(); + + t.same(track.featuredInFlashes, [flash1, flash2], + `featuredInFlashes #2: matches flashes' featuredTracks`); +}); + t.test(`Track.hasUniqueCoverArt`, t => { t.plan(7); @@ -339,8 +377,8 @@ t.only(`Track.originalReleaseTrack`, t => { const {track: track2, album: album2} = stubTrackAndAlbum('track2'); const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({ - trackData: [track1, track2], albumData: [album1, album2], + trackData: [track1, track2], }); t.equal(track2.originalReleaseTrack, null, @@ -366,8 +404,8 @@ t.test(`Track.otherReleases`, t => { const {track: track4, album: album4} = stubTrackAndAlbum('track4'); const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({ - trackData: [track1, track2, track3, track4], albumData: [album1, album2, album3, album4], + trackData: [track1, track2, track3, track4], }); t.same(track1.otherReleases, [], @@ -376,7 +414,6 @@ t.test(`Track.otherReleases`, t => { track2.originalReleaseTrackByRef = 'track:track1'; track3.originalReleaseTrackByRef = 'track:track1'; track4.originalReleaseTrackByRef = 'track:track1'; - XXX_decacheWikiData(); t.same(track1.otherReleases, [track2, track3, track4], @@ -400,3 +437,75 @@ t.test(`Track.otherReleases`, t => { t.same(track4.otherReleases, [track1, track3, track2], `otherReleases #6: otherReleases of rerelease are original track then other rereleases (1/3)`); }); + +t.test(`Track.referencedByTracks`, t => { + t.plan(4); + + const {track: track1, album: album1} = stubTrackAndAlbum('track1'); + const {track: track2, album: album2} = stubTrackAndAlbum('track2'); + const {track: track3, album: album3} = stubTrackAndAlbum('track3'); + const {track: track4, album: album4} = stubTrackAndAlbum('track4'); + + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album1, album2, album3, album4], + trackData: [track1, track2, track3, track4], + }); + + t.same(track1.referencedByTracks, [], + `referencedByTracks #1: defaults to empty array`); + + track2.referencedTracksByRef = ['track:track1']; + track3.referencedTracksByRef = ['track:track1']; + XXX_decacheWikiData(); + + t.same(track1.referencedByTracks, [track2, track3], + `referencedByTracks #2: matches tracks' referencedTracks`); + + track4.sampledTracksByRef = ['track:track1']; + XXX_decacheWikiData(); + + t.same(track1.referencedByTracks, [track2, track3], + `referencedByTracks #3: doesn't match tracks' sampledTracks`); + + track3.originalReleaseTrackByRef = 'track:track2'; + XXX_decacheWikiData(); + + t.same(track1.referencedByTracks, [track2], + `referencedByTracks #4: doesn't include re-releases`); +}); + +t.test(`Track.sampledByTracks`, t => { + t.plan(4); + + const {track: track1, album: album1} = stubTrackAndAlbum('track1'); + const {track: track2, album: album2} = stubTrackAndAlbum('track2'); + const {track: track3, album: album3} = stubTrackAndAlbum('track3'); + const {track: track4, album: album4} = stubTrackAndAlbum('track4'); + + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album1, album2, album3, album4], + trackData: [track1, track2, track3, track4], + }); + + t.same(track1.sampledByTracks, [], + `sampledByTracks #1: defaults to empty array`); + + track2.sampledTracksByRef = ['track:track1']; + track3.sampledTracksByRef = ['track:track1']; + XXX_decacheWikiData(); + + t.same(track1.sampledByTracks, [track2, track3], + `sampledByTracks #2: matches tracks' sampledTracks`); + + track4.referencedTracksByRef = ['track:track1']; + XXX_decacheWikiData(); + + t.same(track1.sampledByTracks, [track2, track3], + `sampledByTracks #3: doesn't match tracks' referencedTracks`); + + track3.originalReleaseTrackByRef = 'track:track2'; + XXX_decacheWikiData(); + + t.same(track1.sampledByTracks, [track2], + `sampledByTracks #4: doesn't include re-releases`); +}); -- cgit 1.3.0-6-gf8a5 From bbccaf51222cb4bed73466164496f5bc1030292c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 7 Sep 2023 17:30:54 -0300 Subject: data: roll paired "byRef" and "dynamic" properties into one --- test/unit/data/things/track.js | 76 +++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 8939d964..bb8d7079 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -16,8 +16,8 @@ function stubAlbum(tracks, directory = 'bar') { const album = new Album(); album.directory = directory; - const tracksByRef = tracks.map(t => Thing.getReference(t)); - album.trackSections = [{tracksByRef}]; + const trackRefs = tracks.map(t => Thing.getReference(t)); + album.trackSections = [{tracks: trackRefs}]; return album; } @@ -50,7 +50,7 @@ function stubFlashAndAct(directory = 'zam') { flash.directory = directory; const flashAct = new FlashAct(); - flashAct.flashesByRef = [Thing.getReference(flash)]; + flashAct.flashes = [Thing.getReference(flash)]; return {flash, flashAct}; } @@ -75,8 +75,8 @@ t.test(`Track.album`, t => { track2.albumData = [album1, album2]; album1.trackData = [track1, track2]; album2.trackData = [track1, track2]; - album1.trackSections = [{tracksByRef: ['track:track1']}]; - album2.trackSections = [{tracksByRef: ['track:track2']}]; + album1.trackSections = [{tracks: ['track:track1']}]; + album2.trackSections = [{tracks: ['track:track2']}]; t.equal(track1.album, album1, `album #2: is album when album's trackSections matches track`); @@ -98,7 +98,7 @@ t.test(`Track.album`, t => { `album #5: is null when album missing trackData`); album1.trackData = [track1, track2]; - album1.trackSections = [{tracksByRef: ['track:track2']}]; + album1.trackSections = [{tracks: ['track:track2']}]; // XXX_decacheWikiData track1.albumData = []; @@ -124,7 +124,7 @@ t.test(`Track.color`, t => { album.color = '#abcdef'; album.trackSections = [{ color: '#beeeef', - tracksByRef: [Thing.getReference(track)], + tracks: [Thing.getReference(track)], }]; XXX_decacheWikiData(); @@ -172,7 +172,7 @@ t.test(`Track.coverArtDate`, t => { trackData: [track], }); - track.coverArtistContribsByRef = contribs; + track.coverArtistContribs = contribs; t.equal(track.coverArtDate, null, `coverArtDate #1: defaults to null`); @@ -189,12 +189,12 @@ t.test(`Track.coverArtDate`, t => { t.same(track.coverArtDate, new Date('2009-09-09'), `coverArtDate #3: is own value`); - track.coverArtistContribsByRef = []; + track.coverArtistContribs = []; t.equal(track.coverArtDate, null, `coverArtDate #4: is null if track is missing coverArtists`); - album.trackCoverArtistContribsByRef = contribs; + album.trackCoverArtistContribs = contribs; XXX_decacheWikiData(); @@ -222,20 +222,20 @@ t.test(`Track.coverArtFileExtension`, t => { t.equal(track.coverArtFileExtension, null, `coverArtFileExtension #1: defaults to null`); - track.coverArtistContribsByRef = contribs; + track.coverArtistContribs = contribs; t.equal(track.coverArtFileExtension, 'jpg', `coverArtFileExtension #2: is jpg if has cover art and not further specified`); - track.coverArtistContribsByRef = []; + track.coverArtistContribs = []; - album.coverArtistContribsByRef = contribs; + album.coverArtistContribs = contribs; XXX_decacheWikiData(); t.equal(track.coverArtFileExtension, null, `coverArtFileExtension #3: only has value for unique cover art`); - track.coverArtistContribsByRef = contribs; + track.coverArtistContribs = contribs; album.trackCoverArtFileExtension = 'png'; XXX_decacheWikiData(); @@ -248,9 +248,9 @@ t.test(`Track.coverArtFileExtension`, t => { t.equal(track.coverArtFileExtension, 'gif', `coverArtFileExtension #5: is own value (1/2)`); - track.coverArtistContribsByRef = []; + track.coverArtistContribs = []; - album.trackCoverArtistContribsByRef = contribs; + album.trackCoverArtistContribs = contribs; XXX_decacheWikiData(); t.equal(track.coverArtFileExtension, 'gif', @@ -310,8 +310,8 @@ t.test(`Track.featuredInFlashes`, t => { t.same(track.featuredInFlashes, [], `featuredInFlashes #1: defaults to empty array`); - flash1.featuredTracksByRef = ['track:track1']; - flash2.featuredTracksByRef = ['track:track1']; + flash1.featuredTracks = ['track:track1']; + flash2.featuredTracks = ['track:track1']; XXX_decacheWikiData(); t.same(track.featuredInFlashes, [flash1, flash2], @@ -333,7 +333,7 @@ t.test(`Track.hasUniqueCoverArt`, t => { t.equal(track.hasUniqueCoverArt, false, `hasUniqueCoverArt #1: defaults to false`); - album.trackCoverArtistContribsByRef = contribs; + album.trackCoverArtistContribs = contribs; XXX_decacheWikiData(); t.equal(track.hasUniqueCoverArt, true, @@ -346,13 +346,13 @@ t.test(`Track.hasUniqueCoverArt`, t => { track.disableUniqueCoverArt = false; - album.trackCoverArtistContribsByRef = badContribs; + album.trackCoverArtistContribs = badContribs; XXX_decacheWikiData(); t.equal(track.hasUniqueCoverArt, false, - `hasUniqueCoverArt #4: is false if album's trackCoverArtistContribsByRef resolve empty`); + `hasUniqueCoverArt #4: is false if album's trackCoverArtistContribs resolve empty`); - track.coverArtistContribsByRef = contribs; + track.coverArtistContribs = contribs; t.equal(track.hasUniqueCoverArt, true, `hasUniqueCoverArt #5: is true if track specifies coverArtistContribs`); @@ -364,10 +364,10 @@ t.test(`Track.hasUniqueCoverArt`, t => { track.disableUniqueCoverArt = false; - track.coverArtistContribsByRef = badContribs; + track.coverArtistContribs = badContribs; t.equal(track.hasUniqueCoverArt, false, - `hasUniqueCoverArt #7: is false if track's coverArtistContribsByRef resolve empty`); + `hasUniqueCoverArt #7: is false if track's coverArtistContribs resolve empty`); }); t.only(`Track.originalReleaseTrack`, t => { @@ -384,10 +384,10 @@ t.only(`Track.originalReleaseTrack`, t => { t.equal(track2.originalReleaseTrack, null, `originalReleaseTrack #1: defaults to null`); - track2.originalReleaseTrackByRef = 'track:track1'; + track2.originalReleaseTrack = 'track:track1'; t.equal(track2.originalReleaseTrack, track1, - `originalReleaseTrack #2: is resolved from originalReleaseTrackByRef`); + `originalReleaseTrack #2: is resolved from own value`); track2.trackData = []; @@ -411,9 +411,9 @@ t.test(`Track.otherReleases`, t => { t.same(track1.otherReleases, [], `otherReleases #1: defaults to empty array`); - track2.originalReleaseTrackByRef = 'track:track1'; - track3.originalReleaseTrackByRef = 'track:track1'; - track4.originalReleaseTrackByRef = 'track:track1'; + track2.originalReleaseTrack = 'track:track1'; + track3.originalReleaseTrack = 'track:track1'; + track4.originalReleaseTrack = 'track:track1'; XXX_decacheWikiData(); t.same(track1.otherReleases, [track2, track3, track4], @@ -435,7 +435,7 @@ t.test(`Track.otherReleases`, t => { `otherReleases #5: otherReleases of rerelease are original track then other rereleases (2/3)`); t.same(track4.otherReleases, [track1, track3, track2], - `otherReleases #6: otherReleases of rerelease are original track then other rereleases (1/3)`); + `otherReleases #6: otherReleases of rerelease are original track then other rereleases (3/3)`); }); t.test(`Track.referencedByTracks`, t => { @@ -454,20 +454,20 @@ t.test(`Track.referencedByTracks`, t => { t.same(track1.referencedByTracks, [], `referencedByTracks #1: defaults to empty array`); - track2.referencedTracksByRef = ['track:track1']; - track3.referencedTracksByRef = ['track:track1']; + track2.referencedTracks = ['track:track1']; + track3.referencedTracks = ['track:track1']; XXX_decacheWikiData(); t.same(track1.referencedByTracks, [track2, track3], `referencedByTracks #2: matches tracks' referencedTracks`); - track4.sampledTracksByRef = ['track:track1']; + track4.sampledTracks = ['track:track1']; XXX_decacheWikiData(); t.same(track1.referencedByTracks, [track2, track3], `referencedByTracks #3: doesn't match tracks' sampledTracks`); - track3.originalReleaseTrackByRef = 'track:track2'; + track3.originalReleaseTrack = 'track:track2'; XXX_decacheWikiData(); t.same(track1.referencedByTracks, [track2], @@ -490,20 +490,20 @@ t.test(`Track.sampledByTracks`, t => { t.same(track1.sampledByTracks, [], `sampledByTracks #1: defaults to empty array`); - track2.sampledTracksByRef = ['track:track1']; - track3.sampledTracksByRef = ['track:track1']; + track2.sampledTracks = ['track:track1']; + track3.sampledTracks = ['track:track1']; XXX_decacheWikiData(); t.same(track1.sampledByTracks, [track2, track3], `sampledByTracks #2: matches tracks' sampledTracks`); - track4.referencedTracksByRef = ['track:track1']; + track4.referencedTracks = ['track:track1']; XXX_decacheWikiData(); t.same(track1.sampledByTracks, [track2, track3], `sampledByTracks #3: doesn't match tracks' referencedTracks`); - track3.originalReleaseTrackByRef = 'track:track2'; + track3.originalReleaseTrack = 'track:track2'; XXX_decacheWikiData(); t.same(track1.sampledByTracks, [track2], -- cgit 1.3.0-6-gf8a5 From 7132dc6df4a2aabcd0c6f445a91bbd988e64623d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 8 Sep 2023 09:05:24 -0300 Subject: test: Track.coverArtDate: test contribs lists resolving empty --- test/unit/data/things/track.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index bb8d7079..6597c2f9 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -161,10 +161,10 @@ t.test(`Track.color`, t => { }); t.test(`Track.coverArtDate`, t => { - t.plan(6); + t.plan(8); const {track, album} = stubTrackAndAlbum(); - const {artist, contribs} = stubArtistAndContribs(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); const {XXX_decacheWikiData} = linkAndBindWikiData({ albumData: [album], @@ -192,19 +192,32 @@ t.test(`Track.coverArtDate`, t => { track.coverArtistContribs = []; t.equal(track.coverArtDate, null, - `coverArtDate #4: is null if track is missing coverArtists`); + `coverArtDate #4: is null if track coverArtistContribs empty`); album.trackCoverArtistContribs = contribs; XXX_decacheWikiData(); t.same(track.coverArtDate, new Date('2009-09-09'), - `coverArtDate #5: is not null if album specifies trackCoverArtistContribs`); + `coverArtDate #5: is not null if album trackCoverArtistContribs specified`); + + album.trackCoverArtistContribs = badContribs; + + XXX_decacheWikiData(); + t.equal(track.coverArtDate, null, + `coverArtDate #6: is null if album trackCoverArtistContribs resolves empty`); + + track.coverArtistContribs = badContribs; + + t.equal(track.coverArtDate, null, + `coverArtDate #7: is null if track coverArtistContribs resolves empty`); + + track.coverArtistContribs = contribs; track.disableUniqueCoverArt = true; t.equal(track.coverArtDate, null, - `coverArtDate #6: is null if track disables unique cover artwork`); + `coverArtDate #8: is null if track disables unique cover artwork`); }); t.test(`Track.coverArtFileExtension`, t => { -- cgit 1.3.0-6-gf8a5 From 2ce923876663fcbdd2c9aaec96692592a066436c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 8 Sep 2023 09:08:51 -0300 Subject: test: Album.coverArtDate (unit) The last test is deliberately failing. --- test/unit/data/things/album.js | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 test/unit/data/things/album.js (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js new file mode 100644 index 00000000..358abb3b --- /dev/null +++ b/test/unit/data/things/album.js @@ -0,0 +1,61 @@ +import t from 'tap'; + +import {linkAndBindWikiData} from '#test-lib'; +import thingConstructors from '#things'; + +const { + Album, + Artist, + Thing, + Track, +} = thingConstructors; + +function stubArtistAndContribs() { + const artist = new Artist(); + artist.name = `Test Artist`; + + const contribs = [{who: `Test Artist`, what: null}]; + const badContribs = [{who: `Figment of Your Imagination`, what: null}]; + + return {artist, contribs, badContribs}; +} + +t.test(`Album.coverArtDate`, t => { + t.plan(6); + + const album = new Album(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + }); + + t.equal(album.coverArtDate, null, + `Album.coverArtDate #1: defaults to null`); + + album.date = new Date('2012-10-25'); + + t.equal(album.coverArtDate, null, + `Album.coverArtDate #2: is null if coverArtistContribs empty (1/2)`); + + album.coverArtDate = new Date('2011-04-13'); + + t.equal(album.coverArtDate, null, + `Album.coverArtDate #3: is null if coverArtistContribs empty (2/2)`); + + album.coverArtistContribs = contribs; + + t.same(album.coverArtDate, new Date('2011-04-13'), + `Album.coverArtDate #4: is own value`); + + album.coverArtDate = null; + + t.same(album.coverArtDate, new Date(`2012-10-25`), + `Album.coverArtDate #5: inherits album release date`); + + album.coverArtistContribs = badContribs; + + t.equal(album.coverArtDate, null, + `Album.coverArtDate #6: is null if coverArtistContribs resolves empty`); +}); -- cgit 1.3.0-6-gf8a5 From 3344a16a02a2c680d9e9eaf27c81890e17b9b5f4 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 8 Sep 2023 09:12:42 -0300 Subject: test: Album.coverArtFileExtension (unit) Most of these currently fail. --- test/unit/data/things/album.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js index 358abb3b..42f73082 100644 --- a/test/unit/data/things/album.js +++ b/test/unit/data/things/album.js @@ -59,3 +59,39 @@ t.test(`Album.coverArtDate`, t => { t.equal(album.coverArtDate, null, `Album.coverArtDate #6: is null if coverArtistContribs resolves empty`); }); + +t.test(`Album.coverArtFileExtension`, t => { + t.plan(5); + + const album = new Album(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + }); + + t.equal(album.coverArtFileExtension, null, + `Album.coverArtFileExtension #1: is null if coverArtistContribs empty (1/2)`); + + album.coverArtFileExtension = 'png'; + + t.equal(album.coverArtFileExtension, null, + `Album.coverArtFileExtension #2: is null if coverArtistContribs empty (2/2)`); + + album.coverArtFileExtension = null; + album.coverArtistContribs = contribs; + + t.equal(album.coverArtFileExtension, 'jpg', + `Album.coverArtFileExtension #3: defaults to jpg`); + + album.coverArtFileExtension = 'png'; + + t.equal(album.coverArtFileExtension, 'png', + `Album.coverArtFileExtension #4: is own value`); + + album.coverArtistContribs = badContribs; + + t.equal(album.coverArtFileExtension, null, + `Album.coverArtFileExtension #5: is null if coverArtistContribs resolves empty`); +}); -- cgit 1.3.0-6-gf8a5 From f82c43f594ee5eadde34c05d45b9ce7a14301a87 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 8 Sep 2023 11:30:27 -0300 Subject: test: Album.tracks (unit) --- test/unit/data/things/album.js | 50 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js index 42f73082..63d787e5 100644 --- a/test/unit/data/things/album.js +++ b/test/unit/data/things/album.js @@ -6,7 +6,6 @@ import thingConstructors from '#things'; const { Album, Artist, - Thing, Track, } = thingConstructors; @@ -20,6 +19,13 @@ function stubArtistAndContribs() { return {artist, contribs, badContribs}; } +function stubTrack(directory = 'foo') { + const track = new Track(); + track.directory = directory; + + return track; +} + t.test(`Album.coverArtDate`, t => { t.plan(6); @@ -95,3 +101,45 @@ t.test(`Album.coverArtFileExtension`, t => { t.equal(album.coverArtFileExtension, null, `Album.coverArtFileExtension #5: is null if coverArtistContribs resolves empty`); }); + +t.test(`Album.tracks`, t => { + t.plan(4); + + const album = new Album(); + const track1 = stubTrack('track1'); + const track2 = stubTrack('track2'); + const track3 = stubTrack('track3'); + + linkAndBindWikiData({ + albumData: [album], + trackData: [track1, track2, track3], + }); + + t.same(album.tracks, [], + `Album.tracks #1: defaults to empty array`); + + album.trackSections = [ + {tracks: ['track:track1', 'track:track2', 'track:track3']}, + ]; + + t.same(album.tracks, [track1, track2, track3], + `Album.tracks #2: pulls tracks from one track section`); + + album.trackSections = [ + {tracks: ['track:track1']}, + {tracks: ['track:track2', 'track:track3']}, + ]; + + t.same(album.tracks, [track1, track2, track3], + `Album.tracks #3: pulls tracks from multiple track sections`); + + album.trackSections = [ + {tracks: ['track:track1', 'track:does-not-exist']}, + {tracks: ['track:this-one-neither', 'track:track2']}, + {tracks: ['track:effectively-empty-section']}, + {tracks: ['track:track3']}, + ]; + + t.same(album.tracks, [track1, track2, track3], + `Album.tracks #4: filters out references without matches`); +}); -- cgit 1.3.0-6-gf8a5 From f4305e5ab0a64a648f39c647b817a4ba09848f11 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 8 Sep 2023 12:34:48 -0300 Subject: test: Album.trackSections (unit) --- test/unit/data/things/album.js | 68 ++++++++++++++++++++++++++++++++++++++++++ test/unit/data/things/track.js | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js index 63d787e5..3593074a 100644 --- a/test/unit/data/things/album.js +++ b/test/unit/data/things/album.js @@ -143,3 +143,71 @@ t.test(`Album.tracks`, t => { t.same(album.tracks, [track1, track2, track3], `Album.tracks #4: filters out references without matches`); }); + +t.test(`Album.trackSections`, t => { + t.plan(5); + + const album = new Album(); + const track1 = stubTrack('track1'); + const track2 = stubTrack('track2'); + const track3 = stubTrack('track3'); + const track4 = stubTrack('track4'); + + linkAndBindWikiData({ + albumData: [album], + trackData: [track1, track2, track3, track4], + }); + + album.trackSections = [ + {tracks: ['track:track1', 'track:track2']}, + {tracks: ['track:track3', 'track:track4']}, + ]; + + t.match(album.trackSections, [ + {tracks: [track1, track2]}, + {tracks: [track3, track4]}, + ], `Album.trackSections #1: exposes tracks`); + + t.match(album.trackSections, [ + {tracks: [track1, track2], startIndex: 0}, + {tracks: [track3, track4], startIndex: 2}, + ], `Album.trackSections #2: exposes startIndex`); + + album.color = '#123456'; + + album.trackSections = [ + {tracks: ['track:track1'], color: null}, + {tracks: ['track:track2'], color: '#abcdef'}, + {tracks: ['track:track3'], color: null}, + ]; + + t.match(album.trackSections, [ + {tracks: [track1], color: '#123456'}, + {tracks: [track2], color: '#abcdef'}, + {tracks: [track3], color: '#123456'}, + ], `Album.trackSections #3: exposes color, inherited from album`); + + album.trackSections = [ + {tracks: ['track:track1'], dateOriginallyReleased: null}, + {tracks: ['track:track2'], dateOriginallyReleased: new Date('2009-04-11')}, + {tracks: ['track:track3'], dateOriginallyReleased: null}, + ]; + + t.match(album.trackSections, [ + {tracks: [track1], dateOriginallyReleased: null}, + {tracks: [track2], dateOriginallyReleased: new Date('2009-04-11')}, + {tracks: [track3], dateOriginallyReleased: null}, + ], `Album.trackSections #4: exposes dateOriginallyReleased, if present`); + + album.trackSections = [ + {tracks: ['track:track1'], isDefaultTrackSection: true}, + {tracks: ['track:track2'], isDefaultTrackSection: false}, + {tracks: ['track:track3'], isDefaultTrackSection: null}, + ]; + + t.match(album.trackSections, [ + {tracks: [track1], isDefaultTrackSection: true}, + {tracks: [track2], isDefaultTrackSection: false}, + {tracks: [track3], isDefaultTrackSection: false}, + ], `Album.trackSections #5: exposes isDefaultTrackSection, defaults to false`); +}); diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 6597c2f9..8aecf789 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -383,7 +383,7 @@ t.test(`Track.hasUniqueCoverArt`, t => { `hasUniqueCoverArt #7: is false if track's coverArtistContribs resolve empty`); }); -t.only(`Track.originalReleaseTrack`, t => { +t.test(`Track.originalReleaseTrack`, t => { t.plan(3); const {track: track1, album: album1} = stubTrackAndAlbum('track1'); -- cgit 1.3.0-6-gf8a5 From a8718915ffcb9b3977ccab479817c8bd8d9b20c6 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 8 Sep 2023 15:39:40 -0300 Subject: test: Track.commentatorArtists (unit) --- test/unit/data/things/track.js | 68 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 8aecf789..5e7fd829 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -32,14 +32,20 @@ function stubTrack(directory = 'foo') { function stubTrackAndAlbum(trackDirectory = 'foo', albumDirectory = 'bar') { const track = stubTrack(trackDirectory); const album = stubAlbum([track], albumDirectory); + return {track, album}; } -function stubArtistAndContribs() { +function stubArtist(artistName = `Test Artist`) { const artist = new Artist(); - artist.name = `Test Artist`; + artist.name = artistName; + + return artist; +} - const contribs = [{who: `Test Artist`, what: null}]; +function stubArtistAndContribs(artistName = `Test Artist`) { + const artist = stubArtist(artistName); + const contribs = [{who: artistName, what: null}]; const badContribs = [{who: `Figment of Your Imagination`, what: null}]; return {artist, contribs, badContribs}; @@ -160,6 +166,62 @@ t.test(`Track.color`, t => { `color #5: must be set to valid color`); }); +t.test(`Track.commentatorArtists`, t => { + t.plan(6); + + const track = new Track(); + const artist1 = stubArtist(`SnooPING`); + const artist2 = stubArtist(`ASUsual`); + const artist3 = stubArtist(`Icy`); + + linkAndBindWikiData({ + trackData: [track], + artistData: [artist1, artist2, artist3], + }); + + track.commentary = + `SnooPING:\n` + + `Wow.\n`; + + t.same(track.commentatorArtists, [artist1], + `Track.commentatorArtists #1: works with one commentator`); + + track.commentary += + `ASUsual:\n` + + `Yes!\n`; + + t.same(track.commentatorArtists, [artist1, artist2], + `Track.commentatorArtists #2: works with two commentators`); + + track.commentary += + `Icy:\n` + + `Incredible.\n`; + + t.same(track.commentatorArtists, [artist1, artist2, artist3], + `Track.commentatorArtists #3: works with boldface name`); + + track.commentary = + `Icy: (project manager)\n` + + `Very good track.\n`; + + t.same(track.commentatorArtists, [artist3], + `Track.commentatorArtists #4: works with parenthical accent`); + + track.commentary += + `SNooPING ASUsual Icy:\n` + + `WITH ALL THREE POWERS COMBINED...`; + + t.same(track.commentatorArtists, [artist3], + `Track.commentatorArtists #5: ignores artist names not found`); + + track.commentary += + `Icy:\n` + + `I'm back!\n`; + + t.same(track.commentatorArtists, [artist3], + `Track.commentatorArtists #6: ignores duplicate artist`); +}); + t.test(`Track.coverArtDate`, t => { t.plan(8); -- cgit 1.3.0-6-gf8a5 From 666ce1d6c2e1b93e34222c2b2b999ff32a1c6ca8 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 9 Sep 2023 18:24:53 -0300 Subject: test: Album.{banner,wallpaper}{FileExtension,Style} Also Album.bannerDimensions. --- test/unit/data/things/album.js | 160 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js index 3593074a..240150b0 100644 --- a/test/unit/data/things/album.js +++ b/test/unit/data/things/album.js @@ -26,6 +26,101 @@ function stubTrack(directory = 'foo') { return track; } +t.test(`Album.bannerDimensions`, t => { + t.plan(4); + + const album = new Album(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + }); + + t.equal(album.bannerDimensions, null, + `Album.bannerDimensions #1: defaults to null`); + + album.bannerDimensions = [1200, 275]; + + t.equal(album.bannerDimensions, null, + `Album.bannerDimensions #2: is null if bannerArtistContribs empty`); + + album.bannerArtistContribs = badContribs; + + t.equal(album.bannerDimensions, null, + `Album.bannerDimensions #3: is null if bannerArtistContribs resolves empty`); + + album.bannerArtistContribs = contribs; + + t.same(album.bannerDimensions, [1200, 275], + `Album.bannerDimensions #4: is own value`); +}); + +t.test(`Album.bannerFileExtension`, t => { + t.plan(5); + + const album = new Album(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + }); + + t.equal(album.bannerFileExtension, null, + `Album.bannerFileExtension #1: defaults to null`); + + album.bannerFileExtension = 'png'; + + t.equal(album.bannerFileExtension, null, + `Album.bannerFileExtension #2: is null if bannerArtistContribs empty`); + + album.bannerArtistContribs = badContribs; + + t.equal(album.bannerFileExtension, null, + `Album.bannerFileExtension #3: is null if bannerArtistContribs resolves empty`); + + album.bannerArtistContribs = contribs; + + t.equal(album.bannerFileExtension, 'png', + `Album.bannerFileExtension #4: is own value`); + + album.bannerFileExtension = null; + + t.equal(album.bannerFileExtension, 'jpg', + `Album.bannerFileExtension #5: defaults to jpg`); +}); + +t.test(`Album.bannerStyle`, t => { + t.plan(4); + + const album = new Album(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + }); + + t.equal(album.bannerStyle, null, + `Album.bannerStyle #1: defaults to null`); + + album.bannerStyle = `opacity: 0.5`; + + t.equal(album.bannerStyle, null, + `Album.bannerStyle #2: is null if bannerArtistContribs empty`); + + album.bannerArtistContribs = badContribs; + + t.equal(album.bannerStyle, null, + `Album.bannerStyle #3: is null if bannerArtistContribs resolves empty`); + + album.bannerArtistContribs = contribs; + + t.equal(album.bannerStyle, `opacity: 0.5`, + `Album.bannerStyle #4: is own value`); +}); + t.test(`Album.coverArtDate`, t => { t.plan(6); @@ -211,3 +306,68 @@ t.test(`Album.trackSections`, t => { {tracks: [track3], isDefaultTrackSection: false}, ], `Album.trackSections #5: exposes isDefaultTrackSection, defaults to false`); }); + +t.test(`Album.wallpaperFileExtension`, t => { + t.plan(5); + + const album = new Album(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + }); + + t.equal(album.wallpaperFileExtension, null, + `Album.wallpaperFileExtension #1: defaults to null`); + + album.wallpaperFileExtension = 'png'; + + t.equal(album.wallpaperFileExtension, null, + `Album.wallpaperFileExtension #2: is null if wallpaperArtistContribs empty`); + + album.wallpaperArtistContribs = contribs; + + t.equal(album.wallpaperFileExtension, 'png', + `Album.wallpaperFileExtension #3: is own value`); + + album.wallpaperFileExtension = null; + + t.equal(album.wallpaperFileExtension, 'jpg', + `Album.wallpaperFileExtension #4: defaults to jpg`); + + album.wallpaperArtistContribs = badContribs; + + t.equal(album.wallpaperFileExtension, null, + `Album.wallpaperFileExtension #5: is null if wallpaperArtistContribs resolves empty`); +}); + +t.test(`Album.wallpaperStyle`, t => { + t.plan(4); + + const album = new Album(); + const {artist, contribs, badContribs} = stubArtistAndContribs(); + + linkAndBindWikiData({ + albumData: [album], + artistData: [artist], + }); + + t.equal(album.wallpaperStyle, null, + `Album.wallpaperStyle #1: defaults to null`); + + album.wallpaperStyle = `opacity: 0.5`; + + t.equal(album.wallpaperStyle, null, + `Album.wallpaperStyle #2: is null if wallpaperArtistContribs empty`); + + album.wallpaperArtistContribs = badContribs; + + t.equal(album.wallpaperStyle, null, + `Album.wallpaperStyle #3: is null if wallpaperArtistContribs resolves empty`); + + album.wallpaperArtistContribs = contribs; + + t.equal(album.wallpaperStyle, `opacity: 0.5`, + `Album.wallpaperStyle #4: is own value`); +}); -- cgit 1.3.0-6-gf8a5 From b06c194fc02da22564bcb165db33282f411859a3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 9 Sep 2023 18:31:09 -0300 Subject: data, test: filter out empty track sections Also test unmatched track references. --- test/unit/data/things/album.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js index 240150b0..0695fdb6 100644 --- a/test/unit/data/things/album.js +++ b/test/unit/data/things/album.js @@ -240,7 +240,7 @@ t.test(`Album.tracks`, t => { }); t.test(`Album.trackSections`, t => { - t.plan(5); + t.plan(6); const album = new Album(); const track1 = stubTrack('track1'); @@ -305,6 +305,20 @@ t.test(`Album.trackSections`, t => { {tracks: [track2], isDefaultTrackSection: false}, {tracks: [track3], isDefaultTrackSection: false}, ], `Album.trackSections #5: exposes isDefaultTrackSection, defaults to false`); + + album.trackSections = [ + {tracks: ['track:track1', 'track:track2', 'track:snooping'], color: '#112233'}, + {tracks: ['track:track3', 'track:as-usual'], color: '#334455'}, + {tracks: [], color: '#bbbbba'}, + {tracks: ['track:icy', 'track:chilly', 'track:frigid'], color: '#556677'}, + {tracks: ['track:track4'], color: '#778899'}, + ]; + + t.match(album.trackSections, [ + {tracks: [track1, track2], color: '#112233'}, + {tracks: [track3], color: '#334455'}, + {tracks: [track4], color: '#778899'}, + ], `Album.trackSections #6: filters out references without matches & empty sections`); }); t.test(`Album.wallpaperFileExtension`, t => { -- cgit 1.3.0-6-gf8a5 From 14329ec8eedb7ad5dcb6a3308a26686bd381ab36 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 9 Sep 2023 19:04:04 -0300 Subject: data, test: ArtTag.nameShort --- test/unit/data/things/art-tag.js | 71 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 test/unit/data/things/art-tag.js (limited to 'test') diff --git a/test/unit/data/things/art-tag.js b/test/unit/data/things/art-tag.js new file mode 100644 index 00000000..561c93ef --- /dev/null +++ b/test/unit/data/things/art-tag.js @@ -0,0 +1,71 @@ +import t from 'tap'; + +import {linkAndBindWikiData} from '#test-lib'; +import thingConstructors from '#things'; + +const { + Album, + Artist, + ArtTag, + Track, +} = thingConstructors; + +function stubAlbum(tracks, directory = 'bar') { + const album = new Album(); + album.directory = directory; + + const trackRefs = tracks.map(t => Thing.getReference(t)); + album.trackSections = [{tracks: trackRefs}]; + + return album; +} + +function stubTrack(directory = 'foo') { + const track = new Track(); + track.directory = directory; + + return track; +} + +function stubTrackAndAlbum(trackDirectory = 'foo', albumDirectory = 'bar') { + const track = stubTrack(trackDirectory); + const album = stubAlbum([track], albumDirectory); + + return {track, album}; +} + +function stubArtist(artistName = `Test Artist`) { + const artist = new Artist(); + artist.name = artistName; + + return artist; +} + +function stubArtistAndContribs(artistName = `Test Artist`) { + const artist = stubArtist(artistName); + const contribs = [{who: artistName, what: null}]; + const badContribs = [{who: `Figment of Your Imagination`, what: null}]; + + return {artist, contribs, badContribs}; +} + +t.test(`ArtTag.nameShort`, t => { + t.plan(3); + + const artTag = new ArtTag(); + + artTag.name = `Dave Strider`; + + t.equal(artTag.nameShort, `Dave Strider`, + `ArtTag #1: defaults to name`); + + artTag.name = `Dave Strider (Homestuck)`; + + t.equal(artTag.nameShort, `Dave Strider`, + `ArtTag #2: trims parenthical part at end`); + + artTag.name = `This (And) That (Then)`; + + t.equal(artTag.nameShort, `This (And) That`, + `ArtTag #2: doesn't trim midlde parenthical part`); +}); -- cgit 1.3.0-6-gf8a5 From 72c29f7dfee2bdd7ded4113f0bbccc74d9ea60f9 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 19 Sep 2023 10:36:45 -0300 Subject: test: linkThing: basic tests (snapshot) --- test/snapshot/linkThing.js | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 test/snapshot/linkThing.js (limited to 'test') diff --git a/test/snapshot/linkThing.js b/test/snapshot/linkThing.js new file mode 100644 index 00000000..f76b17df --- /dev/null +++ b/test/snapshot/linkThing.js @@ -0,0 +1,76 @@ +import t from 'tap'; +import * as html from '#html'; +import {testContentFunctions} from '#test-lib'; + +testContentFunctions(t, 'linkThing (snapshot)', async (t, evaluate) => { + await evaluate.load(); + + const quickSnapshot = (message, oneOrMultiple) => + evaluate.snapshot(message, + (Array.isArray(oneOrMultiple) + ? {name: 'linkThing', multiple: oneOrMultiple} + : {name: 'linkThing', ...oneOrMultiple})); + + quickSnapshot('basic behavior', { + args: ['localized.track', { + directory: 'foo', + color: '#abcdef', + name: `Cool track!`, + }], + }); + + quickSnapshot('preferShortName', { + args: ['localized.tag', { + directory: 'five-oceanfalls', + name: 'Five (Oceanfalls)', + nameShort: 'Five', + }], + slots: {preferShortName: true}, + }); + + quickSnapshot('tooltip & content', { + args: ['localized.album', { + directory: 'beyond-canon', + name: 'Beyond Canon', + }], + multiple: [ + {slots: {tooltip: false}}, + {slots: {tooltip: true}}, + {slots: {tooltip: true, content: 'Next'}}, + {slots: {tooltip: 'Apple', content: 'Banana'}}, + {slots: {content: 'Banana'}}, + ], + }); + + quickSnapshot('color', { + args: ['localized.track', { + directory: 'showtime-piano-refrain', + name: 'Showtime (Piano Refrain)', + color: '#38f43d', + }], + multiple: [ + {slots: {color: false}}, + {slots: {color: true}}, + {slots: {color: '#aaccff'}}, + ], + }); + + quickSnapshot('tags in name escaped', [ + {args: ['localized.track', { + directory: 'foo', + name: `AS USUAL I SEE`, + }]}, + {args: ['localized.track', { + directory: 'bar', + name: `boldface`, + }]}, + {args: ['localized.album', { + directory: 'exile', + name: '>Exile<', + }]}, + {args: ['localized.track', { + directory: 'heart', + name: '<3', + }]}, + ]); +}); -- cgit 1.3.0-6-gf8a5 From fcb00a7afd8c70586d645a7072fc84fa5de074b3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 19 Sep 2023 10:43:27 -0300 Subject: test: linkThing: specifically test tag stripping --- test/snapshot/linkThing.js | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'test') diff --git a/test/snapshot/linkThing.js b/test/snapshot/linkThing.js index f76b17df..195d8c0e 100644 --- a/test/snapshot/linkThing.js +++ b/test/snapshot/linkThing.js @@ -73,4 +73,15 @@ testContentFunctions(t, 'linkThing (snapshot)', async (t, evaluate) => { name: '<3', }]}, ]); + + quickSnapshot('nested links in content stripped', { + args: ['localized.staticPage', {directory: 'foo', name: 'Foo'}], + slots: { + content: + html.tag('b', {[html.joinChildren]: ''}, [ + html.tag('a', {href: 'bar'}, `Oooo!`), + ` Very spooky.`, + ]), + }, + }); }); -- cgit 1.3.0-6-gf8a5 From 572b5465f9ce1e992e0384aa92461ec11dbaabff Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 21 Sep 2023 11:04:33 -0300 Subject: data: make composites work --- test/lib/index.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'test') diff --git a/test/lib/index.js b/test/lib/index.js index 6eaaa656..5fb5bf78 100644 --- a/test/lib/index.js +++ b/test/lib/index.js @@ -1,3 +1,5 @@ +Error.stackTraceLimit = Infinity; + export * from './content-function.js'; export * from './generic-mock.js'; export * from './wiki-data.js'; -- cgit 1.3.0-6-gf8a5 From 8bcae16b391762f6b533654ec06c3bf0c8770d35 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 23 Sep 2023 20:24:08 -0300 Subject: data, test: WIP tests for compositeFrom --- test/unit/data/composite/compositeFrom.js | 237 ++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 test/unit/data/composite/compositeFrom.js (limited to 'test') diff --git a/test/unit/data/composite/compositeFrom.js b/test/unit/data/composite/compositeFrom.js new file mode 100644 index 00000000..faeef59a --- /dev/null +++ b/test/unit/data/composite/compositeFrom.js @@ -0,0 +1,237 @@ +import t from 'tap'; + +import {isString} from '#validators'; + +import { + compositeFrom, + continuationSymbol, + debugComposite, + input, +} from '#composite'; + +t.test(`compositeFrom: basic behavior`, t => { + t.plan(2); + + const composite = compositeFrom({ + annotation: `myComposite`, + compose: false, + + steps: [ + { + dependencies: ['foo'], + compute: (continuation, {foo}) => + continuation({'#bar': foo * 2}), + }, + + { + dependencies: ['#bar', 'baz', 'suffix'], + compute: ({'#bar': bar, baz, suffix}) => + baz.repeat(bar) + suffix, + }, + ], + }); + + t.match(composite, { + annotation: `myComposite`, + + flags: {expose: true, compose: false, update: false}, + + expose: { + dependencies: ['foo', 'baz', 'suffix'], + compute: Function, + transform: null, + }, + + update: null, + }); + + t.equal( + composite.expose.compute({ + foo: 3, + baz: 'ba', + suffix: 'BOOM', + }), + 'babababababaBOOM'); +}); + +t.test(`compositeFrom: input-shaped step dependencies`, t => { + t.plan(2); + + const composite = compositeFrom({ + compose: false, + steps: [ + { + dependencies: [ + input.myself(), + input.updateValue(), + ], + + transform: (updateValue1, { + [input.myself()]: me, + [input.updateValue()]: updateValue2, + }) => ({me, updateValue1, updateValue2}), + }, + ], + }); + + t.match(composite, { + expose: { + dependencies: ['this'], + transform: Function, + compute: null, + }, + }); + + const myself = {foo: 'bar'}; + + t.same( + composite.expose.transform('banana', { + this: myself, + pomelo: 'delicious', + }), + { + me: myself, + updateValue1: 'banana', + updateValue2: 'banana', + }); +}); + +t.test(`compositeFrom: dependencies from inputs`, t => { + t.plan(3); + + const composite = compositeFrom({ + annotation: `myComposite`, + + compose: true, + + inputs: { + foo: input('bar'), + pomelo: input.value('delicious'), + humorous: input.dependency('#mammal'), + data: input.dependency('albumData'), + ref: input.updateValue(), + }, + + steps: [ + { + dependencies: [ + input('foo'), + input('pomelo'), + input('humorous'), + input('data'), + input('ref'), + ], + + compute: (continuation, { + [input('foo')]: foo, + [input('pomelo')]: pomelo, + [input('humorous')]: humorous, + [input('data')]: data, + [input('ref')]: ref, + }) => continuation.exit({foo, pomelo, humorous, data, ref}), + }, + ], + }); + + t.match(composite, { + expose: { + dependencies: [ + input('bar'), + '#mammal', + 'albumData', + ], + + transform: Function, + compute: null, + }, + }); + + const exitData = {}; + const continuation = { + exit(value) { + Object.assign(exitData, value); + return continuationSymbol; + }, + }; + + t.equal( + composite.expose.transform('album:bepis', continuation, { + [input('bar')]: 'squid time', + '#mammal': 'fox', + 'albumData': ['album1', 'album2'], + }), + continuationSymbol); + + t.same(exitData, { + foo: 'squid time', + pomelo: 'delicious', + humorous: 'fox', + data: ['album1', 'album2'], + ref: 'album:bepis', + }); +}); + +t.test(`compositeFrom: update from various sources`, t => { + t.plan(2); + + const match = { + flags: {update: true, expose: true, compose: false}, + + update: { + validate: isString, + default: 'foo', + }, + + expose: { + transform: Function, + compute: null, + }, + }; + + t.test(`compositeFrom: update from composition description`, t => { + t.plan(2); + + const composite = compositeFrom({ + compose: false, + + update: { + validate: isString, + default: 'foo', + }, + + steps: [ + {transform: (value, continuation) => continuation(value.repeat(2))}, + {transform: (value) => `Xx_${value}_xX`}, + ], + }); + + t.match(composite, match); + t.equal(composite.expose.transform('foo'), `Xx_foofoo_xX`); + }); + + t.test(`compositeFrom: update from step dependencies`, t => { + t.plan(2); + + const composite = compositeFrom({ + compose: false, + + steps: [ + { + dependencies: [ + input.updateValue({ + validate: isString, + default: 'foo', + }), + ], + + compute: ({ + [input.updateValue()]: value, + }) => `Xx_${value.repeat(2)}_xX`, + }, + ], + }); + + t.match(composite, match); + t.equal(debugComposite(() => composite.expose.transform('foo')), 'Xx_foofoo_xX'); + }); +}); -- cgit 1.3.0-6-gf8a5 From f3d98f5ea63db7f7b2155e7efb0812f025c5bcf3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 23 Sep 2023 20:35:57 -0300 Subject: data, test: collate update description from composition inputs --- test/unit/data/composite/compositeFrom.js | 52 +++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/compositeFrom.js b/test/unit/data/composite/compositeFrom.js index faeef59a..6ae1e7bc 100644 --- a/test/unit/data/composite/compositeFrom.js +++ b/test/unit/data/composite/compositeFrom.js @@ -172,7 +172,7 @@ t.test(`compositeFrom: dependencies from inputs`, t => { }); t.test(`compositeFrom: update from various sources`, t => { - t.plan(2); + t.plan(3); const match = { flags: {update: true, expose: true, compose: false}, @@ -232,6 +232,54 @@ t.test(`compositeFrom: update from various sources`, t => { }); t.match(composite, match); - t.equal(debugComposite(() => composite.expose.transform('foo')), 'Xx_foofoo_xX'); + t.equal(composite.expose.transform('foo'), 'Xx_foofoo_xX'); + }); + + t.test(`compositeFrom: update from inputs`, t => { + t.plan(3); + + const composite = compositeFrom({ + inputs: { + myInput: input.updateValue({ + validate: isString, + default: 'foo', + }), + }, + + steps: [ + { + dependencies: [input('myInput')], + compute: (continuation, { + [input('myInput')]: value, + }) => continuation({ + '#value': `Xx_${value.repeat(2)}_xX`, + }), + }, + + { + dependencies: ['#value'], + transform: (_value, continuation, {'#value': value}) => + continuation(value), + }, + ], + }); + + let continuationValue = null; + const continuation = value => { + continuationValue = value; + return continuationSymbol; + }; + + t.match(composite, { + ...match, + + flags: {update: true, expose: true, compose: true}, + }); + + t.equal( + composite.expose.transform('foo', continuation), + continuationSymbol); + + t.equal(continuationValue, 'Xx_foofoo_xX'); }); }); -- cgit 1.3.0-6-gf8a5 From 942b05a2beed7d28e93ae256de0f58be5b7e385a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 23 Sep 2023 22:15:49 -0300 Subject: data, test: exposeDependency (unit) --- test/unit/data/composite/compositeFrom.js | 8 +-- test/unit/data/composite/exposeDependency.js | 80 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 test/unit/data/composite/exposeDependency.js (limited to 'test') diff --git a/test/unit/data/composite/compositeFrom.js b/test/unit/data/composite/compositeFrom.js index 6ae1e7bc..06a66e61 100644 --- a/test/unit/data/composite/compositeFrom.js +++ b/test/unit/data/composite/compositeFrom.js @@ -1,14 +1,8 @@ import t from 'tap'; +import {compositeFrom, continuationSymbol, input} from '#composite'; import {isString} from '#validators'; -import { - compositeFrom, - continuationSymbol, - debugComposite, - input, -} from '#composite'; - t.test(`compositeFrom: basic behavior`, t => { t.plan(2); diff --git a/test/unit/data/composite/exposeDependency.js b/test/unit/data/composite/exposeDependency.js new file mode 100644 index 00000000..7487e44c --- /dev/null +++ b/test/unit/data/composite/exposeDependency.js @@ -0,0 +1,80 @@ +import t from 'tap'; + +import { + compositeFrom, + continuationSymbol, + exposeDependency, + input, +} from '#composite'; + +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); + + let caughtError; + + try { + caughtError = null; + exposeDependency({}); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + errors: [/Required these inputs: dependency/], + }); + + try { + caughtError = null; + exposeDependency({ + dependency: input.value('some static value'), + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + errors: [/Expected static dependencies: dependency/], + }); +}); -- cgit 1.3.0-6-gf8a5 From 243af63c1ca36343f780b6e95b05493ff03ca68b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 24 Sep 2023 10:01:14 -0300 Subject: test: image: supply & test missingImagePaths --- test/snapshot/image.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'test') diff --git a/test/snapshot/image.js b/test/snapshot/image.js index 8608ab69..2a1e9805 100644 --- a/test/snapshot/image.js +++ b/test/snapshot/image.js @@ -14,6 +14,7 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { getThumbnailEqualOrSmaller: () => 'medium', getThumbnailsAvailableForDimensions: () => [['large', 800], ['medium', 400], ['small', 250]], + missingImagePaths: ['album-art/missing/cover.png'], ...extraDependencies, }, ...opts, @@ -114,6 +115,7 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { getThumbnailsAvailableForDimensions: () => [['voluminous', 1200], ['middling', 900], ['petite', 20]], getThumbnailEqualOrSmaller: () => 'voluminous', + missingImagePaths: [], }, slots: { thumb: 'gargantuan', @@ -127,4 +129,20 @@ testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => { path: ['media.flashArt', '5426', 'gif'], }, }); + + quickSnapshot('missing image path', { + slots: { + thumb: 'medium', + path: ['media.albumCover', 'missing', 'png'], + link: true, + }, + }); + + quickSnapshot('missing image path w/ missingSourceContent', { + slots: { + thumb: 'medium', + path: ['media.albumCover', 'missing', 'png'], + missingSourceContent: `Cover's missing, whoops`, + }, + }); }); -- cgit 1.3.0-6-gf8a5 From 219596b6d52443d1090c94e50244cf79d548a167 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 25 Sep 2023 08:48:19 -0300 Subject: data, test: exposeConstant, withResultOfAvailabilityCheck --- test/unit/data/composite/exposeConstant.js | 60 ++++++++ .../composite/withResultOfAvailabilityCheck.js | 159 +++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 test/unit/data/composite/exposeConstant.js create mode 100644 test/unit/data/composite/withResultOfAvailabilityCheck.js (limited to 'test') diff --git a/test/unit/data/composite/exposeConstant.js b/test/unit/data/composite/exposeConstant.js new file mode 100644 index 00000000..ce3f5e3d --- /dev/null +++ b/test/unit/data/composite/exposeConstant.js @@ -0,0 +1,60 @@ +import t from 'tap'; + +import { + compositeFrom, + continuationSymbol, + exposeConstant, + input, +} from '#composite'; + +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); + + let caughtError; + + try { + caughtError = null; + exposeConstant({}); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + errors: [/Required these inputs: value/], + }); + + try { + caughtError = null; + exposeConstant({ + value: 'some dependency', + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + errors: [/Expected static values: value/], + }); +}); diff --git a/test/unit/data/composite/withResultOfAvailabilityCheck.js b/test/unit/data/composite/withResultOfAvailabilityCheck.js new file mode 100644 index 00000000..f5229fb3 --- /dev/null +++ b/test/unit/data/composite/withResultOfAvailabilityCheck.js @@ -0,0 +1,159 @@ +import t from 'tap'; + +import { + compositeFrom, + continuationSymbol, + withResultOfAvailabilityCheck, + input, +} from '#composite'; + +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(10); + + 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: 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(10); + + 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: 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(10); + + 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, 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: default mode`, t => { + t.plan(1); + + const template = withResultOfAvailabilityCheck({ + from: 'foo', + }); + + t.match(template.toDescription(), { + inputs: { + from: input.dependency('foo'), + mode: input.value('null'), + }, + }); +}); + +t.test(`withResultOfAvailabilityCheck: validate inputs`, t => { + t.plan(4); + + let caughtError; + + try { + caughtError = null; + withResultOfAvailabilityCheck({}); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + errors: [/Required these inputs: from/], + }); + + t.doesNotThrow(() => + withResultOfAvailabilityCheck({ + from: 'dependency1', + mode: 'dependency2', + })); + + t.doesNotThrow(() => + withResultOfAvailabilityCheck({ + from: input.value('static'), + mode: input.value('null'), + })); + + try { + caughtError = null; + withResultOfAvailabilityCheck({ + from: 'foo', + mode: input.value('invalid'), + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + errors: [ + { + message: /mode: Validation failed for static value/, + cause: /Expected one of null empty falsy, got invalid/, + }, + ], + }); +}); -- cgit 1.3.0-6-gf8a5 From b5cfc2a793f22da60606a4dd7387fcf3d3163843 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 25 Sep 2023 14:23:23 -0300 Subject: data: misc. improvements for input validation & infrastructure --- test/unit/data/composite/compositeFrom.js | 78 ++++++++++++++++++++-- .../composite/withResultOfAvailabilityCheck.js | 2 +- test/unit/data/things/track.js | 1 + 3 files changed, 74 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/compositeFrom.js b/test/unit/data/composite/compositeFrom.js index 06a66e61..00296675 100644 --- a/test/unit/data/composite/compositeFrom.js +++ b/test/unit/data/composite/compositeFrom.js @@ -98,12 +98,20 @@ t.test(`compositeFrom: dependencies from inputs`, t => { compose: true, - inputs: { - foo: input('bar'), - pomelo: input.value('delicious'), + inputMapping: { + foo: input('bar'), + pomelo: input.value('delicious'), humorous: input.dependency('#mammal'), - data: input.dependency('albumData'), - ref: input.updateValue(), + data: input.dependency('albumData'), + ref: input.updateValue(), + }, + + inputDescriptions: { + foo: input(), + pomelo: input(), + humorous: input(), + data: input(), + ref: input(), }, steps: [ @@ -233,13 +241,17 @@ t.test(`compositeFrom: update from various sources`, t => { t.plan(3); const composite = compositeFrom({ - inputs: { + inputMapping: { myInput: input.updateValue({ validate: isString, default: 'foo', }), }, + inputDescriptions: { + myInput: input(), + }, + steps: [ { dependencies: [input('myInput')], @@ -277,3 +289,57 @@ t.test(`compositeFrom: update from various sources`, t => { t.equal(continuationValue, 'Xx_foofoo_xX'); }); }); + +t.test(`compositeFrom: dynamic input validation from type`, t => { + t.plan(2); + + const composite = compositeFrom({ + inputMapping: { + string: input('string'), + number: input('number'), + boolean: input('boolean'), + function: input('function'), + object: input('object'), + array: input('array'), + }, + + inputDescriptions: { + string: input({null: true, type: 'string'}), + number: input({null: true, type: 'number'}), + boolean: input({null: true, type: 'boolean'}), + function: input({null: true, type: 'function'}), + object: input({null: true, type: 'object'}), + array: input({null: true, type: 'array'}), + }, + + outputs: {'#result': '#result'}, + + steps: [ + {compute: continuation => continuation({'#result': 'OK'})}, + ], + }); + + const notCalledSymbol = Symbol('continuation not called'); + + let continuationValue; + const continuation = value => { + continuationValue = value; + return continuationSymbol; + }; + + let thrownError; + + try { + continuationValue = notCalledSymbol; + thrownError = null; + composite.expose.compute(continuation, { + [input('string')]: 123, + }); + } catch (error) { + thrownError = error; + } + + t.equal(continuationValue, notCalledSymbol); + t.match(thrownError, { + }); +}); diff --git a/test/unit/data/composite/withResultOfAvailabilityCheck.js b/test/unit/data/composite/withResultOfAvailabilityCheck.js index f5229fb3..cac98d3c 100644 --- a/test/unit/data/composite/withResultOfAvailabilityCheck.js +++ b/test/unit/data/composite/withResultOfAvailabilityCheck.js @@ -103,7 +103,7 @@ t.test(`withResultOfAvailabilityCheck: default mode`, t => { }); t.match(template.toDescription(), { - inputs: { + inputMapping: { from: input.dependency('foo'), mode: input.value('null'), }, diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 5e7fd829..f68510c6 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -1,5 +1,6 @@ import t from 'tap'; +import {showAggregate} from '#sugar'; import {linkAndBindWikiData} from '#test-lib'; import thingConstructors from '#things'; -- cgit 1.3.0-6-gf8a5 From ef290302472bd66ff9823aad1a4e029a4b4e2eba Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 28 Sep 2023 14:13:09 -0300 Subject: test: templateCompositeFrom (WIP), various composite test updates --- test/unit/data/composite/exposeConstant.js | 38 ++-- test/unit/data/composite/exposeDependency.js | 38 ++-- test/unit/data/composite/templateCompositeFrom.js | 218 +++++++++++++++++++++ .../composite/withResultOfAvailabilityCheck.js | 81 +++++++- 4 files changed, 323 insertions(+), 52 deletions(-) create mode 100644 test/unit/data/composite/templateCompositeFrom.js (limited to 'test') diff --git a/test/unit/data/composite/exposeConstant.js b/test/unit/data/composite/exposeConstant.js index ce3f5e3d..829dc706 100644 --- a/test/unit/data/composite/exposeConstant.js +++ b/test/unit/data/composite/exposeConstant.js @@ -32,29 +32,21 @@ t.test(`exposeConstant: basic behavior`, t => { t.test(`exposeConstant: validate inputs`, t => { t.plan(2); - let caughtError; - - try { - caughtError = null; - exposeConstant({}); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - errors: [/Required these inputs: value/], - }); - - try { - caughtError = null; - exposeConstant({ - value: 'some dependency', + t.throws( + () => exposeConstant({}), + { + message: `Errors in input options passed to exposeConstant`, + errors: [ + {message: `Required these inputs: value`}, + ], }); - } catch (error) { - caughtError = error; - } - t.match(caughtError, { - errors: [/Expected static values: 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/exposeDependency.js b/test/unit/data/composite/exposeDependency.js index 7487e44c..78801343 100644 --- a/test/unit/data/composite/exposeDependency.js +++ b/test/unit/data/composite/exposeDependency.js @@ -52,29 +52,23 @@ t.test(`exposeDependency: basic behavior`, t => { t.test(`exposeDependency: validate inputs`, t => { t.plan(2); - let caughtError; - - try { - caughtError = null; - exposeDependency({}); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - errors: [/Required these inputs: dependency/], - }); + t.throws( + () => exposeDependency({}), + { + message: `Errors in input options passed to exposeDependency`, + errors: [ + {message: `Required these inputs: dependency`}, + ], + }); - try { - caughtError = null; - exposeDependency({ + 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`}, + ], }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - errors: [/Expected static dependencies: dependency/], - }); }); diff --git a/test/unit/data/composite/templateCompositeFrom.js b/test/unit/data/composite/templateCompositeFrom.js new file mode 100644 index 00000000..e96b782e --- /dev/null +++ b/test/unit/data/composite/templateCompositeFrom.js @@ -0,0 +1,218 @@ +import t from 'tap'; + +import {isString} from '#validators'; + +import { + compositeFrom, + continuationSymbol, + input, + templateCompositeFrom, +} from '#composite'; + +t.test(`templateCompositeFrom: basic behavior`, t => { + t.plan(1); + + const myCoolUtility = templateCompositeFrom({ + annotation: `myCoolUtility`, + + inputs: { + foo: input(), + }, + + outputs: ['#bar'], + + steps: () => [ + { + dependencies: [input('foo')], + compute: (continuation, { + [input('foo')]: foo, + }) => continuation({ + ['#bar']: (typeof foo).toUpperCase() + }), + }, + ], + }); + + const instantiatedTemplate = myCoolUtility({ + foo: 'color', + }); + + t.match(instantiatedTemplate.toDescription(), { + annotation: `myCoolUtility`, + + inputMapping: { + foo: input.dependency('color'), + }, + + inputDescriptions: { + foo: input(), + }, + + outputs: { + '#bar': '#bar', + }, + + steps: Function, + }); +}); + +t.test(`templateCompositeFrom: validate static input values`, t => { + t.plan(3); + + const stub = { + annotation: 'stubComposite', + outputs: ['#result'], + steps: () => [{compute: continuation => continuation({'#result': 'OK'})}], + }; + + const quickThrows = (t, composite, inputOptions, ...errorMessages) => + t.throws( + () => composite(inputOptions), + { + message: `Errors in input options passed to stubComposite`, + errors: errorMessages.map(message => ({message})), + }); + + t.test(`templateCompositeFrom: validate input token shapes`, t => { + t.plan(15); + + const template1 = templateCompositeFrom({ + ...stub, inputs: { + foo: input(), + }, + }); + + t.doesNotThrow( + () => template1({foo: 'dependency'})); + + t.doesNotThrow( + () => template1({foo: input.dependency('dependency')})); + + t.doesNotThrow( + () => template1({foo: input.value('static value')})); + + t.doesNotThrow( + () => template1({foo: input('outerInput')})); + + t.doesNotThrow( + () => template1({foo: input.updateValue()})); + + t.doesNotThrow( + () => template1({foo: input.myself()})); + + quickThrows(t, template1, + {foo: input.staticValue()}, + `foo: Expected dependency name or value-providing input() call, got input.staticValue`); + + quickThrows(t, template1, + {foo: input.staticDependency()}, + `foo: Expected dependency name or value-providing input() call, got input.staticDependency`); + + const template2 = templateCompositeFrom({ + ...stub, inputs: { + bar: input.staticDependency(), + }, + }); + + t.doesNotThrow( + () => template2({bar: 'dependency'})); + + t.doesNotThrow( + () => template2({bar: input.dependency('dependency')})); + + quickThrows(t, template2, + {bar: input.value(123)}, + `bar: Expected dependency name, got input.value`); + + quickThrows(t, template2, + {bar: input('outOfPlace')}, + `bar: Expected dependency name, got input`); + + const template3 = templateCompositeFrom({ + ...stub, inputs: { + baz: input.staticValue(), + }, + }); + + t.doesNotThrow( + () => template3({baz: input.value(1025)})); + + quickThrows(t, template3, + {baz: 'dependency'}, + `baz: Expected input.value() call, got dependency name`); + + quickThrows(t, template3, + {baz: input('outOfPlace')}, + `baz: Expected input.value() call, got input() call`); + }); + + t.test(`templateCompositeFrom: validate missing / misplaced inputs`, t => { + t.plan(1); + + const template = templateCompositeFrom({ + ...stub, inputs: { + foo: input(), + bar: input(), + }, + }); + + t.throws( + () => template({ + baz: 'aeiou', + raz: input.value(123), + }), + { + message: `Errors in input options passed to stubComposite`, + errors: [ + {message: `Unexpected input names: baz, raz`}, + {message: `Required these inputs: foo, bar`}, + ], + }); + }); + + t.test(`templateCompositeFrom: validate acceptsNull / defaultValue: null`, t => { + t.plan(3); + + const template1 = templateCompositeFrom({ + ...stub, inputs: { + foo: input(), + }, + }); + + t.throws( + () => template1({}), + { + message: `Errors in input options passed to stubComposite`, + errors: [ + {message: `Required these inputs: foo`}, + ], + }, + `throws if input missing and not marked specially`); + + const template2 = templateCompositeFrom({ + ...stub, inputs: { + bar: input({acceptsNull: true}), + }, + }); + + t.throws( + () => template2({}), + { + message: `Errors in input options passed to stubComposite`, + errors: [ + {message: `Required these inputs: bar`}, + ], + }, + `throws if input missing even if marked {acceptsNull}`); + + const template3 = templateCompositeFrom({ + ...stub, inputs: { + baz: input({defaultValue: null}), + }, + }); + + t.doesNotThrow( + () => template3({}), + `does not throw if input missing if marked {defaultValue: null}`); + }); +}); diff --git a/test/unit/data/composite/withResultOfAvailabilityCheck.js b/test/unit/data/composite/withResultOfAvailabilityCheck.js index cac98d3c..01220a3a 100644 --- a/test/unit/data/composite/withResultOfAvailabilityCheck.js +++ b/test/unit/data/composite/withResultOfAvailabilityCheck.js @@ -110,8 +110,8 @@ t.test(`withResultOfAvailabilityCheck: default mode`, t => { }); }); -t.test(`withResultOfAvailabilityCheck: validate inputs`, t => { - t.plan(4); +t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { + t.plan(5); let caughtError; @@ -134,7 +134,7 @@ t.test(`withResultOfAvailabilityCheck: validate inputs`, t => { t.doesNotThrow(() => withResultOfAvailabilityCheck({ - from: input.value('static'), + from: input.value('some static value'), mode: input.value('null'), })); @@ -149,11 +149,78 @@ t.test(`withResultOfAvailabilityCheck: validate inputs`, t => { } t.match(caughtError, { + message: /Errors in input options passed to withResultOfAvailabilityCheck/, errors: [ - { - message: /mode: Validation failed for static value/, - cause: /Expected one of null empty falsy, got invalid/, - }, + /mode: Expected one of null empty falsy, got invalid/, ], }); + + try { + caughtError = null; + withResultOfAvailabilityCheck({ + from: input.value(null), + mode: input.value(null), + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + message: /Errors in input options passed to withResultOfAvailabilityCheck/, + errors: [ + /mode: Expected value, got null/, + ], + }); +}); + +t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { + t.plan(2); + + let caughtError; + + try { + caughtError = null; + composite.expose.compute({ + from: 'apple', + mode: 'banana', + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + message: /Error computing composition/, + cause: { + message: /Error computing composition withResultOfAvailabilityCheck/, + cause: { + message: /Errors in input values provided to withResultOfAvailabilityCheck/, + errors: [ + /mode: Expected one of null empty falsy, got banana/, + ], + }, + }, + }); + + try { + caughtError = null; + composite.expose.compute({ + from: null, + mode: null, + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + message: /Error computing composition/, + cause: { + message: /Error computing composition withResultOfAvailabilityCheck/, + cause: { + message: /Errors in input values provided to withResultOfAvailabilityCheck/, + errors: [ + /mode: Expected value, got null/, + ], + }, + }, + }); }); -- cgit 1.3.0-6-gf8a5 From ac37f9db30d997d64de069a7b3b53c3474bdc413 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 28 Sep 2023 18:29:33 -0300 Subject: test: reorganize data tests a lil --- test/unit/data/cacheable-object.js | 270 ++++++++++++++++ .../composite/common-utilities/exposeConstant.js | 52 ++++ .../composite/common-utilities/exposeDependency.js | 74 +++++ .../withResultOfAvailabilityCheck.js | 226 ++++++++++++++ test/unit/data/composite/compositeFrom.js | 345 --------------------- test/unit/data/composite/exposeConstant.js | 52 ---- test/unit/data/composite/exposeDependency.js | 74 ----- test/unit/data/composite/templateCompositeFrom.js | 218 ------------- .../composite/withResultOfAvailabilityCheck.js | 226 -------------- test/unit/data/compositeFrom.js | 345 +++++++++++++++++++++ test/unit/data/templateCompositeFrom.js | 218 +++++++++++++ test/unit/data/things/cacheable-object.js | 270 ---------------- 12 files changed, 1185 insertions(+), 1185 deletions(-) create mode 100644 test/unit/data/cacheable-object.js create mode 100644 test/unit/data/composite/common-utilities/exposeConstant.js create mode 100644 test/unit/data/composite/common-utilities/exposeDependency.js create mode 100644 test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js delete mode 100644 test/unit/data/composite/compositeFrom.js delete mode 100644 test/unit/data/composite/exposeConstant.js delete mode 100644 test/unit/data/composite/exposeDependency.js delete mode 100644 test/unit/data/composite/templateCompositeFrom.js delete mode 100644 test/unit/data/composite/withResultOfAvailabilityCheck.js create mode 100644 test/unit/data/compositeFrom.js create mode 100644 test/unit/data/templateCompositeFrom.js delete mode 100644 test/unit/data/things/cacheable-object.js (limited to 'test') diff --git a/test/unit/data/cacheable-object.js b/test/unit/data/cacheable-object.js new file mode 100644 index 00000000..2e82af08 --- /dev/null +++ b/test/unit/data/cacheable-object.js @@ -0,0 +1,270 @@ +import t from 'tap'; + +import {CacheableObject} from '#things'; + +function newCacheableObject(PD) { + return new (class extends CacheableObject { + static propertyDescriptors = PD; + }); +} + +t.test(`CacheableObject simple separate update & expose`, t => { + const obj = newCacheableObject({ + number: { + flags: { + update: true + } + }, + + timesTwo: { + flags: { + expose: true + }, + + expose: { + dependencies: ['number'], + compute: ({ number }) => number * 2 + } + } + }); + + t.plan(1); + obj.number = 5; + t.equal(obj.timesTwo, 10); +}); + +t.test(`CacheableObject basic cache behavior`, t => { + let computeCount = 0; + + const obj = newCacheableObject({ + string: { + flags: { + update: true + } + }, + + karkat: { + flags: { + expose: true + }, + + expose: { + dependencies: ['string'], + compute: ({ string }) => { + computeCount++; + return string.toUpperCase(); + } + } + } + }); + + t.plan(8); + + t.equal(computeCount, 0); + + obj.string = 'hello world'; + t.equal(computeCount, 0); + + obj.karkat; + t.equal(computeCount, 1); + + obj.karkat; + t.equal(computeCount, 1); + + obj.string = 'testing once again'; + t.equal(computeCount, 1); + + obj.karkat; + t.equal(computeCount, 2); + + obj.string = 'testing once again'; + t.equal(computeCount, 2); + + obj.karkat; + t.equal(computeCount, 2); +}); + +t.test(`CacheableObject combined update & expose (no transform)`, t => { + const obj = newCacheableObject({ + directory: { + flags: { + update: true, + expose: true + } + } + }); + + t.plan(2); + + obj.directory = 'the-world-revolving'; + t.equal(obj.directory, 'the-world-revolving'); + + obj.directory = 'chaos-king'; + t.equal(obj.directory, 'chaos-king'); +}); + +t.test(`CacheableObject combined update & expose (basic transform)`, t => { + const obj = newCacheableObject({ + getsRepeated: { + flags: { + update: true, + expose: true + }, + + expose: { + transform: value => value.repeat(2) + } + } + }); + + t.plan(1); + + obj.getsRepeated = 'dog'; + t.equal(obj.getsRepeated, 'dogdog'); +}); + +t.test(`CacheableObject combined update & expose (transform with dependency)`, t => { + const obj = newCacheableObject({ + customRepeat: { + flags: { + update: true, + expose: true + }, + + expose: { + dependencies: ['times'], + transform: (value, { times }) => value.repeat(times) + } + }, + + times: { + flags: { + update: true + } + } + }); + + t.plan(3); + + obj.customRepeat = 'dog'; + obj.times = 1; + t.equal(obj.customRepeat, 'dog'); + + obj.times = 5; + t.equal(obj.customRepeat, 'dogdogdogdogdog'); + + obj.customRepeat = 'cat'; + t.equal(obj.customRepeat, 'catcatcatcatcat'); +}); + +t.test(`CacheableObject validate on update`, t => { + const mockError = new TypeError(`Expected a string, not ${typeof value}`); + + const obj = newCacheableObject({ + directory: { + flags: { + update: true, + expose: true + }, + + update: { + validate: value => { + if (typeof value !== 'string') { + throw mockError; + } + return true; + } + } + }, + + date: { + flags: { + update: true, + expose: true + }, + + update: { + validate: value => (value instanceof Date) + } + } + }); + + let thrownError; + t.plan(6); + + obj.directory = 'megalovania'; + t.equal(obj.directory, 'megalovania'); + + try { + obj.directory = 25; + } catch (err) { + thrownError = err; + } + + t.equal(thrownError, mockError); + t.equal(obj.directory, 'megalovania'); + + const date = new Date(`25 December 2009`); + + obj.date = date; + t.equal(obj.date, date); + + try { + obj.date = `TWELFTH PERIGEE'S EVE`; + } catch (err) { + thrownError = err; + } + + t.equal(thrownError?.constructor, TypeError); + t.equal(obj.date, date); +}); + +t.test(`CacheableObject default update property value`, t => { + const obj = newCacheableObject({ + fruit: { + flags: { + update: true, + expose: true + }, + + update: { + default: 'potassium' + } + } + }); + + t.plan(1); + t.equal(obj.fruit, 'potassium'); +}); + +t.test(`CacheableObject default property throws if invalid`, t => { + const mockError = new TypeError(`Expected a string, not ${typeof value}`); + + t.plan(1); + + let thrownError; + + try { + newCacheableObject({ + string: { + flags: { + update: true + }, + + update: { + default: 123, + validate: value => { + if (typeof value !== 'string') { + throw mockError; + } + return true; + } + } + } + }); + } catch (err) { + thrownError = err; + } + + t.equal(thrownError, mockError); +}); diff --git a/test/unit/data/composite/common-utilities/exposeConstant.js b/test/unit/data/composite/common-utilities/exposeConstant.js new file mode 100644 index 00000000..829dc706 --- /dev/null +++ b/test/unit/data/composite/common-utilities/exposeConstant.js @@ -0,0 +1,52 @@ +import t from 'tap'; + +import { + compositeFrom, + continuationSymbol, + exposeConstant, + input, +} from '#composite'; + +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/common-utilities/exposeDependency.js b/test/unit/data/composite/common-utilities/exposeDependency.js new file mode 100644 index 00000000..78801343 --- /dev/null +++ b/test/unit/data/composite/common-utilities/exposeDependency.js @@ -0,0 +1,74 @@ +import t from 'tap'; + +import { + compositeFrom, + continuationSymbol, + exposeDependency, + input, +} from '#composite'; + +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/common-utilities/withResultOfAvailabilityCheck.js b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js new file mode 100644 index 00000000..01220a3a --- /dev/null +++ b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js @@ -0,0 +1,226 @@ +import t from 'tap'; + +import { + compositeFrom, + continuationSymbol, + withResultOfAvailabilityCheck, + input, +} from '#composite'; + +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(10); + + 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: 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(10); + + 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: 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(10); + + 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, 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: 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); + + let caughtError; + + try { + caughtError = null; + withResultOfAvailabilityCheck({}); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + errors: [/Required these inputs: from/], + }); + + t.doesNotThrow(() => + withResultOfAvailabilityCheck({ + from: 'dependency1', + mode: 'dependency2', + })); + + t.doesNotThrow(() => + withResultOfAvailabilityCheck({ + from: input.value('some static value'), + mode: input.value('null'), + })); + + try { + caughtError = null; + withResultOfAvailabilityCheck({ + from: 'foo', + mode: input.value('invalid'), + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + message: /Errors in input options passed to withResultOfAvailabilityCheck/, + errors: [ + /mode: Expected one of null empty falsy, got invalid/, + ], + }); + + try { + caughtError = null; + withResultOfAvailabilityCheck({ + from: input.value(null), + mode: input.value(null), + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + message: /Errors in input options passed to withResultOfAvailabilityCheck/, + errors: [ + /mode: Expected value, got null/, + ], + }); +}); + +t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { + t.plan(2); + + let caughtError; + + try { + caughtError = null; + composite.expose.compute({ + from: 'apple', + mode: 'banana', + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + message: /Error computing composition/, + cause: { + message: /Error computing composition withResultOfAvailabilityCheck/, + cause: { + message: /Errors in input values provided to withResultOfAvailabilityCheck/, + errors: [ + /mode: Expected one of null empty falsy, got banana/, + ], + }, + }, + }); + + try { + caughtError = null; + composite.expose.compute({ + from: null, + mode: null, + }); + } catch (error) { + caughtError = error; + } + + t.match(caughtError, { + message: /Error computing composition/, + cause: { + message: /Error computing composition withResultOfAvailabilityCheck/, + cause: { + message: /Errors in input values provided to withResultOfAvailabilityCheck/, + errors: [ + /mode: Expected value, got null/, + ], + }, + }, + }); +}); diff --git a/test/unit/data/composite/compositeFrom.js b/test/unit/data/composite/compositeFrom.js deleted file mode 100644 index 00296675..00000000 --- a/test/unit/data/composite/compositeFrom.js +++ /dev/null @@ -1,345 +0,0 @@ -import t from 'tap'; - -import {compositeFrom, continuationSymbol, input} from '#composite'; -import {isString} from '#validators'; - -t.test(`compositeFrom: basic behavior`, t => { - t.plan(2); - - const composite = compositeFrom({ - annotation: `myComposite`, - compose: false, - - steps: [ - { - dependencies: ['foo'], - compute: (continuation, {foo}) => - continuation({'#bar': foo * 2}), - }, - - { - dependencies: ['#bar', 'baz', 'suffix'], - compute: ({'#bar': bar, baz, suffix}) => - baz.repeat(bar) + suffix, - }, - ], - }); - - t.match(composite, { - annotation: `myComposite`, - - flags: {expose: true, compose: false, update: false}, - - expose: { - dependencies: ['foo', 'baz', 'suffix'], - compute: Function, - transform: null, - }, - - update: null, - }); - - t.equal( - composite.expose.compute({ - foo: 3, - baz: 'ba', - suffix: 'BOOM', - }), - 'babababababaBOOM'); -}); - -t.test(`compositeFrom: input-shaped step dependencies`, t => { - t.plan(2); - - const composite = compositeFrom({ - compose: false, - steps: [ - { - dependencies: [ - input.myself(), - input.updateValue(), - ], - - transform: (updateValue1, { - [input.myself()]: me, - [input.updateValue()]: updateValue2, - }) => ({me, updateValue1, updateValue2}), - }, - ], - }); - - t.match(composite, { - expose: { - dependencies: ['this'], - transform: Function, - compute: null, - }, - }); - - const myself = {foo: 'bar'}; - - t.same( - composite.expose.transform('banana', { - this: myself, - pomelo: 'delicious', - }), - { - me: myself, - updateValue1: 'banana', - updateValue2: 'banana', - }); -}); - -t.test(`compositeFrom: dependencies from inputs`, t => { - t.plan(3); - - const composite = compositeFrom({ - annotation: `myComposite`, - - compose: true, - - inputMapping: { - foo: input('bar'), - pomelo: input.value('delicious'), - humorous: input.dependency('#mammal'), - data: input.dependency('albumData'), - ref: input.updateValue(), - }, - - inputDescriptions: { - foo: input(), - pomelo: input(), - humorous: input(), - data: input(), - ref: input(), - }, - - steps: [ - { - dependencies: [ - input('foo'), - input('pomelo'), - input('humorous'), - input('data'), - input('ref'), - ], - - compute: (continuation, { - [input('foo')]: foo, - [input('pomelo')]: pomelo, - [input('humorous')]: humorous, - [input('data')]: data, - [input('ref')]: ref, - }) => continuation.exit({foo, pomelo, humorous, data, ref}), - }, - ], - }); - - t.match(composite, { - expose: { - dependencies: [ - input('bar'), - '#mammal', - 'albumData', - ], - - transform: Function, - compute: null, - }, - }); - - const exitData = {}; - const continuation = { - exit(value) { - Object.assign(exitData, value); - return continuationSymbol; - }, - }; - - t.equal( - composite.expose.transform('album:bepis', continuation, { - [input('bar')]: 'squid time', - '#mammal': 'fox', - 'albumData': ['album1', 'album2'], - }), - continuationSymbol); - - t.same(exitData, { - foo: 'squid time', - pomelo: 'delicious', - humorous: 'fox', - data: ['album1', 'album2'], - ref: 'album:bepis', - }); -}); - -t.test(`compositeFrom: update from various sources`, t => { - t.plan(3); - - const match = { - flags: {update: true, expose: true, compose: false}, - - update: { - validate: isString, - default: 'foo', - }, - - expose: { - transform: Function, - compute: null, - }, - }; - - t.test(`compositeFrom: update from composition description`, t => { - t.plan(2); - - const composite = compositeFrom({ - compose: false, - - update: { - validate: isString, - default: 'foo', - }, - - steps: [ - {transform: (value, continuation) => continuation(value.repeat(2))}, - {transform: (value) => `Xx_${value}_xX`}, - ], - }); - - t.match(composite, match); - t.equal(composite.expose.transform('foo'), `Xx_foofoo_xX`); - }); - - t.test(`compositeFrom: update from step dependencies`, t => { - t.plan(2); - - const composite = compositeFrom({ - compose: false, - - steps: [ - { - dependencies: [ - input.updateValue({ - validate: isString, - default: 'foo', - }), - ], - - compute: ({ - [input.updateValue()]: value, - }) => `Xx_${value.repeat(2)}_xX`, - }, - ], - }); - - t.match(composite, match); - t.equal(composite.expose.transform('foo'), 'Xx_foofoo_xX'); - }); - - t.test(`compositeFrom: update from inputs`, t => { - t.plan(3); - - const composite = compositeFrom({ - inputMapping: { - myInput: input.updateValue({ - validate: isString, - default: 'foo', - }), - }, - - inputDescriptions: { - myInput: input(), - }, - - steps: [ - { - dependencies: [input('myInput')], - compute: (continuation, { - [input('myInput')]: value, - }) => continuation({ - '#value': `Xx_${value.repeat(2)}_xX`, - }), - }, - - { - dependencies: ['#value'], - transform: (_value, continuation, {'#value': value}) => - continuation(value), - }, - ], - }); - - let continuationValue = null; - const continuation = value => { - continuationValue = value; - return continuationSymbol; - }; - - t.match(composite, { - ...match, - - flags: {update: true, expose: true, compose: true}, - }); - - t.equal( - composite.expose.transform('foo', continuation), - continuationSymbol); - - t.equal(continuationValue, 'Xx_foofoo_xX'); - }); -}); - -t.test(`compositeFrom: dynamic input validation from type`, t => { - t.plan(2); - - const composite = compositeFrom({ - inputMapping: { - string: input('string'), - number: input('number'), - boolean: input('boolean'), - function: input('function'), - object: input('object'), - array: input('array'), - }, - - inputDescriptions: { - string: input({null: true, type: 'string'}), - number: input({null: true, type: 'number'}), - boolean: input({null: true, type: 'boolean'}), - function: input({null: true, type: 'function'}), - object: input({null: true, type: 'object'}), - array: input({null: true, type: 'array'}), - }, - - outputs: {'#result': '#result'}, - - steps: [ - {compute: continuation => continuation({'#result': 'OK'})}, - ], - }); - - const notCalledSymbol = Symbol('continuation not called'); - - let continuationValue; - const continuation = value => { - continuationValue = value; - return continuationSymbol; - }; - - let thrownError; - - try { - continuationValue = notCalledSymbol; - thrownError = null; - composite.expose.compute(continuation, { - [input('string')]: 123, - }); - } catch (error) { - thrownError = error; - } - - t.equal(continuationValue, notCalledSymbol); - t.match(thrownError, { - }); -}); diff --git a/test/unit/data/composite/exposeConstant.js b/test/unit/data/composite/exposeConstant.js deleted file mode 100644 index 829dc706..00000000 --- a/test/unit/data/composite/exposeConstant.js +++ /dev/null @@ -1,52 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - continuationSymbol, - exposeConstant, - input, -} from '#composite'; - -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/exposeDependency.js b/test/unit/data/composite/exposeDependency.js deleted file mode 100644 index 78801343..00000000 --- a/test/unit/data/composite/exposeDependency.js +++ /dev/null @@ -1,74 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - continuationSymbol, - exposeDependency, - input, -} from '#composite'; - -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/templateCompositeFrom.js b/test/unit/data/composite/templateCompositeFrom.js deleted file mode 100644 index e96b782e..00000000 --- a/test/unit/data/composite/templateCompositeFrom.js +++ /dev/null @@ -1,218 +0,0 @@ -import t from 'tap'; - -import {isString} from '#validators'; - -import { - compositeFrom, - continuationSymbol, - input, - templateCompositeFrom, -} from '#composite'; - -t.test(`templateCompositeFrom: basic behavior`, t => { - t.plan(1); - - const myCoolUtility = templateCompositeFrom({ - annotation: `myCoolUtility`, - - inputs: { - foo: input(), - }, - - outputs: ['#bar'], - - steps: () => [ - { - dependencies: [input('foo')], - compute: (continuation, { - [input('foo')]: foo, - }) => continuation({ - ['#bar']: (typeof foo).toUpperCase() - }), - }, - ], - }); - - const instantiatedTemplate = myCoolUtility({ - foo: 'color', - }); - - t.match(instantiatedTemplate.toDescription(), { - annotation: `myCoolUtility`, - - inputMapping: { - foo: input.dependency('color'), - }, - - inputDescriptions: { - foo: input(), - }, - - outputs: { - '#bar': '#bar', - }, - - steps: Function, - }); -}); - -t.test(`templateCompositeFrom: validate static input values`, t => { - t.plan(3); - - const stub = { - annotation: 'stubComposite', - outputs: ['#result'], - steps: () => [{compute: continuation => continuation({'#result': 'OK'})}], - }; - - const quickThrows = (t, composite, inputOptions, ...errorMessages) => - t.throws( - () => composite(inputOptions), - { - message: `Errors in input options passed to stubComposite`, - errors: errorMessages.map(message => ({message})), - }); - - t.test(`templateCompositeFrom: validate input token shapes`, t => { - t.plan(15); - - const template1 = templateCompositeFrom({ - ...stub, inputs: { - foo: input(), - }, - }); - - t.doesNotThrow( - () => template1({foo: 'dependency'})); - - t.doesNotThrow( - () => template1({foo: input.dependency('dependency')})); - - t.doesNotThrow( - () => template1({foo: input.value('static value')})); - - t.doesNotThrow( - () => template1({foo: input('outerInput')})); - - t.doesNotThrow( - () => template1({foo: input.updateValue()})); - - t.doesNotThrow( - () => template1({foo: input.myself()})); - - quickThrows(t, template1, - {foo: input.staticValue()}, - `foo: Expected dependency name or value-providing input() call, got input.staticValue`); - - quickThrows(t, template1, - {foo: input.staticDependency()}, - `foo: Expected dependency name or value-providing input() call, got input.staticDependency`); - - const template2 = templateCompositeFrom({ - ...stub, inputs: { - bar: input.staticDependency(), - }, - }); - - t.doesNotThrow( - () => template2({bar: 'dependency'})); - - t.doesNotThrow( - () => template2({bar: input.dependency('dependency')})); - - quickThrows(t, template2, - {bar: input.value(123)}, - `bar: Expected dependency name, got input.value`); - - quickThrows(t, template2, - {bar: input('outOfPlace')}, - `bar: Expected dependency name, got input`); - - const template3 = templateCompositeFrom({ - ...stub, inputs: { - baz: input.staticValue(), - }, - }); - - t.doesNotThrow( - () => template3({baz: input.value(1025)})); - - quickThrows(t, template3, - {baz: 'dependency'}, - `baz: Expected input.value() call, got dependency name`); - - quickThrows(t, template3, - {baz: input('outOfPlace')}, - `baz: Expected input.value() call, got input() call`); - }); - - t.test(`templateCompositeFrom: validate missing / misplaced inputs`, t => { - t.plan(1); - - const template = templateCompositeFrom({ - ...stub, inputs: { - foo: input(), - bar: input(), - }, - }); - - t.throws( - () => template({ - baz: 'aeiou', - raz: input.value(123), - }), - { - message: `Errors in input options passed to stubComposite`, - errors: [ - {message: `Unexpected input names: baz, raz`}, - {message: `Required these inputs: foo, bar`}, - ], - }); - }); - - t.test(`templateCompositeFrom: validate acceptsNull / defaultValue: null`, t => { - t.plan(3); - - const template1 = templateCompositeFrom({ - ...stub, inputs: { - foo: input(), - }, - }); - - t.throws( - () => template1({}), - { - message: `Errors in input options passed to stubComposite`, - errors: [ - {message: `Required these inputs: foo`}, - ], - }, - `throws if input missing and not marked specially`); - - const template2 = templateCompositeFrom({ - ...stub, inputs: { - bar: input({acceptsNull: true}), - }, - }); - - t.throws( - () => template2({}), - { - message: `Errors in input options passed to stubComposite`, - errors: [ - {message: `Required these inputs: bar`}, - ], - }, - `throws if input missing even if marked {acceptsNull}`); - - const template3 = templateCompositeFrom({ - ...stub, inputs: { - baz: input({defaultValue: null}), - }, - }); - - t.doesNotThrow( - () => template3({}), - `does not throw if input missing if marked {defaultValue: null}`); - }); -}); diff --git a/test/unit/data/composite/withResultOfAvailabilityCheck.js b/test/unit/data/composite/withResultOfAvailabilityCheck.js deleted file mode 100644 index 01220a3a..00000000 --- a/test/unit/data/composite/withResultOfAvailabilityCheck.js +++ /dev/null @@ -1,226 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - continuationSymbol, - withResultOfAvailabilityCheck, - input, -} from '#composite'; - -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(10); - - 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: 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(10); - - 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: 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(10); - - 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, 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: 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); - - let caughtError; - - try { - caughtError = null; - withResultOfAvailabilityCheck({}); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - errors: [/Required these inputs: from/], - }); - - t.doesNotThrow(() => - withResultOfAvailabilityCheck({ - from: 'dependency1', - mode: 'dependency2', - })); - - t.doesNotThrow(() => - withResultOfAvailabilityCheck({ - from: input.value('some static value'), - mode: input.value('null'), - })); - - try { - caughtError = null; - withResultOfAvailabilityCheck({ - from: 'foo', - mode: input.value('invalid'), - }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Errors in input options passed to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected one of null empty falsy, got invalid/, - ], - }); - - try { - caughtError = null; - withResultOfAvailabilityCheck({ - from: input.value(null), - mode: input.value(null), - }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Errors in input options passed to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected value, got null/, - ], - }); -}); - -t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { - t.plan(2); - - let caughtError; - - try { - caughtError = null; - composite.expose.compute({ - from: 'apple', - mode: 'banana', - }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Error computing composition/, - cause: { - message: /Error computing composition withResultOfAvailabilityCheck/, - cause: { - message: /Errors in input values provided to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected one of null empty falsy, got banana/, - ], - }, - }, - }); - - try { - caughtError = null; - composite.expose.compute({ - from: null, - mode: null, - }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Error computing composition/, - cause: { - message: /Error computing composition withResultOfAvailabilityCheck/, - cause: { - message: /Errors in input values provided to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected value, got null/, - ], - }, - }, - }); -}); diff --git a/test/unit/data/compositeFrom.js b/test/unit/data/compositeFrom.js new file mode 100644 index 00000000..00296675 --- /dev/null +++ b/test/unit/data/compositeFrom.js @@ -0,0 +1,345 @@ +import t from 'tap'; + +import {compositeFrom, continuationSymbol, input} from '#composite'; +import {isString} from '#validators'; + +t.test(`compositeFrom: basic behavior`, t => { + t.plan(2); + + const composite = compositeFrom({ + annotation: `myComposite`, + compose: false, + + steps: [ + { + dependencies: ['foo'], + compute: (continuation, {foo}) => + continuation({'#bar': foo * 2}), + }, + + { + dependencies: ['#bar', 'baz', 'suffix'], + compute: ({'#bar': bar, baz, suffix}) => + baz.repeat(bar) + suffix, + }, + ], + }); + + t.match(composite, { + annotation: `myComposite`, + + flags: {expose: true, compose: false, update: false}, + + expose: { + dependencies: ['foo', 'baz', 'suffix'], + compute: Function, + transform: null, + }, + + update: null, + }); + + t.equal( + composite.expose.compute({ + foo: 3, + baz: 'ba', + suffix: 'BOOM', + }), + 'babababababaBOOM'); +}); + +t.test(`compositeFrom: input-shaped step dependencies`, t => { + t.plan(2); + + const composite = compositeFrom({ + compose: false, + steps: [ + { + dependencies: [ + input.myself(), + input.updateValue(), + ], + + transform: (updateValue1, { + [input.myself()]: me, + [input.updateValue()]: updateValue2, + }) => ({me, updateValue1, updateValue2}), + }, + ], + }); + + t.match(composite, { + expose: { + dependencies: ['this'], + transform: Function, + compute: null, + }, + }); + + const myself = {foo: 'bar'}; + + t.same( + composite.expose.transform('banana', { + this: myself, + pomelo: 'delicious', + }), + { + me: myself, + updateValue1: 'banana', + updateValue2: 'banana', + }); +}); + +t.test(`compositeFrom: dependencies from inputs`, t => { + t.plan(3); + + const composite = compositeFrom({ + annotation: `myComposite`, + + compose: true, + + inputMapping: { + foo: input('bar'), + pomelo: input.value('delicious'), + humorous: input.dependency('#mammal'), + data: input.dependency('albumData'), + ref: input.updateValue(), + }, + + inputDescriptions: { + foo: input(), + pomelo: input(), + humorous: input(), + data: input(), + ref: input(), + }, + + steps: [ + { + dependencies: [ + input('foo'), + input('pomelo'), + input('humorous'), + input('data'), + input('ref'), + ], + + compute: (continuation, { + [input('foo')]: foo, + [input('pomelo')]: pomelo, + [input('humorous')]: humorous, + [input('data')]: data, + [input('ref')]: ref, + }) => continuation.exit({foo, pomelo, humorous, data, ref}), + }, + ], + }); + + t.match(composite, { + expose: { + dependencies: [ + input('bar'), + '#mammal', + 'albumData', + ], + + transform: Function, + compute: null, + }, + }); + + const exitData = {}; + const continuation = { + exit(value) { + Object.assign(exitData, value); + return continuationSymbol; + }, + }; + + t.equal( + composite.expose.transform('album:bepis', continuation, { + [input('bar')]: 'squid time', + '#mammal': 'fox', + 'albumData': ['album1', 'album2'], + }), + continuationSymbol); + + t.same(exitData, { + foo: 'squid time', + pomelo: 'delicious', + humorous: 'fox', + data: ['album1', 'album2'], + ref: 'album:bepis', + }); +}); + +t.test(`compositeFrom: update from various sources`, t => { + t.plan(3); + + const match = { + flags: {update: true, expose: true, compose: false}, + + update: { + validate: isString, + default: 'foo', + }, + + expose: { + transform: Function, + compute: null, + }, + }; + + t.test(`compositeFrom: update from composition description`, t => { + t.plan(2); + + const composite = compositeFrom({ + compose: false, + + update: { + validate: isString, + default: 'foo', + }, + + steps: [ + {transform: (value, continuation) => continuation(value.repeat(2))}, + {transform: (value) => `Xx_${value}_xX`}, + ], + }); + + t.match(composite, match); + t.equal(composite.expose.transform('foo'), `Xx_foofoo_xX`); + }); + + t.test(`compositeFrom: update from step dependencies`, t => { + t.plan(2); + + const composite = compositeFrom({ + compose: false, + + steps: [ + { + dependencies: [ + input.updateValue({ + validate: isString, + default: 'foo', + }), + ], + + compute: ({ + [input.updateValue()]: value, + }) => `Xx_${value.repeat(2)}_xX`, + }, + ], + }); + + t.match(composite, match); + t.equal(composite.expose.transform('foo'), 'Xx_foofoo_xX'); + }); + + t.test(`compositeFrom: update from inputs`, t => { + t.plan(3); + + const composite = compositeFrom({ + inputMapping: { + myInput: input.updateValue({ + validate: isString, + default: 'foo', + }), + }, + + inputDescriptions: { + myInput: input(), + }, + + steps: [ + { + dependencies: [input('myInput')], + compute: (continuation, { + [input('myInput')]: value, + }) => continuation({ + '#value': `Xx_${value.repeat(2)}_xX`, + }), + }, + + { + dependencies: ['#value'], + transform: (_value, continuation, {'#value': value}) => + continuation(value), + }, + ], + }); + + let continuationValue = null; + const continuation = value => { + continuationValue = value; + return continuationSymbol; + }; + + t.match(composite, { + ...match, + + flags: {update: true, expose: true, compose: true}, + }); + + t.equal( + composite.expose.transform('foo', continuation), + continuationSymbol); + + t.equal(continuationValue, 'Xx_foofoo_xX'); + }); +}); + +t.test(`compositeFrom: dynamic input validation from type`, t => { + t.plan(2); + + const composite = compositeFrom({ + inputMapping: { + string: input('string'), + number: input('number'), + boolean: input('boolean'), + function: input('function'), + object: input('object'), + array: input('array'), + }, + + inputDescriptions: { + string: input({null: true, type: 'string'}), + number: input({null: true, type: 'number'}), + boolean: input({null: true, type: 'boolean'}), + function: input({null: true, type: 'function'}), + object: input({null: true, type: 'object'}), + array: input({null: true, type: 'array'}), + }, + + outputs: {'#result': '#result'}, + + steps: [ + {compute: continuation => continuation({'#result': 'OK'})}, + ], + }); + + const notCalledSymbol = Symbol('continuation not called'); + + let continuationValue; + const continuation = value => { + continuationValue = value; + return continuationSymbol; + }; + + let thrownError; + + try { + continuationValue = notCalledSymbol; + thrownError = null; + composite.expose.compute(continuation, { + [input('string')]: 123, + }); + } catch (error) { + thrownError = error; + } + + t.equal(continuationValue, notCalledSymbol); + t.match(thrownError, { + }); +}); diff --git a/test/unit/data/templateCompositeFrom.js b/test/unit/data/templateCompositeFrom.js new file mode 100644 index 00000000..e96b782e --- /dev/null +++ b/test/unit/data/templateCompositeFrom.js @@ -0,0 +1,218 @@ +import t from 'tap'; + +import {isString} from '#validators'; + +import { + compositeFrom, + continuationSymbol, + input, + templateCompositeFrom, +} from '#composite'; + +t.test(`templateCompositeFrom: basic behavior`, t => { + t.plan(1); + + const myCoolUtility = templateCompositeFrom({ + annotation: `myCoolUtility`, + + inputs: { + foo: input(), + }, + + outputs: ['#bar'], + + steps: () => [ + { + dependencies: [input('foo')], + compute: (continuation, { + [input('foo')]: foo, + }) => continuation({ + ['#bar']: (typeof foo).toUpperCase() + }), + }, + ], + }); + + const instantiatedTemplate = myCoolUtility({ + foo: 'color', + }); + + t.match(instantiatedTemplate.toDescription(), { + annotation: `myCoolUtility`, + + inputMapping: { + foo: input.dependency('color'), + }, + + inputDescriptions: { + foo: input(), + }, + + outputs: { + '#bar': '#bar', + }, + + steps: Function, + }); +}); + +t.test(`templateCompositeFrom: validate static input values`, t => { + t.plan(3); + + const stub = { + annotation: 'stubComposite', + outputs: ['#result'], + steps: () => [{compute: continuation => continuation({'#result': 'OK'})}], + }; + + const quickThrows = (t, composite, inputOptions, ...errorMessages) => + t.throws( + () => composite(inputOptions), + { + message: `Errors in input options passed to stubComposite`, + errors: errorMessages.map(message => ({message})), + }); + + t.test(`templateCompositeFrom: validate input token shapes`, t => { + t.plan(15); + + const template1 = templateCompositeFrom({ + ...stub, inputs: { + foo: input(), + }, + }); + + t.doesNotThrow( + () => template1({foo: 'dependency'})); + + t.doesNotThrow( + () => template1({foo: input.dependency('dependency')})); + + t.doesNotThrow( + () => template1({foo: input.value('static value')})); + + t.doesNotThrow( + () => template1({foo: input('outerInput')})); + + t.doesNotThrow( + () => template1({foo: input.updateValue()})); + + t.doesNotThrow( + () => template1({foo: input.myself()})); + + quickThrows(t, template1, + {foo: input.staticValue()}, + `foo: Expected dependency name or value-providing input() call, got input.staticValue`); + + quickThrows(t, template1, + {foo: input.staticDependency()}, + `foo: Expected dependency name or value-providing input() call, got input.staticDependency`); + + const template2 = templateCompositeFrom({ + ...stub, inputs: { + bar: input.staticDependency(), + }, + }); + + t.doesNotThrow( + () => template2({bar: 'dependency'})); + + t.doesNotThrow( + () => template2({bar: input.dependency('dependency')})); + + quickThrows(t, template2, + {bar: input.value(123)}, + `bar: Expected dependency name, got input.value`); + + quickThrows(t, template2, + {bar: input('outOfPlace')}, + `bar: Expected dependency name, got input`); + + const template3 = templateCompositeFrom({ + ...stub, inputs: { + baz: input.staticValue(), + }, + }); + + t.doesNotThrow( + () => template3({baz: input.value(1025)})); + + quickThrows(t, template3, + {baz: 'dependency'}, + `baz: Expected input.value() call, got dependency name`); + + quickThrows(t, template3, + {baz: input('outOfPlace')}, + `baz: Expected input.value() call, got input() call`); + }); + + t.test(`templateCompositeFrom: validate missing / misplaced inputs`, t => { + t.plan(1); + + const template = templateCompositeFrom({ + ...stub, inputs: { + foo: input(), + bar: input(), + }, + }); + + t.throws( + () => template({ + baz: 'aeiou', + raz: input.value(123), + }), + { + message: `Errors in input options passed to stubComposite`, + errors: [ + {message: `Unexpected input names: baz, raz`}, + {message: `Required these inputs: foo, bar`}, + ], + }); + }); + + t.test(`templateCompositeFrom: validate acceptsNull / defaultValue: null`, t => { + t.plan(3); + + const template1 = templateCompositeFrom({ + ...stub, inputs: { + foo: input(), + }, + }); + + t.throws( + () => template1({}), + { + message: `Errors in input options passed to stubComposite`, + errors: [ + {message: `Required these inputs: foo`}, + ], + }, + `throws if input missing and not marked specially`); + + const template2 = templateCompositeFrom({ + ...stub, inputs: { + bar: input({acceptsNull: true}), + }, + }); + + t.throws( + () => template2({}), + { + message: `Errors in input options passed to stubComposite`, + errors: [ + {message: `Required these inputs: bar`}, + ], + }, + `throws if input missing even if marked {acceptsNull}`); + + const template3 = templateCompositeFrom({ + ...stub, inputs: { + baz: input({defaultValue: null}), + }, + }); + + t.doesNotThrow( + () => template3({}), + `does not throw if input missing if marked {defaultValue: null}`); + }); +}); diff --git a/test/unit/data/things/cacheable-object.js b/test/unit/data/things/cacheable-object.js deleted file mode 100644 index 2e82af08..00000000 --- a/test/unit/data/things/cacheable-object.js +++ /dev/null @@ -1,270 +0,0 @@ -import t from 'tap'; - -import {CacheableObject} from '#things'; - -function newCacheableObject(PD) { - return new (class extends CacheableObject { - static propertyDescriptors = PD; - }); -} - -t.test(`CacheableObject simple separate update & expose`, t => { - const obj = newCacheableObject({ - number: { - flags: { - update: true - } - }, - - timesTwo: { - flags: { - expose: true - }, - - expose: { - dependencies: ['number'], - compute: ({ number }) => number * 2 - } - } - }); - - t.plan(1); - obj.number = 5; - t.equal(obj.timesTwo, 10); -}); - -t.test(`CacheableObject basic cache behavior`, t => { - let computeCount = 0; - - const obj = newCacheableObject({ - string: { - flags: { - update: true - } - }, - - karkat: { - flags: { - expose: true - }, - - expose: { - dependencies: ['string'], - compute: ({ string }) => { - computeCount++; - return string.toUpperCase(); - } - } - } - }); - - t.plan(8); - - t.equal(computeCount, 0); - - obj.string = 'hello world'; - t.equal(computeCount, 0); - - obj.karkat; - t.equal(computeCount, 1); - - obj.karkat; - t.equal(computeCount, 1); - - obj.string = 'testing once again'; - t.equal(computeCount, 1); - - obj.karkat; - t.equal(computeCount, 2); - - obj.string = 'testing once again'; - t.equal(computeCount, 2); - - obj.karkat; - t.equal(computeCount, 2); -}); - -t.test(`CacheableObject combined update & expose (no transform)`, t => { - const obj = newCacheableObject({ - directory: { - flags: { - update: true, - expose: true - } - } - }); - - t.plan(2); - - obj.directory = 'the-world-revolving'; - t.equal(obj.directory, 'the-world-revolving'); - - obj.directory = 'chaos-king'; - t.equal(obj.directory, 'chaos-king'); -}); - -t.test(`CacheableObject combined update & expose (basic transform)`, t => { - const obj = newCacheableObject({ - getsRepeated: { - flags: { - update: true, - expose: true - }, - - expose: { - transform: value => value.repeat(2) - } - } - }); - - t.plan(1); - - obj.getsRepeated = 'dog'; - t.equal(obj.getsRepeated, 'dogdog'); -}); - -t.test(`CacheableObject combined update & expose (transform with dependency)`, t => { - const obj = newCacheableObject({ - customRepeat: { - flags: { - update: true, - expose: true - }, - - expose: { - dependencies: ['times'], - transform: (value, { times }) => value.repeat(times) - } - }, - - times: { - flags: { - update: true - } - } - }); - - t.plan(3); - - obj.customRepeat = 'dog'; - obj.times = 1; - t.equal(obj.customRepeat, 'dog'); - - obj.times = 5; - t.equal(obj.customRepeat, 'dogdogdogdogdog'); - - obj.customRepeat = 'cat'; - t.equal(obj.customRepeat, 'catcatcatcatcat'); -}); - -t.test(`CacheableObject validate on update`, t => { - const mockError = new TypeError(`Expected a string, not ${typeof value}`); - - const obj = newCacheableObject({ - directory: { - flags: { - update: true, - expose: true - }, - - update: { - validate: value => { - if (typeof value !== 'string') { - throw mockError; - } - return true; - } - } - }, - - date: { - flags: { - update: true, - expose: true - }, - - update: { - validate: value => (value instanceof Date) - } - } - }); - - let thrownError; - t.plan(6); - - obj.directory = 'megalovania'; - t.equal(obj.directory, 'megalovania'); - - try { - obj.directory = 25; - } catch (err) { - thrownError = err; - } - - t.equal(thrownError, mockError); - t.equal(obj.directory, 'megalovania'); - - const date = new Date(`25 December 2009`); - - obj.date = date; - t.equal(obj.date, date); - - try { - obj.date = `TWELFTH PERIGEE'S EVE`; - } catch (err) { - thrownError = err; - } - - t.equal(thrownError?.constructor, TypeError); - t.equal(obj.date, date); -}); - -t.test(`CacheableObject default update property value`, t => { - const obj = newCacheableObject({ - fruit: { - flags: { - update: true, - expose: true - }, - - update: { - default: 'potassium' - } - } - }); - - t.plan(1); - t.equal(obj.fruit, 'potassium'); -}); - -t.test(`CacheableObject default property throws if invalid`, t => { - const mockError = new TypeError(`Expected a string, not ${typeof value}`); - - t.plan(1); - - let thrownError; - - try { - newCacheableObject({ - string: { - flags: { - update: true - }, - - update: { - default: 123, - validate: value => { - if (typeof value !== 'string') { - throw mockError; - } - return true; - } - } - } - }); - } catch (err) { - thrownError = err; - } - - t.equal(thrownError, mockError); -}); -- cgit 1.3.0-6-gf8a5 From b606bb424026597560144750d8dcf7b3e2601755 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 28 Sep 2023 19:53:18 -0300 Subject: test: withPropertyFromObject (WIP) --- .../common-utilities/withPropertyFromObject.js | 124 +++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 test/unit/data/composite/common-utilities/withPropertyFromObject.js (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withPropertyFromObject.js b/test/unit/data/composite/common-utilities/withPropertyFromObject.js new file mode 100644 index 00000000..c0e3dccf --- /dev/null +++ b/test/unit/data/composite/common-utilities/withPropertyFromObject.js @@ -0,0 +1,124 @@ +import t from 'tap'; + +import { + compositeFrom, + exposeDependency, + input, + withPropertyFromObject, +} from '#composite'; + +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: output shapes & values`, t => { + t.plan(3 * 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 map = [ + ['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, submap] of map) { + for (const [propertyInput, dict] of submap) { + const step = withPropertyFromObject({ + object: objectInput, + property: propertyInput, + }); + + t.same( + Object.keys(step.toDescription().outputs), + Object.keys(dict)); + + const composite = compositeFrom({ + compose: false, + + steps: [ + step, + + { + dependencies: Object.keys(dict), + compute: dependencies => dependencies, + }, + ], + }); + + t.same(composite.expose.compute(dependencies), dict); + } + } +}); -- cgit 1.3.0-6-gf8a5 From bc0f3fe427626bb3253c790898770f16701a512a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 29 Sep 2023 10:02:37 -0300 Subject: test: tidy output-checking syntax --- .../common-utilities/withPropertyFromObject.js | 41 +++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withPropertyFromObject.js b/test/unit/data/composite/common-utilities/withPropertyFromObject.js index c0e3dccf..11487226 100644 --- a/test/unit/data/composite/common-utilities/withPropertyFromObject.js +++ b/test/unit/data/composite/common-utilities/withPropertyFromObject.js @@ -46,7 +46,7 @@ t.test(`withPropertyFromObject: basic behavior`, t => { }); t.test(`withPropertyFromObject: output shapes & values`, t => { - t.plan(3 * 3 * 2); + t.plan(2 * 3 ** 2); const dependencies = { ['object_dependency']: @@ -59,7 +59,7 @@ t.test(`withPropertyFromObject: output shapes & values`, t => { 'baz', }; - const map = [ + const mapLevel1 = [ ['object_dependency', [ ['property_dependency', { '#value': 'apple', @@ -94,31 +94,32 @@ t.test(`withPropertyFromObject: output shapes & values`, t => { }]]], ]; - for (const [objectInput, submap] of map) { - for (const [propertyInput, dict] of submap) { + for (const [objectInput, mapLevel2] of mapLevel1) { + for (const [propertyInput, outputDict] of mapLevel2) { const step = withPropertyFromObject({ object: objectInput, property: propertyInput, }); - t.same( - Object.keys(step.toDescription().outputs), - Object.keys(dict)); - - const composite = compositeFrom({ - compose: false, + quickCheckOutputs(step, outputDict); + } + } - steps: [ - step, + function quickCheckOutputs(step, outputDict) { + t.same( + Object.keys(step.toDescription().outputs), + Object.keys(outputDict)); - { - dependencies: Object.keys(dict), - compute: dependencies => dependencies, - }, - ], - }); + const composite = compositeFrom({ + compose: false, + steps: [step, { + dependencies: Object.keys(outputDict), + compute: dependencies => dependencies, + }], + }); - t.same(composite.expose.compute(dependencies), dict); - } + t.same( + composite.expose.compute(dependencies), + outputDict); } }); -- cgit 1.3.0-6-gf8a5 From 90492f9e58251f63a1175d0e52d76f00fa78e3bf Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 29 Sep 2023 10:29:04 -0300 Subject: test: withPropertiesFromObject (WIP) --- .../common-utilities/withPropertiesFromObject.js | 181 +++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 test/unit/data/composite/common-utilities/withPropertiesFromObject.js (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js new file mode 100644 index 00000000..9bcb84c3 --- /dev/null +++ b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js @@ -0,0 +1,181 @@ +import t from 'tap'; + +import { + compositeFrom, + exposeDependency, + input, + withPropertiesFromObject, +} from '#composite'; + +t.test(`withPropertiesFromObject: basic behavior`, t => { + t.plan(4); + + const composite = compositeFrom({ + compose: false, + + steps: [ + withPropertiesFromObject({ + object: 'object', + properties: 'properties', + }), + + exposeDependency({dependency: '#object'}), + ], + }); + + 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 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, + }); + + quickCheckOutputs(step, outputDict); + } + } + } + + function quickCheckOutputs(step, outputDict) { + t.same( + Object.keys(step.toDescription().outputs), + Object.keys(outputDict)); + + const composite = compositeFrom({ + compose: false, + steps: [step, { + dependencies: Object.keys(outputDict), + compute: dependencies => dependencies, + }], + }); + + t.same( + composite.expose.compute(dependencies), + outputDict); + } +}); -- cgit 1.3.0-6-gf8a5 From e4dc2be4c12a5578bfb5d5945a592907aed1cb4f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 29 Sep 2023 10:36:59 -0300 Subject: data, test: type validation message adjustments --- .../data/composite/common-utilities/withResultOfAvailabilityCheck.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js index 01220a3a..c6fa7b21 100644 --- a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js +++ b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js @@ -168,7 +168,7 @@ t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { t.match(caughtError, { message: /Errors in input options passed to withResultOfAvailabilityCheck/, errors: [ - /mode: Expected value, got null/, + /mode: Expected a value, got null/, ], }); }); @@ -218,7 +218,7 @@ t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { cause: { message: /Errors in input values provided to withResultOfAvailabilityCheck/, errors: [ - /mode: Expected value, got null/, + /mode: Expected a value, got null/, ], }, }, -- cgit 1.3.0-6-gf8a5 From 38e8ed330aa238dc258b7749ce704eb98f5ac670 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 29 Sep 2023 10:40:46 -0300 Subject: test: tidy error matching --- .../withResultOfAvailabilityCheck.js | 124 ++++++++------------- 1 file changed, 49 insertions(+), 75 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js index c6fa7b21..dacf60f0 100644 --- a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js +++ b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js @@ -113,18 +113,14 @@ t.test(`withResultOfAvailabilityCheck: default mode`, t => { t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { t.plan(5); - let caughtError; - - try { - caughtError = null; - withResultOfAvailabilityCheck({}); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - errors: [/Required these inputs: from/], - }); + t.throws( + () => withResultOfAvailabilityCheck({}), + { + message: `Errors in input options passed to withResultOfAvailabilityCheck`, + errors: [ + {message: `Required these inputs: from`} + ], + }); t.doesNotThrow(() => withResultOfAvailabilityCheck({ @@ -138,89 +134,67 @@ t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { mode: input.value('null'), })); - try { - caughtError = null; - withResultOfAvailabilityCheck({ + 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, got invalid`}, + ], }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Errors in input options passed to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected one of null empty falsy, got invalid/, - ], - }); - try { - caughtError = null; + 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`}, + ], }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Errors in input options passed to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected a value, got null/, - ], - }); }); t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { t.plan(2); - let caughtError; - - try { - caughtError = null; - composite.expose.compute({ + t.throws( + () => composite.expose.compute({ from: 'apple', mode: 'banana', - }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Error computing composition/, - cause: { - message: /Error computing composition withResultOfAvailabilityCheck/, + }), + { + message: `Error computing composition`, cause: { - message: /Errors in input values provided to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected one of null empty falsy, got banana/, - ], + message: `Error computing composition withResultOfAvailabilityCheck`, + cause: { + message: `Errors in input values provided to withResultOfAvailabilityCheck`, + errors: [ + {message: `mode: Expected one of null empty falsy, got banana`}, + ], + }, }, - }, - }); + }); - try { - caughtError = null; - composite.expose.compute({ + t.throws( + () => composite.expose.compute({ from: null, mode: null, - }); - } catch (error) { - caughtError = error; - } - - t.match(caughtError, { - message: /Error computing composition/, - cause: { - message: /Error computing composition withResultOfAvailabilityCheck/, + }), + { + message: `Error computing composition`, cause: { - message: /Errors in input values provided to withResultOfAvailabilityCheck/, - errors: [ - /mode: Expected a value, got null/, - ], + message: `Error computing composition withResultOfAvailabilityCheck`, + cause: { + message: `Errors in input values provided to withResultOfAvailabilityCheck`, + errors: [ + {message: `mode: Expected a value, got null`}, + ], + }, }, - }, - }); + }); }); -- cgit 1.3.0-6-gf8a5 From 13b25a8d48d142b60d5c351aad4ad1bf80104320 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 30 Sep 2023 08:29:13 -0300 Subject: util, test: WIP decorate error with index symbol --- .../common-utilities/withPropertiesFromObject.js | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js index 9bcb84c3..6b4e10c4 100644 --- a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js +++ b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js @@ -179,3 +179,46 @@ t.test(`withPropertiesFromObject: output shapes & values`, t => { 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.sugar.index')]: 2, + message: /Expected a string, got number/, + }, + ]}, + ]}); +}); -- cgit 1.3.0-6-gf8a5 From 6eaa070e5c036ba8cd45f79c16dc2732b40ea480 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 30 Sep 2023 09:14:29 -0300 Subject: data, util: hsmusic.sugar.index -> hsmusic.decorate.indexInSourceArray --- test/unit/data/composite/common-utilities/withPropertiesFromObject.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js index 6b4e10c4..b1b8be7a 100644 --- a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js +++ b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js @@ -216,7 +216,7 @@ t.test(`withPropertiesFromObject: validate static inputs`, t => { {message: 'object: Expected an object, got array'}, {message: 'properties: Errors validating array items', errors: [ { - [Symbol.for('hsmusic.sugar.index')]: 2, + [Symbol.for('hsmusic.decorate.indexInSourceArray')]: 2, message: /Expected a string, got number/, }, ]}, -- cgit 1.3.0-6-gf8a5 From 902ebbafb7cfb56f7a878d33cf94e5708861ff4a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 30 Sep 2023 19:22:52 -0300 Subject: test: various error whitespace cleanup --- .../composite/common-utilities/exposeConstant.js | 18 +++---- .../composite/common-utilities/exposeDependency.js | 18 +++---- .../common-utilities/withPropertiesFromObject.js | 26 ++++----- .../withResultOfAvailabilityCheck.js | 63 +++++++--------------- test/unit/data/templateCompositeFrom.js | 29 ++++------ 5 files changed, 52 insertions(+), 102 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/exposeConstant.js b/test/unit/data/composite/common-utilities/exposeConstant.js index 829dc706..bfed0951 100644 --- a/test/unit/data/composite/common-utilities/exposeConstant.js +++ b/test/unit/data/composite/common-utilities/exposeConstant.js @@ -34,19 +34,13 @@ t.test(`exposeConstant: validate inputs`, t => { t.throws( () => exposeConstant({}), - { - message: `Errors in input options passed to exposeConstant`, - errors: [ - {message: `Required these inputs: value`}, - ], - }); + {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`}, - ], - }); + {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/common-utilities/exposeDependency.js b/test/unit/data/composite/common-utilities/exposeDependency.js index 78801343..4f07cc16 100644 --- a/test/unit/data/composite/common-utilities/exposeDependency.js +++ b/test/unit/data/composite/common-utilities/exposeDependency.js @@ -54,21 +54,15 @@ t.test(`exposeDependency: validate inputs`, t => { t.throws( () => exposeDependency({}), - { - message: `Errors in input options passed to exposeDependency`, - errors: [ - {message: `Required these inputs: dependency`}, - ], - }); + {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`}, - ], - }); + {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/common-utilities/withPropertiesFromObject.js b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js index b1b8be7a..835767cb 100644 --- a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js +++ b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js @@ -185,12 +185,9 @@ t.test(`withPropertiesFromObject: validate static inputs`, t => { t.throws( () => withPropertiesFromObject({}), - { - message: `Errors in input options passed to withPropertiesFromObject`, - errors: [ - {message: `Required these inputs: object, properties`}, - ], - }); + {message: `Errors in input options passed to withPropertiesFromObject`, errors: [ + {message: `Required these inputs: object, properties`}, + ]}); t.throws( () => withPropertiesFromObject({ @@ -198,14 +195,11 @@ t.test(`withPropertiesFromObject: validate static inputs`, t => { 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'}, - ], - }); + {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({ @@ -213,8 +207,8 @@ t.test(`withPropertiesFromObject: validate static inputs`, t => { 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: [ + {message: `object: Expected an object, got array`}, + {message: `properties: Errors validating array items`, errors: [ { [Symbol.for('hsmusic.decorate.indexInSourceArray')]: 2, message: /Expected a string, got number/, diff --git a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js index dacf60f0..50c127d3 100644 --- a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js +++ b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js @@ -3,8 +3,8 @@ import t from 'tap'; import { compositeFrom, continuationSymbol, - withResultOfAvailabilityCheck, input, + withResultOfAvailabilityCheck, } from '#composite'; const composite = compositeFrom({ @@ -115,12 +115,9 @@ t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { t.throws( () => withResultOfAvailabilityCheck({}), - { - message: `Errors in input options passed to withResultOfAvailabilityCheck`, - errors: [ - {message: `Required these inputs: from`} - ], - }); + {message: `Errors in input options passed to withResultOfAvailabilityCheck`, errors: [ + {message: `Required these inputs: from`}, + ]}); t.doesNotThrow(() => withResultOfAvailabilityCheck({ @@ -139,24 +136,18 @@ t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { from: 'foo', mode: input.value('invalid'), }), - { - message: `Errors in input options passed to withResultOfAvailabilityCheck`, - errors: [ - {message: `mode: Expected one of null empty falsy, got invalid`}, - ], - }); + {message: `Errors in input options passed to withResultOfAvailabilityCheck`, errors: [ + {message: `mode: Expected one of null empty falsy, 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`}, - ], - }); + {message: `Errors in input options passed to withResultOfAvailabilityCheck`, errors: [ + {message: `mode: Expected a value, got null`}, + ]}); }); t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { @@ -167,34 +158,20 @@ t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { from: 'apple', mode: 'banana', }), - { - message: `Error computing composition`, - cause: { - message: `Error computing composition withResultOfAvailabilityCheck`, - cause: { - message: `Errors in input values provided to withResultOfAvailabilityCheck`, - errors: [ - {message: `mode: Expected one of null empty falsy, got banana`}, - ], - }, - }, - }); + {message: `Error computing composition`, cause: + {message: `Error computing composition withResultOfAvailabilityCheck`, cause: + {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [ + {message: `mode: Expected one of null empty falsy, got banana`}, + ]}}}); t.throws( () => composite.expose.compute({ from: null, mode: null, }), - { - message: `Error computing composition`, - cause: { - message: `Error computing composition withResultOfAvailabilityCheck`, - cause: { - message: `Errors in input values provided to withResultOfAvailabilityCheck`, - errors: [ - {message: `mode: Expected a value, got null`}, - ], - }, - }, - }); + {message: `Error computing composition`, 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/templateCompositeFrom.js b/test/unit/data/templateCompositeFrom.js index e96b782e..2de18730 100644 --- a/test/unit/data/templateCompositeFrom.js +++ b/test/unit/data/templateCompositeFrom.js @@ -161,13 +161,10 @@ t.test(`templateCompositeFrom: validate static input values`, t => { baz: 'aeiou', raz: input.value(123), }), - { - message: `Errors in input options passed to stubComposite`, - errors: [ - {message: `Unexpected input names: baz, raz`}, - {message: `Required these inputs: foo, bar`}, - ], - }); + {message: `Errors in input options passed to stubComposite`, errors: [ + {message: `Unexpected input names: baz, raz`}, + {message: `Required these inputs: foo, bar`}, + ]}); }); t.test(`templateCompositeFrom: validate acceptsNull / defaultValue: null`, t => { @@ -181,12 +178,9 @@ t.test(`templateCompositeFrom: validate static input values`, t => { t.throws( () => template1({}), - { - message: `Errors in input options passed to stubComposite`, - errors: [ - {message: `Required these inputs: foo`}, - ], - }, + {message: `Errors in input options passed to stubComposite`, errors: [ + {message: `Required these inputs: foo`}, + ]}, `throws if input missing and not marked specially`); const template2 = templateCompositeFrom({ @@ -197,12 +191,9 @@ t.test(`templateCompositeFrom: validate static input values`, t => { t.throws( () => template2({}), - { - message: `Errors in input options passed to stubComposite`, - errors: [ - {message: `Required these inputs: bar`}, - ], - }, + {message: `Errors in input options passed to stubComposite`, errors: [ + {message: `Required these inputs: bar`}, + ]}, `throws if input missing even if marked {acceptsNull}`); const template3 = templateCompositeFrom({ -- cgit 1.3.0-6-gf8a5 From b09540f108de841a067f8f6a8e62c82335221ee9 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 30 Sep 2023 19:23:38 -0300 Subject: test: withPropertiesFromObject: dynamic input validation --- .../common-utilities/withPropertiesFromObject.js | 57 +++++++++++++++++----- 1 file changed, 45 insertions(+), 12 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js index 835767cb..3431382e 100644 --- a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js +++ b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js @@ -7,21 +7,21 @@ import { withPropertiesFromObject, } from '#composite'; -t.test(`withPropertiesFromObject: basic behavior`, t => { - t.plan(4); +const composite = compositeFrom({ + compose: false, - const composite = compositeFrom({ - compose: false, + steps: [ + withPropertiesFromObject({ + object: 'object', + properties: 'properties', + }), - steps: [ - withPropertiesFromObject({ - object: 'object', - properties: 'properties', - }), + exposeDependency({dependency: '#object'}), + ], +}); - exposeDependency({dependency: '#object'}), - ], - }); +t.test(`withPropertiesFromObject: basic behavior`, t => { + t.plan(4); t.match(composite, { expose: { @@ -216,3 +216,36 @@ t.test(`withPropertiesFromObject: validate static inputs`, t => { ]}, ]}); }); + +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 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 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.decorate.indexInSourceArray')]: 2, + message: /Expected a string, got number/, + }, + ]}, + ]}}}); +}); -- cgit 1.3.0-6-gf8a5 From dfcf911501211bbfc64b8ce6a964b70c6406447f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 1 Oct 2023 09:41:57 -0300 Subject: test: test missing tracks property in album.trackSections --- test/unit/data/things/album.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js index 0695fdb6..c5f7fa98 100644 --- a/test/unit/data/things/album.js +++ b/test/unit/data/things/album.js @@ -198,7 +198,7 @@ t.test(`Album.coverArtFileExtension`, t => { }); t.test(`Album.tracks`, t => { - t.plan(4); + t.plan(5); const album = new Album(); const track1 = stubTrack('track1'); @@ -237,6 +237,18 @@ t.test(`Album.tracks`, t => { t.same(album.tracks, [track1, track2, track3], `Album.tracks #4: filters out references without matches`); + + album.trackSections = [ + {tracks: ['track:track1']}, + {}, + {tracks: ['track:track2']}, + {}, + {}, + {tracks: ['track:track3']}, + ]; + + t.same(album.tracks, [track1, track2, track3], + `Album.tracks #5: skips missing tracks property`); }); t.test(`Album.trackSections`, t => { -- cgit 1.3.0-6-gf8a5 From ab7591e45e7e31b4e2c0e2f81e224672145993fa Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 1 Oct 2023 17:01:21 -0300 Subject: data, test: refactor utilities into own file Primarily for more precies test coverage mapping, but also to make navigation a bit easier and consolidate complex functions with lots of imports out of the same space as other, more simple or otherwise specialized files. --- .../composite/common-utilities/exposeConstant.js | 46 ---- .../composite/common-utilities/exposeDependency.js | 68 ------ .../common-utilities/withPropertiesFromObject.js | 251 --------------------- .../common-utilities/withPropertyFromObject.js | 125 ---------- .../withResultOfAvailabilityCheck.js | 177 --------------- .../data/composite/control-flow/exposeConstant.js | 42 ++++ .../composite/control-flow/exposeDependency.js | 64 ++++++ .../control-flow/withResultOfAvailabilityCheck.js | 173 ++++++++++++++ .../composite/data/withPropertiesFromObject.js | 248 ++++++++++++++++++++ .../data/composite/data/withPropertyFromObject.js | 122 ++++++++++ 10 files changed, 649 insertions(+), 667 deletions(-) delete mode 100644 test/unit/data/composite/common-utilities/exposeConstant.js delete mode 100644 test/unit/data/composite/common-utilities/exposeDependency.js delete mode 100644 test/unit/data/composite/common-utilities/withPropertiesFromObject.js delete mode 100644 test/unit/data/composite/common-utilities/withPropertyFromObject.js delete mode 100644 test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js create mode 100644 test/unit/data/composite/control-flow/exposeConstant.js create mode 100644 test/unit/data/composite/control-flow/exposeDependency.js create mode 100644 test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js create mode 100644 test/unit/data/composite/data/withPropertiesFromObject.js create mode 100644 test/unit/data/composite/data/withPropertyFromObject.js (limited to 'test') diff --git a/test/unit/data/composite/common-utilities/exposeConstant.js b/test/unit/data/composite/common-utilities/exposeConstant.js deleted file mode 100644 index bfed0951..00000000 --- a/test/unit/data/composite/common-utilities/exposeConstant.js +++ /dev/null @@ -1,46 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - continuationSymbol, - exposeConstant, - input, -} from '#composite'; - -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/common-utilities/exposeDependency.js b/test/unit/data/composite/common-utilities/exposeDependency.js deleted file mode 100644 index 4f07cc16..00000000 --- a/test/unit/data/composite/common-utilities/exposeDependency.js +++ /dev/null @@ -1,68 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - continuationSymbol, - exposeDependency, - input, -} from '#composite'; - -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/common-utilities/withPropertiesFromObject.js b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js deleted file mode 100644 index 3431382e..00000000 --- a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js +++ /dev/null @@ -1,251 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - exposeDependency, - input, - withPropertiesFromObject, -} from '#composite'; - -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 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, - }); - - quickCheckOutputs(step, outputDict); - } - } - } - - function quickCheckOutputs(step, outputDict) { - t.same( - Object.keys(step.toDescription().outputs), - Object.keys(outputDict)); - - const composite = compositeFrom({ - compose: false, - steps: [step, { - dependencies: Object.keys(outputDict), - compute: dependencies => dependencies, - }], - }); - - t.same( - composite.expose.compute(dependencies), - 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.decorate.indexInSourceArray')]: 2, - 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 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 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.decorate.indexInSourceArray')]: 2, - message: /Expected a string, got number/, - }, - ]}, - ]}}}); -}); diff --git a/test/unit/data/composite/common-utilities/withPropertyFromObject.js b/test/unit/data/composite/common-utilities/withPropertyFromObject.js deleted file mode 100644 index 11487226..00000000 --- a/test/unit/data/composite/common-utilities/withPropertyFromObject.js +++ /dev/null @@ -1,125 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - exposeDependency, - input, - withPropertyFromObject, -} from '#composite'; - -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: 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 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, - }); - - quickCheckOutputs(step, outputDict); - } - } - - function quickCheckOutputs(step, outputDict) { - t.same( - Object.keys(step.toDescription().outputs), - Object.keys(outputDict)); - - const composite = compositeFrom({ - compose: false, - steps: [step, { - dependencies: Object.keys(outputDict), - compute: dependencies => dependencies, - }], - }); - - t.same( - composite.expose.compute(dependencies), - outputDict); - } -}); diff --git a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js deleted file mode 100644 index 50c127d3..00000000 --- a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js +++ /dev/null @@ -1,177 +0,0 @@ -import t from 'tap'; - -import { - compositeFrom, - continuationSymbol, - input, - withResultOfAvailabilityCheck, -} from '#composite'; - -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(10); - - 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: 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(10); - - 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: 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(10); - - 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, 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: 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, 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 computing composition withResultOfAvailabilityCheck`, cause: - {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [ - {message: `mode: Expected one of null empty falsy, got banana`}, - ]}}}); - - t.throws( - () => composite.expose.compute({ - from: null, - mode: null, - }), - {message: `Error computing composition`, 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/control-flow/exposeConstant.js b/test/unit/data/composite/control-flow/exposeConstant.js new file mode 100644 index 00000000..0c75894b --- /dev/null +++ b/test/unit/data/composite/control-flow/exposeConstant.js @@ -0,0 +1,42 @@ +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 new file mode 100644 index 00000000..8f6bfd01 --- /dev/null +++ b/test/unit/data/composite/control-flow/exposeDependency.js @@ -0,0 +1,64 @@ +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 new file mode 100644 index 00000000..4c4be04a --- /dev/null +++ b/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js @@ -0,0 +1,173 @@ +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(10); + + 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: 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(10); + + 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: 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(10); + + 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, 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: 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, 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 computing composition withResultOfAvailabilityCheck`, cause: + {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [ + {message: `mode: Expected one of null empty falsy, got banana`}, + ]}}}); + + t.throws( + () => composite.expose.compute({ + from: null, + mode: null, + }), + {message: `Error computing composition`, 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 new file mode 100644 index 00000000..ead1b9b2 --- /dev/null +++ b/test/unit/data/composite/data/withPropertiesFromObject.js @@ -0,0 +1,248 @@ +import t from 'tap'; + +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 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, + }); + + quickCheckOutputs(step, outputDict); + } + } + } + + function quickCheckOutputs(step, outputDict) { + t.same( + Object.keys(step.toDescription().outputs), + Object.keys(outputDict)); + + const composite = compositeFrom({ + compose: false, + steps: [step, { + dependencies: Object.keys(outputDict), + compute: dependencies => dependencies, + }], + }); + + t.same( + composite.expose.compute(dependencies), + 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.decorate.indexInSourceArray')]: 2, + 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 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 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.decorate.indexInSourceArray')]: 2, + message: /Expected a string, got number/, + }, + ]}, + ]}}}); +}); diff --git a/test/unit/data/composite/data/withPropertyFromObject.js b/test/unit/data/composite/data/withPropertyFromObject.js new file mode 100644 index 00000000..6a772c36 --- /dev/null +++ b/test/unit/data/composite/data/withPropertyFromObject.js @@ -0,0 +1,122 @@ +import t from 'tap'; + +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: 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 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, + }); + + quickCheckOutputs(step, outputDict); + } + } + + function quickCheckOutputs(step, outputDict) { + t.same( + Object.keys(step.toDescription().outputs), + Object.keys(outputDict)); + + const composite = compositeFrom({ + compose: false, + steps: [step, { + dependencies: Object.keys(outputDict), + compute: dependencies => dependencies, + }], + }); + + t.same( + composite.expose.compute(dependencies), + outputDict); + } +}); -- cgit 1.3.0-6-gf8a5 From a60c8906ed7580a21527c9f96cd0e6e277978263 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 1 Oct 2023 17:58:56 -0300 Subject: data, test: expose track section names --- test/unit/data/things/album.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js index c5f7fa98..76a2b90f 100644 --- a/test/unit/data/things/album.js +++ b/test/unit/data/things/album.js @@ -252,7 +252,7 @@ t.test(`Album.tracks`, t => { }); t.test(`Album.trackSections`, t => { - t.plan(6); + t.plan(7); const album = new Album(); const track1 = stubTrack('track1'); @@ -280,6 +280,18 @@ t.test(`Album.trackSections`, t => { {tracks: [track3, track4], startIndex: 2}, ], `Album.trackSections #2: exposes startIndex`); + album.trackSections = [ + {name: 'First section', tracks: ['track:track1']}, + {name: 'Second section', tracks: ['track:track2']}, + {tracks: ['track:track3']}, + ]; + + t.match(album.trackSections, [ + {name: 'First section', tracks: [track1]}, + {name: 'Second section', tracks: [track2]}, + {name: 'Unnamed Track Section', tracks: [track3]}, + ], `Album.trackSections #3: exposes name, with fallback value`); + album.color = '#123456'; album.trackSections = [ @@ -292,7 +304,7 @@ t.test(`Album.trackSections`, t => { {tracks: [track1], color: '#123456'}, {tracks: [track2], color: '#abcdef'}, {tracks: [track3], color: '#123456'}, - ], `Album.trackSections #3: exposes color, inherited from album`); + ], `Album.trackSections #4: exposes color, inherited from album`); album.trackSections = [ {tracks: ['track:track1'], dateOriginallyReleased: null}, @@ -304,7 +316,7 @@ t.test(`Album.trackSections`, t => { {tracks: [track1], dateOriginallyReleased: null}, {tracks: [track2], dateOriginallyReleased: new Date('2009-04-11')}, {tracks: [track3], dateOriginallyReleased: null}, - ], `Album.trackSections #4: exposes dateOriginallyReleased, if present`); + ], `Album.trackSections #5: exposes dateOriginallyReleased, if present`); album.trackSections = [ {tracks: ['track:track1'], isDefaultTrackSection: true}, @@ -316,7 +328,7 @@ t.test(`Album.trackSections`, t => { {tracks: [track1], isDefaultTrackSection: true}, {tracks: [track2], isDefaultTrackSection: false}, {tracks: [track3], isDefaultTrackSection: false}, - ], `Album.trackSections #5: exposes isDefaultTrackSection, defaults to false`); + ], `Album.trackSections #6: exposes isDefaultTrackSection, defaults to false`); album.trackSections = [ {tracks: ['track:track1', 'track:track2', 'track:snooping'], color: '#112233'}, @@ -330,7 +342,7 @@ t.test(`Album.trackSections`, t => { {tracks: [track1, track2], color: '#112233'}, {tracks: [track3], color: '#334455'}, {tracks: [track4], color: '#778899'}, - ], `Album.trackSections #6: filters out references without matches & empty sections`); + ], `Album.trackSections #7: filters out references without matches & empty sections`); }); t.test(`Album.wallpaperFileExtension`, t => { -- cgit 1.3.0-6-gf8a5 From b37d81240307f3e38faaa781c3932feff53e9aac Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 6 Oct 2023 12:06:15 -0300 Subject: data, test: fix track contribs not inheriting properly --- test/unit/data/things/track.js | 95 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) (limited to 'test') diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index f68510c6..06a4041d 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -115,6 +115,51 @@ t.test(`Track.album`, t => { `album #6: is null when album's trackSections don't match track`); }); +t.test(`Track.artistContribs`, t => { + t.plan(4); + + const {track, album} = stubTrackAndAlbum(); + const artist1 = stubArtist(`Artist 1`); + const artist2 = stubArtist(`Artist 2`); + + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album], + artistData: [artist1, artist2], + trackData: [track], + }); + + t.same(track.artistContribs, [], + `artistContribs #1: defaults to empty array`); + + album.artistContribs = [ + {who: `Artist 1`, what: `composition`}, + {who: `Artist 2`, what: null}, + ]; + + XXX_decacheWikiData(); + + t.same(track.artistContribs, + [{who: artist1, what: `composition`}, {who: artist2, what: null}], + `artistContribs #2: inherits album artistContribs`); + + track.artistContribs = [ + {who: `Artist 1`, what: `arrangement`}, + ]; + + t.same(track.artistContribs, [{who: artist1, what: `arrangement`}], + `artistContribs #3: resolves from own value`); + + track.artistContribs = [ + {who: `Artist 1`, what: `snooping`}, + {who: `Artist 413`, what: `as`}, + {who: `Artist 2`, what: `usual`}, + ]; + + t.same(track.artistContribs, + [{who: artist1, what: `snooping`}, {who: artist2, what: `usual`}], + `artistContribs #4: filters out names without matches`); +}); + t.test(`Track.color`, t => { t.plan(5); @@ -223,6 +268,56 @@ t.test(`Track.commentatorArtists`, t => { `Track.commentatorArtists #6: ignores duplicate artist`); }); +t.test(`Track.coverArtistContribs`, t => { + t.plan(5); + + const {track, album} = stubTrackAndAlbum(); + const artist1 = stubArtist(`Artist 1`); + const artist2 = stubArtist(`Artist 2`); + + const {XXX_decacheWikiData} = linkAndBindWikiData({ + albumData: [album], + artistData: [artist1, artist2], + trackData: [track], + }); + + t.same(track.coverArtistContribs, [], + `coverArtistContribs #1: defaults to empty array`); + + album.trackCoverArtistContribs = [ + {who: `Artist 1`, what: `lines`}, + {who: `Artist 2`, what: null}, + ]; + + XXX_decacheWikiData(); + + t.same(track.coverArtistContribs, + [{who: artist1, what: `lines`}, {who: artist2, what: null}], + `coverArtistContribs #2: inherits album trackCoverArtistContribs`); + + track.coverArtistContribs = [ + {who: `Artist 1`, what: `collage`}, + ]; + + t.same(track.coverArtistContribs, [{who: artist1, what: `collage`}], + `coverArtistContribs #3: resolves from own value`); + + track.coverArtistContribs = [ + {who: `Artist 1`, what: `snooping`}, + {who: `Artist 413`, what: `as`}, + {who: `Artist 2`, what: `usual`}, + ]; + + t.same(track.coverArtistContribs, + [{who: artist1, what: `snooping`}, {who: artist2, what: `usual`}], + `coverArtistContribs #4: filters out names without matches`); + + track.disableUniqueCoverArt = true; + + t.same(track.coverArtistContribs, [], + `coverArtistContribs #5: is empty if track disables unique cover artwork`); +}); + t.test(`Track.coverArtDate`, t => { t.plan(8); -- cgit 1.3.0-6-gf8a5 From 645a127bef38c3a7a2ef1b94d23b25fb7bdc4191 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 18 Oct 2023 14:26:49 -0300 Subject: data, test: wrap property value errors with proper class & cause --- test/unit/data/cacheable-object.js | 30 ++++++++++-------------------- test/unit/data/things/track.js | 3 ++- 2 files changed, 12 insertions(+), 21 deletions(-) (limited to 'test') diff --git a/test/unit/data/cacheable-object.js b/test/unit/data/cacheable-object.js index 2e82af08..57e562d8 100644 --- a/test/unit/data/cacheable-object.js +++ b/test/unit/data/cacheable-object.js @@ -195,13 +195,10 @@ t.test(`CacheableObject validate on update`, t => { obj.directory = 'megalovania'; t.equal(obj.directory, 'megalovania'); - try { - obj.directory = 25; - } catch (err) { - thrownError = err; - } + t.throws( + () => { obj.directory = 25; }, + {cause: mockError}); - t.equal(thrownError, mockError); t.equal(obj.directory, 'megalovania'); const date = new Date(`25 December 2009`); @@ -209,13 +206,10 @@ t.test(`CacheableObject validate on update`, t => { obj.date = date; t.equal(obj.date, date); - try { - obj.date = `TWELFTH PERIGEE'S EVE`; - } catch (err) { - thrownError = err; - } + t.throws( + () => { obj.date = `TWELFTH PERIGEE'S EVE`; }, + {cause: TypeError}); - t.equal(thrownError?.constructor, TypeError); t.equal(obj.date, date); }); @@ -244,8 +238,8 @@ t.test(`CacheableObject default property throws if invalid`, t => { let thrownError; - try { - newCacheableObject({ + t.throws( + () => newCacheableObject({ string: { flags: { update: true @@ -261,10 +255,6 @@ t.test(`CacheableObject default property throws if invalid`, t => { } } } - }); - } catch (err) { - thrownError = err; - } - - t.equal(thrownError, mockError); + }), + {cause: mockError}); }); diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 06a4041d..806efbf1 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -208,7 +208,8 @@ t.test(`Track.color`, t => { t.equal(track.color, '#123456', `color #4: is own value`); - t.throws(() => { track.color = '#aeiouw'; }, TypeError, + t.throws(() => { track.color = '#aeiouw'; }, + {cause: TypeError}, `color #5: must be set to valid color`); }); -- cgit 1.3.0-6-gf8a5 From 2532ba76a87971a8d8041d689788d91ae13dc627 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 26 Oct 2023 17:13:02 -0300 Subject: test: Flash.color (unit) --- test/unit/data/things/flash.js | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 test/unit/data/things/flash.js (limited to 'test') diff --git a/test/unit/data/things/flash.js b/test/unit/data/things/flash.js new file mode 100644 index 00000000..62059604 --- /dev/null +++ b/test/unit/data/things/flash.js @@ -0,0 +1,55 @@ +import t from 'tap'; + +import {linkAndBindWikiData} from '#test-lib'; +import thingConstructors from '#things'; + +const { + Flash, + FlashAct, + Thing, +} = thingConstructors; + +function stubFlash(directory = 'foo') { + const flash = new Flash(); + flash.directory = directory; + + return flash; +} + +function stubFlashAct(flashes, directory = 'bar') { + const flashAct = new FlashAct(); + flashAct.directory = directory; + flashAct.flashes = flashes.map(flash => Thing.getReference(flash)); + + return flashAct; +} + +t.test(`Flash.color`, t => { + t.plan(4); + + const flash = stubFlash(); + const flashAct = stubFlashAct([flash]); + + const {XXX_decacheWikiData} = linkAndBindWikiData({ + flashData: [flash], + flashActData: [flashAct], + }); + + t.equal(flash.color, null, + `color #1: defaults to null`); + + flashAct.color = '#abcdef'; + XXX_decacheWikiData(); + + t.equal(flash.color, '#abcdef', + `color #2: inherits from flash act`); + + flash.color = '#123456'; + + t.equal(flash.color, '#123456', + `color #3: is own value`); + + t.throws(() => { flash.color = '#aeiouw'; }, + {cause: TypeError}, + `color #4: must be set to valid color`); +}); -- cgit 1.3.0-6-gf8a5 From bdad6e4abd6c456a65e30b93247964fdd9a1f48c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 26 Oct 2023 17:47:54 -0300 Subject: test: Track.withAlbum (unit) --- test/unit/data/composite/things/track/withAlbum.js | 144 +++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 test/unit/data/composite/things/track/withAlbum.js (limited to 'test') diff --git a/test/unit/data/composite/things/track/withAlbum.js b/test/unit/data/composite/things/track/withAlbum.js new file mode 100644 index 00000000..30f8cc5d --- /dev/null +++ b/test/unit/data/composite/things/track/withAlbum.js @@ -0,0 +1,144 @@ +import t from 'tap'; + +import {compositeFrom, input} from '#composite'; +import {exposeConstant, exposeDependency} from '#composite/control-flow'; +import {withAlbum} from '#composite/things/track'; + +t.test(`withAlbum: basic behavior`, t => { + t.plan(3); + + const composite = compositeFrom({ + compose: false, + steps: [ + withAlbum(), + exposeDependency({dependency: '#album'}), + ], + }); + + t.match(composite, { + expose: { + dependencies: ['albumData', 'this'], + }, + }); + + const fakeTrack1 = {directory: 'foo'}; + const fakeTrack2 = {directory: 'bar'}; + const fakeAlbum = {directory: 'baz', tracks: [fakeTrack1]}; + + t.equal( + composite.expose.compute({ + albumData: [fakeAlbum], + this: fakeTrack1, + }), + fakeAlbum); + + t.equal( + composite.expose.compute({ + albumData: [fakeAlbum], + this: fakeTrack2, + }), + null); +}); + +t.test(`withAlbum: early exit conditions (notFoundMode: null)`, t => { + t.plan(4); + + const composite = compositeFrom({ + compose: false, + steps: [ + withAlbum(), + exposeConstant({ + value: input.value('bimbam'), + }), + ], + }); + + const fakeTrack1 = {directory: 'foo'}; + const fakeTrack2 = {directory: 'bar'}; + const fakeAlbum = {directory: 'baz', tracks: [fakeTrack1]}; + + t.equal( + composite.expose.compute({ + albumData: [fakeAlbum], + this: fakeTrack1, + }), + 'bimbam', + `does not early exit if albumData is present and contains the track`); + + t.equal( + composite.expose.compute({ + albumData: [fakeAlbum], + this: fakeTrack2, + }), + 'bimbam', + `does not early exit if albumData is present and does not contain the track`); + + t.equal( + composite.expose.compute({ + albumData: [], + this: fakeTrack1, + }), + 'bimbam', + `does not early exit if albumData is empty array`); + + t.equal( + composite.expose.compute({ + albumData: null, + this: fakeTrack1, + }), + null, + `early exits if albumData is null`); +}); + +t.test(`withAlbum: early exit conditions (notFoundMode: exit)`, t => { + t.plan(4); + + const composite = compositeFrom({ + compose: false, + steps: [ + withAlbum({ + notFoundMode: input.value('exit'), + }), + + exposeConstant({ + value: input.value('bimbam'), + }), + ], + }); + + const fakeTrack1 = {directory: 'foo'}; + const fakeTrack2 = {directory: 'bar'}; + const fakeAlbum = {directory: 'baz', tracks: [fakeTrack1]}; + + t.equal( + composite.expose.compute({ + albumData: [fakeAlbum], + this: fakeTrack1, + }), + 'bimbam', + `does not early exit if albumData is present and contains the track`); + + t.equal( + composite.expose.compute({ + albumData: [fakeAlbum], + this: fakeTrack2, + }), + null, + `early exits if albumData is present and does not contain the track`); + + t.equal( + composite.expose.compute({ + albumData: [], + this: fakeTrack1, + }), + null, + `early exits if albumData is empty array`); + + t.equal( + composite.expose.compute({ + albumData: null, + this: fakeTrack1, + }), + null, + `early exits if albumData is null`); +}); -- cgit 1.3.0-6-gf8a5 From 5f740046562c85ab4e00063037ddf3af5a545279 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 26 Oct 2023 19:05:39 -0300 Subject: data, test: withResultOfAvailabilityCheck: index mode --- .../control-flow/withResultOfAvailabilityCheck.js | 32 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js b/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js index 4c4be04a..2bcabb4f 100644 --- a/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js +++ b/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js @@ -38,7 +38,7 @@ const quickThrows = (t, {from, mode}) => t.throws(() => composite.expose.compute({from, mode})); t.test(`withResultOfAvailabilityCheck: mode = null`, t => { - t.plan(10); + t.plan(11); quickCompare(t, true, {mode: 'null', from: 'truthy string'}); quickCompare(t, true, {mode: 'null', from: 123}); @@ -46,6 +46,7 @@ t.test(`withResultOfAvailabilityCheck: mode = null`, t => { 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]}); @@ -56,7 +57,7 @@ t.test(`withResultOfAvailabilityCheck: mode = null`, t => { }); t.test(`withResultOfAvailabilityCheck: mode = empty`, t => { - t.plan(10); + t.plan(11); quickThrows(t, {mode: 'empty', from: 'truthy string'}); quickThrows(t, {mode: 'empty', from: 123}); @@ -64,6 +65,7 @@ t.test(`withResultOfAvailabilityCheck: mode = empty`, t => { 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]}); @@ -74,7 +76,7 @@ t.test(`withResultOfAvailabilityCheck: mode = empty`, t => { }); t.test(`withResultOfAvailabilityCheck: mode = falsy`, t => { - t.plan(10); + t.plan(11); quickCompare(t, true, {mode: 'falsy', from: 'truthy string'}); quickCompare(t, true, {mode: 'falsy', from: 123}); @@ -82,6 +84,7 @@ t.test(`withResultOfAvailabilityCheck: mode = falsy`, t => { 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]}); @@ -91,6 +94,25 @@ t.test(`withResultOfAvailabilityCheck: mode = falsy`, t => { 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); @@ -133,7 +155,7 @@ t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => { mode: input.value('invalid'), }), {message: `Errors in input options passed to withResultOfAvailabilityCheck`, errors: [ - {message: `mode: Expected one of null empty falsy, got invalid`}, + {message: `mode: Expected one of null empty falsy index, got invalid`}, ]}); t.throws(() => @@ -157,7 +179,7 @@ t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => { {message: `Error computing composition`, cause: {message: `Error computing composition withResultOfAvailabilityCheck`, cause: {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [ - {message: `mode: Expected one of null empty falsy, got banana`}, + {message: `mode: Expected one of null empty falsy index, got banana`}, ]}}}); t.throws( -- cgit 1.3.0-6-gf8a5