diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-08-26 19:22:38 -0300 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2023-09-05 21:02:52 -0300 |
commit | 25beb8731d756bfa4fe6babb9e4b0a707c7823e0 (patch) | |
tree | ed7c86d4dd4be4c0b6d6e0a3a386b2477251386e | |
parent | e2f1cd30f8d5804f97043faedc5aea9fe06cea32 (diff) |
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)
-rw-r--r-- | src/data/things/thing.js | 51 | ||||
-rw-r--r-- | src/data/things/track.js | 95 | ||||
-rw-r--r-- | test/unit/data/things/track.js | 53 |
3 files changed, 145 insertions, 54 deletions
diff --git a/src/data/things/thing.js b/src/data/things/thing.js index c870b89c..2af06904 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -1114,6 +1114,57 @@ export default class Thing extends CacheableObject { }; }, + // Exposes a dependency exactly as it is; this is typically the base of a + // composition which was created to serve as one property's descriptor. + // Since this serves as a base, specify {update: true} to indicate that + // the property as a whole updates (and some previous compositional step + // works with that update value). + // + // Please note that this *doesn't* verify that the dependency exists, so + // if you provide the wrong name or it hasn't been set by a previous + // compositional step, the property will be exposed as undefined instead + // of null. + // + expose: (dependency, {update = false} = {}) => ({ + annotation: `Thing.composite.expose`, + flags: {expose: true, update}, + + expose: { + mapDependencies: {dependency}, + compute: ({dependency}) => dependency, + }, + }), + + // Exposes the update value of an {update: true} property, or continues if + // it's unavailable. By default, "unavailable" means value === null, but + // set {mode: 'empty'} to + exposeUpdateValueOrContinue({mode = 'null'} = {}) { + if (mode !== 'null' && mode !== 'empty') { + throw new TypeError(`Expected mode to be null or empty`); + } + + return { + annotation: `Thing.composite.exposeUpdateValueOrContinue`, + flags: {expose: true, compose: true}, + expose: { + options: {mode}, + + transform(value, {'#options': {mode}}, continuation) { + const shouldContinue = + (mode === 'empty' + ? empty(value) + : value === null); + + if (shouldContinue) { + return continuation(); + } else { + return continuation.exit(value); + } + } + }, + }; + }, + // Resolves the contribsByRef contained in the provided dependency, // providing (named by the second argument) the result. "Resolving" // means mapping the "who" reference of each contribution to an artist diff --git a/src/data/things/track.js b/src/data/things/track.js index 6c08aa01..15a48bb4 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -45,26 +45,9 @@ export class Track extends Thing { artTagsByRef: Thing.common.referenceList(ArtTag), color: Thing.composite.from(`Track.color`, [ - { - flags: {expose: true, compose: true}, - expose: { - transform: (color, {}, continuation) => - color ?? continuation(), - }, - }, - - Track.composite.withAlbumProperties({ - properties: ['color'], - }), - - { - flags: {update: true, expose: true}, - update: {validate: isColor}, - expose: { - dependencies: ['#album.color'], - compute: ({'#album.color': color}) => color, - }, - }, + Thing.composite.exposeUpdateValueOrContinue(), + Track.composite.withAlbumProperty('color'), + Thing.composite.expose('#album.color', {update: true}), ]), // Disables presenting the track as though it has its own unique artwork. @@ -169,15 +152,11 @@ export class Track extends Thing { commentatorArtists: Thing.common.commentatorArtists(), - album: { - flags: {expose: true}, - - expose: { - dependencies: ['this', 'albumData'], - compute: ({this: track, albumData}) => - albumData?.find((album) => album.tracks.includes(track)) ?? null, - }, - }, + album: + Thing.composite.from(`Track.album`, [ + Track.composite.withAlbum(), + Thing.composite.expose('#album'), + ]), // Note - this is an internal property used only to help identify a track. // It should not be assumed in general that the album and dataSourceAlbum match @@ -202,17 +181,8 @@ export class Track extends Thing { }, }, - Track.composite.withAlbumProperties({ - properties: ['date'], - }), - - { - flags: {expose: true}, - expose: { - dependencies: ['#album.date'], - compute: ({'#album.date': date}) => date, - }, - }, + Track.composite.withAlbumProperties({properties: ['date']}), + Thing.composite.expose('#album.date'), ]), // Whether or not the track has "unique" cover artwork - a cover which is @@ -369,20 +339,8 @@ export class Track extends Thing { }, }, - Track.composite.withAlbumProperties({ - properties: ['trackCoverArtistContribs'], - }), - - { - flags: {expose: true}, - expose: { - mapDependencies: {contribsFromAlbum: '#album.trackCoverArtistContribs'}, - compute: ({contribsFromAlbum}) => - (empty(contribsFromAlbum) - ? null - : contribsFromAlbum), - }, - }, + Track.composite.withAlbumProperty('trackCoverArtistContribs'), + Thing.composite.expose('#album.trackCoverArtistContribs'), ]), referencedTracks: Thing.composite.from(`Track.referencedTracks`, [ @@ -513,6 +471,35 @@ export class Track extends Thing { }, }), + // Gets a single property from this track's album, providing it as the same + // property name prefixed with '#album.' (by default). If the track's album + // isn't available, and earlyExitIfNotFound hasn't been set, the property + // will be provided as null. + withAlbumProperty: (property, { + to = '#album.' + property, + earlyExitIfNotFound = false, + } = {}) => + Thing.composite.from(`Track.composite.withAlbumProperty`, [ + Track.composite.withAlbum({earlyExitIfNotFound}), + + { + flags: {expose: true, compose: true}, + expose: { + dependencies: ['#album'], + options: {property}, + mapContinuation: {to}, + + compute: ({ + '#album': album, + '#options': {property}, + }, continuation) => + (album + ? continuation.raise({to: album[property]}) + : continuation.raise({to: null})), + }, + }, + ]), + // Gets the listed properties from this track's album, providing them as // dependencies (by default) with '#album.' prefixed before each property // name. If the track's album isn't available, and earlyExitIfNotFound 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); |