« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/content/dependencies/linkContribution.js54
-rw-r--r--test/unit/data/cacheable-object.js2
-rw-r--r--test/unit/data/composite/things/track/withAlbum.js40
-rw-r--r--test/unit/data/things/album.js205
-rw-r--r--test/unit/data/things/art-tag.js19
-rw-r--r--test/unit/data/things/track.js165
-rw-r--r--test/unit/data/things/validators.js16
7 files changed, 359 insertions, 142 deletions
diff --git a/test/unit/content/dependencies/linkContribution.js b/test/unit/content/dependencies/linkContribution.js
index 94908901..ab45b03a 100644
--- a/test/unit/content/dependencies/linkContribution.js
+++ b/test/unit/content/dependencies/linkContribution.js
@@ -2,27 +2,27 @@ import t from 'tap';
 import {testContentFunctions} from '#test-lib';
 
 t.test('generateContributionLinks (unit)', async t => {
-  const who1 = {
+  const artist1 = {
     name: 'Clark Powell',
     directory: 'clark-powell',
     urls: ['https://soundcloud.com/plazmataz'],
   };
 
-  const who2 = {
+  const artist2 = {
     name: 'Grounder & Scratch',
     directory: 'the-big-baddies',
     urls: [],
   };
 
-  const who3 = {
+  const artist3 = {
     name: 'Toby Fox',
     directory: 'toby-fox',
     urls: ['https://tobyfox.bandcamp.com/', 'https://toby.fox/'],
   };
 
-  const what1 = null;
-  const what2 = 'Snooping';
-  const what3 = 'Arrangement';
+  const annotation1 = null;
+  const annotation2 = 'Snooping';
+  const annotation3 = 'Arrangement';
 
   await testContentFunctions(t, 'generateContributionLinks (unit 1)', async (t, evaluate) => {
     const slots = {
@@ -34,14 +34,14 @@ t.test('generateContributionLinks (unit)', async t => {
       mock: evaluate.mock(mock => ({
         linkArtist: {
           relations: mock.function('linkArtist.relations', () => ({}))
-            .args([undefined, who1]).next()
-            .args([undefined, who2]).next()
-            .args([undefined, who3]),
+            .args([undefined, artist1]).next()
+            .args([undefined, artist2]).next()
+            .args([undefined, artist3]),
 
           data: mock.function('linkArtist.data', () => ({}))
-            .args([who1]).next()
-            .args([who2]).next()
-            .args([who3]),
+            .args([artist1]).next()
+            .args([artist2]).next()
+            .args([artist3]),
 
           // This can be tweaked to return a specific (mocked) template
           // for each artist if we need to test for slots in the future.
@@ -51,9 +51,9 @@ t.test('generateContributionLinks (unit)', async t => {
 
         linkExternalAsIcon: {
           data: mock.function('linkExternalAsIcon.data', () => ({}))
-            .args([who1.urls[0]]).next()
-            .args([who3.urls[0]]).next()
-            .args([who3.urls[1]]),
+            .args([artist1.urls[0]]).next()
+            .args([artist3.urls[0]]).next()
+            .args([artist3.urls[1]]),
 
           generate: mock.function('linkExternalAsIcon.generate', () => 'icon')
             .repeat(3),
@@ -64,9 +64,9 @@ t.test('generateContributionLinks (unit)', async t => {
     evaluate({
       name: 'linkContribution',
       multiple: [
-        {args: [{who: who1, what: what1}]},
-        {args: [{who: who2, what: what2}]},
-        {args: [{who: who3, what: what3}]},
+        {args: [{artist: artist1, annotation: annotation1}]},
+        {args: [{artist: artist2, annotation: annotation2}]},
+        {args: [{artist: artist3, annotation: annotation3}]},
       ],
       slots,
     });
@@ -82,14 +82,14 @@ t.test('generateContributionLinks (unit)', async t => {
       mock: evaluate.mock(mock => ({
         linkArtist: {
           relations: mock.function('linkArtist.relations', () => ({}))
-            .args([undefined, who1]).next()
-            .args([undefined, who2]).next()
-            .args([undefined, who3]),
+            .args([undefined, artist1]).next()
+            .args([undefined, artist2]).next()
+            .args([undefined, artist3]),
 
           data: mock.function('linkArtist.data', () => ({}))
-            .args([who1]).next()
-            .args([who2]).next()
-            .args([who3]),
+            .args([artist1]).next()
+            .args([artist2]).next()
+            .args([artist3]),
 
           generate: mock.function(() => 'artist link')
             .repeat(3),
@@ -112,9 +112,9 @@ t.test('generateContributionLinks (unit)', async t => {
     evaluate({
       name: 'linkContribution',
       multiple: [
-        {args: [{who: who1, what: what1}]},
-        {args: [{who: who2, what: what2}]},
-        {args: [{who: who3, what: what3}]},
+        {args: [{artist: artist1, annotation: annotation1}]},
+        {args: [{artist: artist2, annotation: annotation2}]},
+        {args: [{artist: artist3, annotation: annotation3}]},
       ],
       slots,
     });
diff --git a/test/unit/data/cacheable-object.js b/test/unit/data/cacheable-object.js
index 8c31a5bc..4b927248 100644
--- a/test/unit/data/cacheable-object.js
+++ b/test/unit/data/cacheable-object.js
@@ -4,7 +4,7 @@ import CacheableObject from '#cacheable-object';
 
 function newCacheableObject(PD) {
   return new (class extends CacheableObject {
-    static propertyDescriptors = PD;
+    static [CacheableObject.propertyDescriptors] = PD;
   });
 }
 
diff --git a/test/unit/data/composite/things/track/withAlbum.js b/test/unit/data/composite/things/track/withAlbum.js
index d822f318..6f50776b 100644
--- a/test/unit/data/composite/things/track/withAlbum.js
+++ b/test/unit/data/composite/things/track/withAlbum.js
@@ -1,5 +1,9 @@
 import t from 'tap';
 
+import '#import-heck';
+
+import Thing from '#thing';
+
 import {compositeFrom, input} from '#composite';
 import {exposeConstant, exposeDependency} from '#composite/control-flow';
 import {withAlbum} from '#composite/things/track';
@@ -21,9 +25,21 @@ t.test(`withAlbum: basic behavior`, t => {
     },
   });
 
-  const fakeTrack1 = {directory: 'foo'};
-  const fakeTrack2 = {directory: 'bar'};
-  const fakeAlbum = {directory: 'baz', tracks: [fakeTrack1]};
+  const fakeTrack1 = {
+    [Thing.isThing]: true,
+    directory: 'foo',
+  };
+
+  const fakeTrack2 = {
+    [Thing.isThing]: true,
+    directory: 'bar',
+  };
+
+  const fakeAlbum = {
+    [Thing.isThing]: true,
+    directory: 'baz',
+    tracks: [fakeTrack1],
+  };
 
   t.equal(
     composite.expose.compute({
@@ -53,9 +69,21 @@ t.test(`withAlbum: early exit conditions`, t => {
     ],
   });
 
-  const fakeTrack1 = {directory: 'foo'};
-  const fakeTrack2 = {directory: 'bar'};
-  const fakeAlbum = {directory: 'baz', tracks: [fakeTrack1]};
+  const fakeTrack1 = {
+    [Thing.isThing]: true,
+    directory: 'foo',
+  };
+
+  const fakeTrack2 = {
+    [Thing.isThing]: true,
+    directory: 'bar',
+  };
+
+  const fakeAlbum = {
+    [Thing.isThing]: true,
+    directory: 'baz',
+    tracks: [fakeTrack1],
+  };
 
   t.equal(
     composite.expose.compute({
diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js
index 46ea83b0..06265b09 100644
--- a/test/unit/data/things/album.js
+++ b/test/unit/data/things/album.js
@@ -8,6 +8,7 @@ const {
   ArtTag,
   Artist,
   Track,
+  TrackSection,
 } = thingConstructors;
 
 function stubArtTag(tagName = `Test Art Tag`) {
@@ -21,8 +22,8 @@ function stubArtistAndContribs() {
   const artist = new Artist();
   artist.name = `Test Artist`;
 
-  const contribs = [{who: `Test Artist`, what: null}];
-  const badContribs = [{who: `Figment of Your Imagination`, what: null}];
+  const contribs = [{artist: `Test Artist`, annotation: null}];
+  const badContribs = [{artist: `Figment of Your Imagination`, annotation: null}];
 
   return {artist, contribs, badContribs};
 }
@@ -34,6 +35,15 @@ function stubTrack(directory = 'foo') {
   return track;
 }
 
+function stubTrackSection(album, tracks, directory = 'baz') {
+  const trackSection = new TrackSection();
+  trackSection.unqualifiedDirectory = directory;
+  trackSection.tracks = tracks.map(t => Thing.getReference(t));
+  trackSection.ownTrackData = tracks;
+  trackSection.ownAlbumData = [album];
+  return trackSection;
+}
+
 t.test(`Album.artTags`, t => {
   t.plan(3);
 
@@ -237,78 +247,133 @@ t.test(`Album.tracks`, t => {
   t.plan(5);
 
   const album = new Album();
+  album.directory = 'foo';
+
   const track1 = stubTrack('track1');
   const track2 = stubTrack('track2');
   const track3 = stubTrack('track3');
   const tracks = [track1, track2, track3];
 
-  album.ownTrackData = tracks;
+  const section1 = stubTrackSection(album, [], 'section1');
+  const section2 = stubTrackSection(album, [], 'section2');
+  const section3 = stubTrackSection(album, [], 'section3');
+  const section4 = stubTrackSection(album, [], 'section4');
+  const section5 = stubTrackSection(album, [], 'section5');
+  const section6 = stubTrackSection(album, [], 'section6');
+  const sections = [section1, section2, section3, section4, section5, section6];
+
+  const section1_ref = `unqualified-track-section:section1`;
+  const section2_ref = `unqualified-track-section:section2`;
+  const section3_ref = `unqualified-track-section:section3`;
+  const section4_ref = `unqualified-track-section:section4`;
+  const section5_ref = `unqualified-track-section:section5`;
+  const section6_ref = `unqualified-track-section:section6`;
 
   for (const track of tracks) {
     track.albumData = [album];
   }
 
+  for (const section of sections) {
+    section.ownAlbumData = [album];
+  }
+
   t.same(album.tracks, [],
     `Album.tracks #1: defaults to empty array`);
 
-  album.trackSections = [
-    {tracks: ['track:track1', 'track:track2', 'track:track3']},
-  ];
+  section1.tracks = ['track:track1', 'track:track2', 'track:track3'];
+  section1.ownTrackData = [track1, track2, track3];
+
+  album.trackSections = [section1_ref];
+  album.ownTrackSectionData = [section1];
 
   t.same(album.tracks, [track1, track2, track3],
     `Album.tracks #2: pulls tracks from one track section`);
 
-  album.trackSections = [
-    {tracks: ['track:track1']},
-    {tracks: ['track:track2', 'track:track3']},
-  ];
+  section1.tracks = ['track:track1'];
+  section2.tracks = ['track:track2', 'track:track3'];
+
+  section1.ownTrackData = [track1];
+  section2.ownTrackData = [track2, track3];
+
+  album.trackSections = [section1_ref, section2_ref];
+  album.ownTrackSectionData = [section1, section2];
 
   t.same(album.tracks, [track1, track2, track3],
     `Album.tracks #3: pulls tracks from multiple track sections`);
 
-  album.trackSections = [
-    {tracks: ['track:track1', 'track:does-not-exist']},
-    {tracks: ['track:this-one-neither', 'track:track2']},
-    {tracks: ['track:effectively-empty-section']},
-    {tracks: ['track:track3']},
-  ];
+  section1.tracks = ['track:track1', 'track:does-not-exist'];
+  section2.tracks = ['track:this-one-neither', 'track:track2'];
+  section3.tracks = ['track:effectively-empty-section'];
+  section4.tracks = ['track:track3'];
+
+  section1.ownTrackData = [track1];
+  section2.ownTrackData = [track2];
+  section3.ownTrackData = [];
+  section4.ownTrackData = [track3];
+
+  album.trackSections = [section1_ref, section2_ref, section3_ref, section4_ref];
+  album.ownTrackSectionData = [section1, section2, section3, section4];
 
   t.same(album.tracks, [track1, track2, track3],
     `Album.tracks #4: filters out references without matches`);
 
-  album.trackSections = [
-    {tracks: ['track:track1']},
-    {},
-    {tracks: ['track:track2']},
-    {},
-    {},
-    {tracks: ['track:track3']},
-  ];
+  section1.tracks = ['track:track1'];
+  section2.tracks = [];
+  section3.tracks = ['track:track2'];
+  section4.tracks = [];
+  section5.tracks = [];
+  section6.tracks = ['track:track3'];
+
+  section1.ownTrackData = [track1];
+  section2.ownTrackData = [];
+  section3.ownTrackData = [track2];
+  section4.ownTrackData = [];
+  section5.ownTrackData = [];
+  section6.ownTrackData = [track3];
+
+  album.trackSections = [section1_ref, section2_ref, section3_ref, section4_ref, section5_ref, section6_ref];
+  album.ownTrackSectionData = [section1, section2, section3, section4, section5, section6];
 
   t.same(album.tracks, [track1, track2, track3],
-    `Album.tracks #5: skips missing tracks property`);
+    `Album.tracks #5: skips empty track sections`);
 });
 
 t.test(`Album.trackSections`, t => {
   t.plan(7);
 
   const album = new Album();
+
   const track1 = stubTrack('track1');
   const track2 = stubTrack('track2');
   const track3 = stubTrack('track3');
   const track4 = stubTrack('track4');
   const tracks = [track1, track2, track3, track4];
 
-  album.ownTrackData = tracks;
+  const section1 = stubTrackSection(album, [], 'section1');
+  const section2 = stubTrackSection(album, [], 'section2');
+  const section3 = stubTrackSection(album, [], 'section3');
+  const section4 = stubTrackSection(album, [], 'section4');
+  const section5 = stubTrackSection(album, [], 'section5');
+  const sections = [section1, section2, section3, section4, section5];
+
+  const section1_ref = `unqualified-track-section:section1`;
+  const section2_ref = `unqualified-track-section:section2`;
+  const section3_ref = `unqualified-track-section:section3`;
+  const section4_ref = `unqualified-track-section:section4`;
+  const section5_ref = `unqualified-track-section:section5`;
 
   for (const track of tracks) {
     track.albumData = [album];
   }
 
-  album.trackSections = [
-    {tracks: ['track:track1', 'track:track2']},
-    {tracks: ['track:track3', 'track:track4']},
-  ];
+  section1.tracks = ['track:track1', 'track:track2'];
+  section2.tracks = ['track:track3', 'track:track4'];
+
+  section1.ownTrackData = [track1, track2];
+  section2.ownTrackData = [track3, track4];
+
+  album.trackSections = [section1_ref, section2_ref];
+  album.ownTrackSectionData = [section1, section2];
 
   t.match(album.trackSections, [
     {tracks: [track1, track2]},
@@ -320,11 +385,19 @@ t.test(`Album.trackSections`, t => {
     {tracks: [track3, track4], startIndex: 2},
   ], `Album.trackSections #2: exposes startIndex`);
 
-  album.trackSections = [
-    {name: 'First section', tracks: ['track:track1']},
-    {name: 'Second section', tracks: ['track:track2']},
-    {tracks: ['track:track3']},
-  ];
+  section1.tracks = ['track:track1'];
+  section2.tracks = ['track:track2'];
+  section3.tracks = ['track:track3'];
+
+  section1.ownTrackData = [track1];
+  section2.ownTrackData = [track2];
+  section3.ownTrackData = [track3];
+
+  section1.name = 'First section';
+  section2.name = 'Second section';
+
+  album.trackSections = [section1_ref, section2_ref, section3_ref];
+  album.ownTrackSectionData = [section1, section2, section3];
 
   t.match(album.trackSections, [
     {name: 'First section', tracks: [track1]},
@@ -334,11 +407,11 @@ t.test(`Album.trackSections`, t => {
 
   album.color = '#123456';
 
-  album.trackSections = [
-    {tracks: ['track:track1'], color: null},
-    {tracks: ['track:track2'], color: '#abcdef'},
-    {tracks: ['track:track3'], color: null},
-  ];
+  section2.color = '#abcdef';
+
+  // XXX_decacheWikiData
+  album.trackSections = [];
+  album.trackSections = [section1_ref, section2_ref, section3_ref];
 
   t.match(album.trackSections, [
     {tracks: [track1], color: '#123456'},
@@ -346,11 +419,11 @@ t.test(`Album.trackSections`, t => {
     {tracks: [track3], color: '#123456'},
   ], `Album.trackSections #4: exposes color, inherited from album`);
 
-  album.trackSections = [
-    {tracks: ['track:track1'], dateOriginallyReleased: null},
-    {tracks: ['track:track2'], dateOriginallyReleased: new Date('2009-04-11')},
-    {tracks: ['track:track3'], dateOriginallyReleased: null},
-  ];
+  section2.dateOriginallyReleased = new Date('2009-04-11');
+
+  // XXX_decacheWikiData
+  album.trackSections = [];
+  album.trackSections = [section1_ref, section2_ref, section3_ref];
 
   t.match(album.trackSections, [
     {tracks: [track1], dateOriginallyReleased: null},
@@ -358,11 +431,12 @@ t.test(`Album.trackSections`, t => {
     {tracks: [track3], dateOriginallyReleased: null},
   ], `Album.trackSections #5: exposes dateOriginallyReleased, if present`);
 
-  album.trackSections = [
-    {tracks: ['track:track1'], isDefaultTrackSection: true},
-    {tracks: ['track:track2'], isDefaultTrackSection: false},
-    {tracks: ['track:track3'], isDefaultTrackSection: null},
-  ];
+  section1.isDefaultTrackSection = true;
+  section2.isDefaultTrackSection = false;
+
+  // XXX_decacheWikiData
+  album.trackSections = [];
+  album.trackSections = [section1_ref, section2_ref, section3_ref];
 
   t.match(album.trackSections, [
     {tracks: [track1], isDefaultTrackSection: true},
@@ -370,19 +444,34 @@ t.test(`Album.trackSections`, t => {
     {tracks: [track3], isDefaultTrackSection: false},
   ], `Album.trackSections #6: exposes isDefaultTrackSection, defaults to false`);
 
-  album.trackSections = [
-    {tracks: ['track:track1', 'track:track2', 'track:snooping'], color: '#112233'},
-    {tracks: ['track:track3', 'track:as-usual'],                 color: '#334455'},
-    {tracks: [],                                                 color: '#bbbbba'},
-    {tracks: ['track:icy', 'track:chilly', 'track:frigid'],      color: '#556677'},
-    {tracks: ['track:track4'],                                   color: '#778899'},
-  ];
+  section1.tracks = ['track:track1', 'track:track2', 'track:snooping'];
+  section2.tracks = ['track:track3', 'track:as-usual'];
+  section3.tracks = [];
+  section4.tracks = ['track:icy', 'track:chilly', 'track:frigid'];
+  section5.tracks = ['track:track4'];
+
+  section1.ownTrackData = [track1, track2];
+  section2.ownTrackData = [track3];
+  section3.ownTrackData = [];
+  section4.ownTrackData = [];
+  section5.ownTrackData = [track4];
+
+  section1.color = '#112233';
+  section2.color = '#334455';
+  section3.color = '#bbbbba';
+  section4.color = '#556677';
+  section5.color = '#778899';
+
+  album.trackSections = [section1_ref, section2_ref, section3_ref, section4_ref, section5_ref];
+  album.ownTrackSectionData = [section1, section2, section3, section4, section5];
 
   t.match(album.trackSections, [
     {tracks: [track1, track2], color: '#112233'},
     {tracks: [track3],         color: '#334455'},
+    {tracks: [],               color: '#bbbbba'},
+    {tracks: [],               color: '#556677'},
     {tracks: [track4],         color: '#778899'},
-  ], `Album.trackSections #7: filters out references without matches & empty sections`);
+  ], `Album.trackSections #7: filters out references without matches, keeps empty sections`);
 });
 
 t.test(`Album.wallpaperFileExtension`, t => {
diff --git a/test/unit/data/things/art-tag.js b/test/unit/data/things/art-tag.js
index 561c93ef..cf2135c6 100644
--- a/test/unit/data/things/art-tag.js
+++ b/test/unit/data/things/art-tag.js
@@ -8,18 +8,29 @@ const {
   Artist,
   ArtTag,
   Track,
+  trackSection,
 } = thingConstructors;
 
 function stubAlbum(tracks, directory = 'bar') {
   const album = new Album();
   album.directory = directory;
 
-  const trackRefs = tracks.map(t => Thing.getReference(t));
-  album.trackSections = [{tracks: trackRefs}];
+  const trackSection = stubTrackSection(album, tracks);
+  album.trackSections = [`unqualified-track-section:${trackSection.unqualifiedDirectory}`];
+  album.ownTrackSectionData = [trackSection];
 
   return album;
 }
 
+function stubTrackSection(album, tracks, directory = 'baz') {
+  const trackSection = new TrackSection();
+  trackSection.unqualifiedDirectory = directory;
+  trackSection.tracks = tracks.map(t => Thing.getReference(t));
+  trackSection.ownTrackData = tracks;
+  trackSection.ownAlbumData = [album];
+  return trackSection;
+}
+
 function stubTrack(directory = 'foo') {
   const track = new Track();
   track.directory = directory;
@@ -43,8 +54,8 @@ function stubArtist(artistName = `Test Artist`) {
 
 function stubArtistAndContribs(artistName = `Test Artist`) {
   const artist = stubArtist(artistName);
-  const contribs = [{who: artistName, what: null}];
-  const badContribs = [{who: `Figment of Your Imagination`, what: null}];
+  const contribs = [{artist: artistName, annotation: null}];
+  const badContribs = [{artist: `Figment of Your Imagination`, annotation: null}];
 
   return {artist, contribs, badContribs};
 }
diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js
index b1c1611e..c6695b6f 100644
--- a/test/unit/data/things/track.js
+++ b/test/unit/data/things/track.js
@@ -11,18 +11,29 @@ const {
   FlashAct,
   Thing,
   Track,
+  TrackSection,
 } = thingConstructors;
 
 function stubAlbum(tracks, directory = 'bar') {
   const album = new Album();
   album.directory = directory;
 
-  const trackRefs = tracks.map(t => Thing.getReference(t));
-  album.trackSections = [{tracks: trackRefs}];
+  const trackSection = stubTrackSection(album, tracks);
+  album.trackSections = [`unqualified-track-section:${trackSection.unqualifiedDirectory}`];
+  album.ownTrackSectionData = [trackSection];
 
   return album;
 }
 
+function stubTrackSection(album, tracks, directory = 'baz') {
+  const trackSection = new TrackSection();
+  trackSection.unqualifiedDirectory = directory;
+  trackSection.tracks = tracks.map(t => Thing.getReference(t));
+  trackSection.ownTrackData = tracks;
+  trackSection.ownAlbumData = [album];
+  return trackSection;
+}
+
 function stubTrack(directory = 'foo') {
   const track = new Track();
   track.directory = directory;
@@ -46,8 +57,8 @@ function stubArtist(artistName = `Test Artist`) {
 
 function stubArtistAndContribs(artistName = `Test Artist`) {
   const artist = stubArtist(artistName);
-  const contribs = [{who: artistName, what: null}];
-  const badContribs = [{who: `Figment of Your Imagination`, what: null}];
+  const contribs = [{artist: artistName, annotation: null}];
+  const badContribs = [{artist: `Figment of Your Imagination`, annotation: null}];
 
   return {artist, contribs, badContribs};
 }
@@ -81,16 +92,28 @@ t.test(`Track.album`, t => {
   const track2 = stubTrack('track2');
   const album1 = new Album();
   const album2 = new Album();
+  const section1 = new TrackSection();
+  const section2 = new TrackSection();
+  section1.unqualifiedDirectory = 'section1';
+  section2.unqualifiedDirectory = 'section2';
+  const section1_ref = `unqualified-track-section:section1`;
+  const section2_ref = `unqualified-track-section:section2`;
 
   t.equal(track1.album, null,
     `album #1: defaults to null`);
 
   track1.albumData = [album1, album2];
   track2.albumData = [album1, album2];
-  album1.ownTrackData = [track1, track2];
-  album2.ownTrackData = [track1, track2];
-  album1.trackSections = [{tracks: ['track:track1']}];
-  album2.trackSections = [{tracks: ['track:track2']}];
+  section1.ownTrackData = [track1];
+  section2.ownTrackData = [track2];
+  section1.ownAlbumData = [album1];
+  section2.ownAlbumData = [album2];
+  section1.tracks = ['track:track1'];
+  section2.tracks = ['track:track2'];
+  album1.trackSections = [section1_ref];
+  album2.trackSections = [section2_ref];
+  album1.ownTrackSectionData = [section1];
+  album2.ownTrackSectionData = [section2];
 
   t.equal(track1.album, album1,
     `album #2: is album when album's trackSections matches track`);
@@ -105,21 +128,83 @@ t.test(`Track.album`, t => {
   t.equal(track1.album, null,
     `album #4: is null when track missing albumData`);
 
-  album1.ownTrackData = [];
-  track1.albumData = [album1, album2];
+  section1.ownTrackData = [];
+
+  // XXX_decacheWikiData
+  album1.trackSections = [];
+  album1.trackSections = [section1_ref];
+  track1.albumData = [];
+  track1.albumData = [album2, album1];
 
   t.equal(track1.album, null,
-    `album #5: is null when album missing ownTrackData`);
+    `album #5: is null when album track section missing ownTrackData`);
 
-  album1.ownTrackData = [track1, track2];
-  album1.trackSections = [{tracks: ['track:track2']}];
+  section1.ownTrackData = [track2];
+  section1.tracks = ['track:track2'];
 
   // XXX_decacheWikiData
+  album1.trackSections = [];
+  album1.trackSections = [section1_ref];
   track1.albumData = [];
-  track1.albumData = [album1, album2];
+  track1.albumData = [album2, album1];
 
   t.equal(track1.album, null,
-    `album #6: is null when album's trackSections don't match track`);
+    `album #6: is null when album track section doesn't match track`);
+});
+
+t.test(`Track.alwaysReferenceByDirectory`, t => {
+  t.plan(7);
+
+  const {track: originalTrack, album: originalAlbum} =
+    stubTrackAndAlbum('original-track', 'original-album');
+
+  const {track: rereleaseTrack, album: rereleaseAlbum} =
+    stubTrackAndAlbum('rerelease-track', 'rerelease-album');
+
+  originalTrack.name = 'Cowabunga';
+  rereleaseTrack.name = 'Cowabunga';
+
+  originalTrack.dataSourceAlbum = 'album:original-album';
+  rereleaseTrack.dataSourceAlbum = 'album:rerelease-album';
+
+  rereleaseTrack.originalReleaseTrack = 'track:original-track';
+
+  const {XXX_decacheWikiData} = linkAndBindWikiData({
+    albumData: [originalAlbum, rereleaseAlbum],
+    trackData: [originalTrack, rereleaseTrack],
+  });
+
+  t.equal(originalTrack.alwaysReferenceByDirectory, false,
+    `alwaysReferenceByDirectory #1: defaults to false`);
+
+  t.equal(rereleaseTrack.alwaysReferenceByDirectory, true,
+    `alwaysReferenceByDirectory #2: is true if rerelease name matches original`);
+
+  rereleaseTrack.name = 'Foo Dog!';
+
+  t.equal(rereleaseTrack.alwaysReferenceByDirectory, false,
+    `alwaysReferenceByDirectory #3: is false if rerelease name doesn't match original`);
+
+  rereleaseTrack.name = `COWabunga`;
+
+  t.equal(rereleaseTrack.alwaysReferenceByDirectory, false,
+    `alwaysReferenceByDirectory #4: is false if rerelease name doesn't match original exactly`);
+
+  rereleaseAlbum.alwaysReferenceTracksByDirectory = true;
+  XXX_decacheWikiData();
+
+  t.equal(rereleaseTrack.alwaysReferenceByDirectory, true,
+    `alwaysReferenceByDirectory #5: is true if album's alwaysReferenceTracksByDirectory is true`);
+
+  rereleaseTrack.alwaysReferenceByDirectory = false;
+
+  t.equal(rereleaseTrack.alwaysReferenceByDirectory, false,
+    `alwaysReferenceByDirectory #6: doesn't inherit from album if set to false`);
+
+  rereleaseTrack.name = 'Cowabunga';
+
+  t.equal(rereleaseTrack.alwaysReferenceByDirectory, false,
+    `alwaysReferenceByDirectory #7: doesn't compare original release name if set to false`);
 });
 
 t.test(`Track.artTags`, t => {
@@ -189,31 +274,31 @@ t.test(`Track.artistContribs`, t => {
     `artistContribs #1: defaults to empty array`);
 
   album.artistContribs = [
-    {who: `Artist 1`, what: `composition`},
-    {who: `Artist 2`, what: null},
+    {artist: `Artist 1`, annotation: `composition`},
+    {artist: `Artist 2`, annotation: null},
   ];
 
   XXX_decacheWikiData();
 
   t.same(track.artistContribs,
-    [{who: artist1, what: `composition`}, {who: artist2, what: null}],
+    [{artist: artist1, annotation: `composition`}, {artist: artist2, annotation: null}],
     `artistContribs #2: inherits album artistContribs`);
 
   track.artistContribs = [
-    {who: `Artist 1`, what: `arrangement`},
+    {artist: `Artist 1`, annotation: `arrangement`},
   ];
 
-  t.same(track.artistContribs, [{who: artist1, what: `arrangement`}],
+  t.same(track.artistContribs, [{artist: artist1, annotation: `arrangement`}],
     `artistContribs #3: resolves from own value`);
 
   track.artistContribs = [
-    {who: `Artist 1`, what: `snooping`},
-    {who: `Artist 413`, what: `as`},
-    {who: `Artist 2`, what: `usual`},
+    {artist: `Artist 1`, annotation: `snooping`},
+    {artist: `Artist 413`, annotation: `as`},
+    {artist: `Artist 2`, annotation: `usual`},
   ];
 
   t.same(track.artistContribs,
-    [{who: artist1, what: `snooping`}, {who: artist2, what: `usual`}],
+    [{artist: artist1, annotation: `snooping`}, {artist: artist2, annotation: `usual`}],
     `artistContribs #4: filters out names without matches`);
 });
 
@@ -230,11 +315,14 @@ t.test(`Track.color`, t => {
   t.equal(track.color, null,
     `color #1: defaults to null`);
 
+  const section = stubTrackSection(album, [track], 'section');
+
   album.color = '#abcdef';
-  album.trackSections = [{
-    color: '#beeeef',
-    tracks: [Thing.getReference(track)],
-  }];
+  section.color = '#beeeef';
+
+  album.trackSections = [`unqualified-track-section:section`];
+  album.ownTrackSectionData = [section];
+
   XXX_decacheWikiData();
 
   t.equal(track.color, '#beeeef',
@@ -248,6 +336,7 @@ t.test(`Track.color`, t => {
   track.albumData = [
     {
       constructor: {[Thing.referenceType]: 'album'},
+      [Thing.isThing]: true,
       color: '#abcdef',
       tracks: [track],
       trackSections: [
@@ -303,7 +392,7 @@ t.test(`Track.commentatorArtists`, t => {
     `Track.commentatorArtists #2: works with two commentators`);
 
   track.commentary = commentary +=
-    `<i>Icy|<b>Icy What You Did There</b>:</i>\n` +
+    `<i>Icy|<b>Icy annotation You Did There</b>:</i>\n` +
     `Incredible.\n`;
 
   t.same(track.commentatorArtists, [artist1, artist2, artist3],
@@ -362,31 +451,31 @@ t.test(`Track.coverArtistContribs`, t => {
     `coverArtistContribs #1: defaults to empty array`);
 
   album.trackCoverArtistContribs = [
-    {who: `Artist 1`, what: `lines`},
-    {who: `Artist 2`, what: null},
+    {artist: `Artist 1`, annotation: `lines`},
+    {artist: `Artist 2`, annotation: null},
   ];
 
   XXX_decacheWikiData();
 
   t.same(track.coverArtistContribs,
-    [{who: artist1, what: `lines`}, {who: artist2, what: null}],
+    [{artist: artist1, annotation: `lines`}, {artist: artist2, annotation: null}],
     `coverArtistContribs #2: inherits album trackCoverArtistContribs`);
 
   track.coverArtistContribs = [
-    {who: `Artist 1`, what: `collage`},
+    {artist: `Artist 1`, annotation: `collage`},
   ];
 
-  t.same(track.coverArtistContribs, [{who: artist1, what: `collage`}],
+  t.same(track.coverArtistContribs, [{artist: artist1, annotation: `collage`}],
     `coverArtistContribs #3: resolves from own value`);
 
   track.coverArtistContribs = [
-    {who: `Artist 1`, what: `snooping`},
-    {who: `Artist 413`, what: `as`},
-    {who: `Artist 2`, what: `usual`},
+    {artist: `Artist 1`, annotation: `snooping`},
+    {artist: `Artist 413`, annotation: `as`},
+    {artist: `Artist 2`, annotation: `usual`},
   ];
 
   t.same(track.coverArtistContribs,
-    [{who: artist1, what: `snooping`}, {who: artist2, what: `usual`}],
+    [{artist: artist1, annotation: `snooping`}, {artist: artist2, annotation: `usual`}],
     `coverArtistContribs #4: filters out names without matches`);
 
   track.disableUniqueCoverArt = true;
diff --git a/test/unit/data/things/validators.js b/test/unit/data/things/validators.js
index 11134a90..3a217d6f 100644
--- a/test/unit/data/things/validators.js
+++ b/test/unit/data/things/validators.js
@@ -1,5 +1,5 @@
 import t from 'tap';
-import {showAggregate} from '#sugar';
+import {showAggregate} from '#aggregate';
 
 import {
   // Basic types
@@ -280,17 +280,17 @@ t.test('isContentString', t => {
 
 t.test('isContribution', t => {
   t.plan(4);
-  t.ok(isContribution({who: 'artist:toby-fox', what: 'Music'}));
-  t.ok(isContribution({who: 'Toby Fox'}));
-  t.throws(() => isContribution(({who: 'group:umspaf', what: 'Organizing'})),
-    {errors: /who/});
-  t.throws(() => isContribution(({who: 'artist:toby-fox', what: 123})),
-    {errors: /what/});
+  t.ok(isContribution({artist: 'artist:toby-fox', annotation: 'Music'}));
+  t.ok(isContribution({artist: 'Toby Fox'}));
+  t.throws(() => isContribution(({artist: 'group:umspaf', annotation: 'Organizing'})),
+    {errors: /artist/});
+  t.throws(() => isContribution(({artist: 'artist:toby-fox', annotation: 123})),
+    {errors: /annotation/});
 });
 
 t.test('isContributionList', t => {
   t.plan(4);
-  t.ok(isContributionList([{who: 'Beavis'}, {who: 'Butthead', what: 'Wrangling'}]));
+  t.ok(isContributionList([{artist: 'Beavis'}, {artist: 'Butthead', annotation: 'Wrangling'}]));
   t.ok(isContributionList([]));
   t.throws(() => isContributionList(2));
   t.throws(() => isContributionList(['Charlie', 'Woodstock']));