« 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/yaml.js20
-rw-r--r--test/lib/index.js1
-rw-r--r--test/lib/wiki-data.js24
-rw-r--r--test/unit/data/things/track.js105
4 files changed, 93 insertions, 57 deletions
diff --git a/src/data/yaml.js b/src/data/yaml.js
index 25eda3c5..2ad2d41d 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -1320,13 +1320,27 @@ export async function loadAndProcessDataDocuments({dataPath}) {
 
 // Data linking! Basically, provide (portions of) wikiData to the Things which
 // require it - they'll expose dynamically computed properties as a result (many
-// of which are required for page HTML generation).
-export function linkWikiDataArrays(wikiData) {
+// of which are required for page HTML generation and other expected behavior).
+//
+// The XXX_decacheWikiData option should be used specifically to mark
+// points where you *aren't* replacing any of the arrays under wikiData with
+// new values, and are using linkWikiDataArrays to instead "decache" data
+// properties which depend on any of them. It's currently not possible for
+// a CacheableObject to depend directly on the value of a property exposed
+// on some other CacheableObject, so when those values change, you have to
+// manually decache before the object will realize its cache isn't valid
+// anymore.
+export function linkWikiDataArrays(wikiData, {
+  XXX_decacheWikiData = false,
+} = {}) {
   function assignWikiData(things, ...keys) {
+    if (things === undefined) return;
     for (let i = 0; i < things.length; i++) {
       const thing = things[i];
       for (let j = 0; j < keys.length; j++) {
         const key = keys[j];
+        if (!(key in wikiData)) continue;
+        if (XXX_decacheWikiData) thing[key] = [];
         thing[key] = wikiData[key];
       }
     }
@@ -1344,7 +1358,7 @@ export function linkWikiDataArrays(wikiData) {
   assignWikiData(WD.flashData, 'artistData', 'flashActData', 'trackData');
   assignWikiData(WD.flashActData, 'flashData');
   assignWikiData(WD.artTagData, 'albumData', 'trackData');
-  assignWikiData(WD.homepageLayout.rows, 'albumData', 'groupData');
+  assignWikiData(WD.homepageLayout?.rows, 'albumData', 'groupData');
 }
 
 export function sortWikiDataArrays(wikiData) {
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)`);