« get me outta code hell

data, test: misc. additions - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
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
commit25beb8731d756bfa4fe6babb9e4b0a707c7823e0 (patch)
treeed7c86d4dd4be4c0b6d6e0a3a386b2477251386e
parente2f1cd30f8d5804f97043faedc5aea9fe06cea32 (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.js51
-rw-r--r--src/data/things/track.js95
-rw-r--r--test/unit/data/things/track.js53
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);