diff options
-rw-r--r-- | src/data/things/thing.js | 2 | ||||
-rw-r--r-- | src/data/things/track.js | 69 | ||||
-rw-r--r-- | test/unit/data/things/track.js | 34 |
3 files changed, 93 insertions, 12 deletions
diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 2adba5c4..f5dc786e 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -1127,7 +1127,7 @@ export default class Thing extends CacheableObject { // compositional step, the property will be exposed as undefined instead // of null. // - expose: (dependency, {update = false} = {}) => ({ + exposeDependency: (dependency, {update = false} = {}) => ({ annotation: `Thing.composite.expose`, flags: {expose: true, update: !!update}, diff --git a/src/data/things/track.js b/src/data/things/track.js index 8d0a7ad4..621044d5 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -46,8 +46,25 @@ export class Track extends Thing { color: Thing.composite.from(`Track.color`, [ Thing.composite.exposeUpdateValueOrContinue(), + Track.composite.withContainingTrackSection({earlyExitIfNotFound: false}), + + { + flags: {expose: true, compose: true}, + expose: { + dependencies: ['#trackSection'], + compute: ({'#trackSection': trackSection}, continuation) => + // Album.trackSections guarantees the track section will have a + // color property (inheriting from the album's own color), but only + // if it's actually present! Color will be inherited directly from + // album otherwise. + (trackSection + ? trackSection.color + : continuation()), + }, + }, + Track.composite.withAlbumProperty('color'), - Thing.composite.expose('#album.color', { + Thing.composite.exposeDependency('#album.color', { update: {validate: isColor}, }), ]), @@ -157,7 +174,7 @@ export class Track extends Thing { album: Thing.composite.from(`Track.album`, [ Track.composite.withAlbum(), - Thing.composite.expose('#album'), + Thing.composite.exposeDependency('#album'), ]), // Note - this is an internal property used only to help identify a track. @@ -183,8 +200,8 @@ export class Track extends Thing { }, }, - Track.composite.withAlbumProperties({properties: ['date']}), - Thing.composite.expose('#album.date'), + Track.composite.withAlbumProperty('date'), + Thing.composite.exposeDependency('#album.date'), ]), // Whether or not the track has "unique" cover artwork - a cover which is @@ -342,7 +359,7 @@ export class Track extends Thing { }, Track.composite.withAlbumProperty('trackCoverArtistContribs'), - Thing.composite.expose('#album.trackCoverArtistContribs'), + Thing.composite.exposeDependency('#album.trackCoverArtistContribs'), ]), referencedTracks: Thing.composite.from(`Track.referencedTracks`, [ @@ -435,7 +452,7 @@ export class Track extends Thing { ]), // Gets the track's album. Unless earlyExitIfNotFound is overridden false, - // this will early-exit with null in two cases - albumData being missing, + // this will early exit with null in two cases - albumData being missing, // or not including an album whose .tracks array includes this track. withAlbum: ({to = '#album', earlyExitIfNotFound = true} = {}) => ({ annotation: `Track.composite.withAlbum`, @@ -542,6 +559,46 @@ export class Track extends Thing { }, ]), + // Gets the track section containing this track from its album's track list. + // Unless earlyExitIfNotFound is overridden false, this will early exit if + // the album can't be found or if none of its trackSections includes the + // track for some reason. + withContainingTrackSection: ({ + to = '#trackSection', + earlyExitIfNotFound = true, + } = {}) => + Thing.composite.from(`Track.composite.withContainingTrackSection`, [ + Track.composite.withAlbumProperty('trackSections', {earlyExitIfNotFound}), + + { + flags: {expose: true, compose: true}, + expose: { + dependencies: ['this', '#album.trackSections'], + mapContinuation: {to}, + + compute({ + this: track, + '#album.trackSections': trackSections, + }, continuation) { + if (!trackSections) { + return continuation.raise({to: null}); + } + + const trackSection = + trackSections.find(({tracks}) => tracks.includes(track)); + + if (trackSection) { + return continuation.raise({to: trackSection}); + } else if (earlyExitIfNotFound) { + return continuation.exit(null); + } else { + return continuation.raise({to: null}); + } + }, + }, + }, + ]), + // Just includes the original release of this track as a dependency, or // null, if it's not a rerelease. Note that this will early exit if the // original release is specified by reference and that reference doesn't 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 => { |