« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/data/things/thing.js2
-rw-r--r--src/data/things/track.js69
-rw-r--r--test/unit/data/things/track.js34
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 => {