« 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/data
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/data')
-rw-r--r--test/unit/data/cacheable-object.js12
-rw-r--r--test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js18
-rw-r--r--test/unit/data/composite/data/withPropertiesFromObject.js59
-rw-r--r--test/unit/data/composite/data/withPropertyFromObject.js111
-rw-r--r--test/unit/data/composite/data/withUniqueItemsOnly.js23
-rw-r--r--test/unit/data/composite/things/track/withAlbum.js119
-rw-r--r--test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js102
-rw-r--r--test/unit/data/things/album.js451
-rw-r--r--test/unit/data/things/art-tag.js71
-rw-r--r--test/unit/data/things/flash.js55
-rw-r--r--test/unit/data/things/track.js815
-rw-r--r--test/unit/data/validators.js (renamed from test/unit/data/things/validators.js)4
12 files changed, 139 insertions, 1701 deletions
diff --git a/test/unit/data/cacheable-object.js b/test/unit/data/cacheable-object.js
index 4b927248..4be31788 100644
--- a/test/unit/data/cacheable-object.js
+++ b/test/unit/data/cacheable-object.js
@@ -2,10 +2,14 @@ import t from 'tap';
 
 import CacheableObject from '#cacheable-object';
 
-function newCacheableObject(PD) {
-  return new (class extends CacheableObject {
-    static [CacheableObject.propertyDescriptors] = PD;
-  });
+function newCacheableObject(propertyDescriptors) {
+  const constructor = class extends CacheableObject {
+    static [CacheableObject.propertyDescriptors] = propertyDescriptors;
+  };
+
+  constructor.finalizeCacheableObjectPrototype();
+
+  return Reflect.construct(constructor, []);
 }
 
 t.test(`CacheableObject simple separate update & expose`, t => {
diff --git a/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js b/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js
index 2bcabb4f..9d588e4c 100644
--- a/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js
+++ b/test/unit/data/composite/control-flow/withResultOfAvailabilityCheck.js
@@ -177,10 +177,11 @@ t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => {
       mode: 'banana',
     }),
     {message: `Error computing composition`, cause:
-      {message: `Error computing composition withResultOfAvailabilityCheck`, cause:
-        {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [
-          {message: `mode: Expected one of null empty falsy index, got banana`},
-        ]}}});
+      {message: `Error in step 1 of 2, withResultOfAvailabilityCheck`, cause:
+        {message: `Error computing composition withResultOfAvailabilityCheck`, cause:
+          {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [
+            {message: `mode: Expected one of null empty falsy index, got banana`},
+          ]}}}});
 
   t.throws(
     () => composite.expose.compute({
@@ -188,8 +189,9 @@ t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => {
       mode: null,
     }),
     {message: `Error computing composition`, cause:
-      {message: `Error computing composition withResultOfAvailabilityCheck`, cause:
-        {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [
-          {message: `mode: Expected a value, got null`},
-        ]}}});
+      {message: `Error in step 1 of 2, withResultOfAvailabilityCheck`, cause:
+        {message: `Error computing composition withResultOfAvailabilityCheck`, cause:
+          {message: `Errors in input values provided to withResultOfAvailabilityCheck`, errors: [
+            {message: `mode: Expected a value, got null`},
+          ]}}}});
 });
diff --git a/test/unit/data/composite/data/withPropertiesFromObject.js b/test/unit/data/composite/data/withPropertiesFromObject.js
index 750dc8c4..b81d51a5 100644
--- a/test/unit/data/composite/data/withPropertiesFromObject.js
+++ b/test/unit/data/composite/data/withPropertiesFromObject.js
@@ -1,4 +1,5 @@
 import t from 'tap';
+import {quickCheckCompositeOutputs} from '#test-lib';
 
 import {compositeFrom, input} from '#composite';
 import {exposeDependency} from '#composite/control-flow';
@@ -62,6 +63,8 @@ t.test(`withPropertiesFromObject: output shapes & values`, t => {
       ['foo', 'baz', 'missing3'],
   };
 
+  const qcco = quickCheckCompositeOutputs(t, dependencies);
+
   const mapLevel1 = [
     [input.value('prefix_value'), [
       ['object_dependency', [
@@ -153,28 +156,10 @@ t.test(`withPropertiesFromObject: output shapes & values`, t => {
           properties: propertiesInput,
         });
 
-        quickCheckOutputs(step, outputDict);
+        qcco(step, outputDict);
       }
     }
   }
-
-  function quickCheckOutputs(step, outputDict) {
-    t.same(
-      Object.keys(step.toDescription().outputs),
-      Object.keys(outputDict));
-
-    const composite = compositeFrom({
-      compose: false,
-      steps: [step, {
-        dependencies: Object.keys(outputDict),
-        compute: dependencies => dependencies,
-      }],
-    });
-
-    t.same(
-      composite.expose.compute(dependencies),
-      outputDict);
-  }
 });
 
 t.test(`withPropertiesFromObject: validate static inputs`, t => {
@@ -226,11 +211,12 @@ t.test(`withPropertiesFromObject: validate dynamic inputs`, t => {
       properties: 'onceMore',
     }),
     {message: `Error computing composition`, cause:
-      {message: `Error computing composition withPropertiesFromObject`, cause:
-        {message: `Errors in input values provided to withPropertiesFromObject`, errors: [
-          {message: `object: Expected an object, got string`},
-          {message: `properties: Expected an array, got string`},
-        ]}}});
+      {message: `Error in step 1 of 2, withPropertiesFromObject`, cause:
+        {message: `Error computing composition withPropertiesFromObject`, cause:
+          {message: `Errors in input values provided to withPropertiesFromObject`, errors: [
+            {message: `object: Expected an object, got string`},
+            {message: `properties: Expected an array, got string`},
+          ]}}}});
 
   t.throws(
     () => composite.expose.compute({
@@ -238,17 +224,18 @@ t.test(`withPropertiesFromObject: validate dynamic inputs`, t => {
       properties: ['abc', 'def', 123],
     }),
     {message: `Error computing composition`, cause:
-      {message: `Error computing composition withPropertiesFromObject`, cause:
-        {message: `Errors in input values provided to withPropertiesFromObject`, errors: [
-          {message: `object: Expected an object, got array`},
-          {message: `properties: Errors validating array items`, errors: [
-            {
-              [Symbol.for('hsmusic.annotateError.indexInSourceArray')]: 2,
-              message: `Error at zero-index 2: 123`,
-              cause: {
-                message: `Expected a string, got number`,
+      {message: `Error in step 1 of 2, withPropertiesFromObject`, cause:
+        {message: `Error computing composition withPropertiesFromObject`, cause:
+          {message: `Errors in input values provided to withPropertiesFromObject`, errors: [
+            {message: `object: Expected an object, got array`},
+            {message: `properties: Errors validating array items`, errors: [
+              {
+                [Symbol.for('hsmusic.annotateError.indexInSourceArray')]: 2,
+                message: `Error at zero-index 2: 123`,
+                cause: {
+                  message: `Expected a string, got number`,
+                },
               },
-            },
-          ]},
-        ]}}});
+            ]},
+          ]}}}});
 });
diff --git a/test/unit/data/composite/data/withPropertyFromObject.js b/test/unit/data/composite/data/withPropertyFromObject.js
index 6a772c36..068932e2 100644
--- a/test/unit/data/composite/data/withPropertyFromObject.js
+++ b/test/unit/data/composite/data/withPropertyFromObject.js
@@ -1,5 +1,7 @@
 import t from 'tap';
+import {quickCheckCompositeOutputs} from '#test-lib';
 
+import CacheableObject from '#cacheable-object';
 import {compositeFrom, input} from '#composite';
 import {exposeDependency} from '#composite/control-flow';
 import {withPropertyFromObject} from '#composite/data';
@@ -42,6 +44,93 @@ t.test(`withPropertyFromObject: basic behavior`, t => {
   }), null);
 });
 
+t.test(`withPropertyFromObject: "internal" input`, t => {
+  t.plan(7);
+
+  const composite = compositeFrom({
+    compose: false,
+
+    steps: [
+      withPropertyFromObject({
+        object: 'object',
+        property: 'property',
+        internal: 'internal',
+      }),
+
+      exposeDependency({dependency: '#value'}),
+    ],
+  });
+
+  const constructor = class extends CacheableObject {
+    static [CacheableObject.propertyDescriptors] = {
+      foo: {
+        flags: {update: true, expose: false},
+      },
+
+      bar: {
+        flags: {update: true, expose: true},
+      },
+
+      baz: {
+        flags: {update: true, expose: true},
+        expose: {
+          transform: baz => baz * 2,
+        },
+      },
+    };
+  };
+
+  constructor.finalizeCacheableObjectPrototype();
+
+  const thing = Reflect.construct(constructor, []);
+
+  thing.foo = 100;
+  thing.bar = 200;
+  thing.baz = 300;
+
+  t.match(composite, {
+    expose: {
+      dependencies: ['object', 'property', 'internal'],
+    },
+  });
+
+  t.equal(composite.expose.compute({
+    object: thing,
+    property: 'foo',
+    internal: true,
+  }), 100);
+
+  t.equal(composite.expose.compute({
+    object: thing,
+    property: 'bar',
+    internal: true,
+  }), 200);
+
+  t.equal(composite.expose.compute({
+    object: thing,
+    property: 'baz',
+    internal: true,
+  }), 300);
+
+  t.equal(composite.expose.compute({
+    object: thing,
+    property: 'baz',
+    internal: false,
+  }), 600);
+
+  t.equal(composite.expose.compute({
+    object: thing,
+    property: 'bimbam',
+    internal: false,
+  }), null);
+
+  t.equal(composite.expose.compute({
+    object: null,
+    property: 'bambim',
+    internal: false,
+  }), null);
+});
+
 t.test(`withPropertyFromObject: output shapes & values`, t => {
   t.plan(2 * 3 ** 2);
 
@@ -56,6 +145,8 @@ t.test(`withPropertyFromObject: output shapes & values`, t => {
       'baz',
   };
 
+  const qcco = quickCheckCompositeOutputs(t, dependencies);
+
   const mapLevel1 = [
     ['object_dependency', [
       ['property_dependency', {
@@ -98,25 +189,7 @@ t.test(`withPropertyFromObject: output shapes & values`, t => {
         property: propertyInput,
       });
 
-      quickCheckOutputs(step, outputDict);
+      qcco(step, outputDict);
     }
   }
-
-  function quickCheckOutputs(step, outputDict) {
-    t.same(
-      Object.keys(step.toDescription().outputs),
-      Object.keys(outputDict));
-
-    const composite = compositeFrom({
-      compose: false,
-      steps: [step, {
-        dependencies: Object.keys(outputDict),
-        compute: dependencies => dependencies,
-      }],
-    });
-
-    t.same(
-      composite.expose.compute(dependencies),
-      outputDict);
-  }
 });
diff --git a/test/unit/data/composite/data/withUniqueItemsOnly.js b/test/unit/data/composite/data/withUniqueItemsOnly.js
index 965b14b5..50b16f43 100644
--- a/test/unit/data/composite/data/withUniqueItemsOnly.js
+++ b/test/unit/data/composite/data/withUniqueItemsOnly.js
@@ -1,4 +1,5 @@
 import t from 'tap';
+import {quickCheckCompositeOutputs} from '#test-lib';
 
 import {compositeFrom, input} from '#composite';
 import {exposeDependency} from '#composite/control-flow';
@@ -44,6 +45,8 @@ t.test(`withUniqueItemsOnly: output shapes & values`, t => {
       [8, 8, 7, 6, 6, 5, 'bar', true, true, 5],
   };
 
+  const qcco = quickCheckCompositeOutputs(t, dependencies);
+
   const mapLevel1 = [
     ['list_dependency', {
       '#list_dependency': [1, 2, 3, 4, 'foo', false],
@@ -61,24 +64,6 @@ t.test(`withUniqueItemsOnly: output shapes & values`, t => {
       list: listInput,
     });
 
-    quickCheckOutputs(step, outputDict);
-  }
-
-  function quickCheckOutputs(step, outputDict) {
-    t.same(
-      Object.keys(step.toDescription().outputs),
-      Object.keys(outputDict));
-
-    const composite = compositeFrom({
-      compose: false,
-      steps: [step, {
-        dependencies: Object.keys(outputDict),
-        compute: dependencies => dependencies,
-      }],
-    });
-
-    t.same(
-      composite.expose.compute(dependencies),
-      outputDict);
+    qcco(step, outputDict);
   }
 });
diff --git a/test/unit/data/composite/things/track/withAlbum.js b/test/unit/data/composite/things/track/withAlbum.js
deleted file mode 100644
index 6f50776b..00000000
--- a/test/unit/data/composite/things/track/withAlbum.js
+++ /dev/null
@@ -1,119 +0,0 @@
-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';
-
-t.test(`withAlbum: basic behavior`, t => {
-  t.plan(3);
-
-  const composite = compositeFrom({
-    compose: false,
-    steps: [
-      withAlbum(),
-      exposeDependency({dependency: '#album'}),
-    ],
-  });
-
-  t.match(composite, {
-    expose: {
-      dependencies: ['albumData', 'this'],
-    },
-  });
-
-  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({
-      albumData: [fakeAlbum],
-      this: fakeTrack1,
-    }),
-    fakeAlbum);
-
-  t.equal(
-    composite.expose.compute({
-      albumData: [fakeAlbum],
-      this: fakeTrack2,
-    }),
-    null);
-});
-
-t.test(`withAlbum: early exit conditions`, t => {
-  t.plan(4);
-
-  const composite = compositeFrom({
-    compose: false,
-    steps: [
-      withAlbum(),
-      exposeConstant({
-        value: input.value('bimbam'),
-      }),
-    ],
-  });
-
-  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({
-      albumData: [fakeAlbum],
-      this: fakeTrack1,
-    }),
-    'bimbam',
-    `does not early exit if albumData is present and contains the track`);
-
-  t.equal(
-    composite.expose.compute({
-      albumData: [fakeAlbum],
-      this: fakeTrack2,
-    }),
-    'bimbam',
-    `does not early exit if albumData is present and does not contain the track`);
-
-  t.equal(
-    composite.expose.compute({
-      albumData: [],
-      this: fakeTrack1,
-    }),
-    'bimbam',
-    `does not early exit if albumData is empty array`);
-
-  t.equal(
-    composite.expose.compute({
-      albumData: null,
-      this: fakeTrack1,
-    }),
-    null,
-    `early exits if albumData is null`);
-});
diff --git a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js b/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js
deleted file mode 100644
index babe4fae..00000000
--- a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import t from 'tap';
-
-import {compositeFrom, input} from '#composite';
-import thingConstructors from '#things';
-
-import {exposeDependency} from '#composite/control-flow';
-import {withParsedCommentaryEntries} from '#composite/wiki-data';
-
-const {Artist} = thingConstructors;
-
-const composite = compositeFrom({
-  compose: false,
-
-  steps: [
-    withParsedCommentaryEntries({
-      from: 'from',
-    }),
-
-    exposeDependency({dependency: '#parsedCommentaryEntries'}),
-  ],
-});
-
-function stubArtist(artistName = `Test Artist`) {
-  const artist = new Artist();
-  artist.name = artistName;
-
-  return artist;
-}
-
-t.test(`withParsedCommentaryEntries: basic behavior`, t => {
-  t.plan(3);
-
-  const artist1 = stubArtist(`Mobius Trip`);
-  const artist2 = stubArtist(`Hadron Kaleido`);
-
-  const artistData = [artist1, artist2];
-
-  t.match(composite, {
-    expose: {
-      dependencies: ['from', 'artistData'],
-    },
-  });
-
-  t.same(composite.expose.compute({
-    artistData,
-    from:
-      `<i>Mobius Trip:</i>\n` +
-      `Some commentary.\n` +
-      `Very cool.\n`,
-  }), [
-    {
-      artists: [artist1],
-      artistDisplayText: null,
-      annotation: null,
-      date: null,
-      body: `Some commentary.\nVery cool.`,
-    },
-  ]);
-
-  t.same(composite.expose.compute({
-    artistData,
-    from:
-      `<i>Mobius Trip|Moo-bius Trip:</i> (music, art, 12 January 2015)\n` +
-      `First commentary entry.\n` +
-      `Very cool.\n` +
-      `<i>Hadron Kaleido|<b>[[artist:hadron-kaleido|The Ol' Hadron]]</b>:</i> (moral support, 4/4/2022)\n` +
-      `Second commentary entry. Yes. So cool.\n` +
-      `<i>Mystery Artist:</i> (pingas, August 25, 2023)\n` +
-      `Oh no.. Oh dear...\n` +
-      `<i>Mobius Trip, Hadron Kaleido:</i>\n` +
-      `And back around we go.`,
-  }), [
-    {
-      artists: [artist1],
-      artistDisplayText: `Moo-bius Trip`,
-      annotation: `music, art`,
-      date: new Date('12 January 2015'),
-      body: `First commentary entry.\nVery cool.`,
-    },
-    {
-      artists: [artist2],
-      artistDisplayText: `<b>[[artist:hadron-kaleido|The Ol' Hadron]]</b>`,
-      annotation: `moral support`,
-      date: new Date('4 April 2022'),
-      body: `Second commentary entry. Yes. So cool.`,
-    },
-    {
-      artists: [],
-      artistDisplayText: null,
-      annotation: `pingas`,
-      date: new Date('25 August 2023'),
-      body: `Oh no.. Oh dear...`,
-    },
-    {
-      artists: [artist1, artist2],
-      artistDisplayText: null,
-      annotation: null,
-      date: null,
-      body: `And back around we go.`,
-    },
-  ]);
-});
diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js
deleted file mode 100644
index bf9992ae..00000000
--- a/test/unit/data/things/album.js
+++ /dev/null
@@ -1,451 +0,0 @@
-import t from 'tap';
-
-import {linkAndBindWikiData} from '#test-lib';
-import thingConstructors from '#things';
-
-const {
-  Album,
-  ArtTag,
-  Artist,
-  Track,
-} = thingConstructors;
-
-function stubArtTag(tagName = `Test Art Tag`) {
-  const tag = new ArtTag();
-  tag.name = tagName;
-
-  return tag;
-}
-
-function stubArtistAndContribs() {
-  const artist = new Artist();
-  artist.name = `Test Artist`;
-
-  const contribs = [{artist: `Test Artist`, annotation: null}];
-  const badContribs = [{artist: `Figment of Your Imagination`, annotation: null}];
-
-  return {artist, contribs, badContribs};
-}
-
-function stubTrack(directory = 'foo') {
-  const track = new Track();
-  track.directory = directory;
-
-  return track;
-}
-
-t.test(`Album.artTags`, t => {
-  t.plan(3);
-
-  const {artist, contribs} = stubArtistAndContribs();
-  const album = new Album();
-  const tag1 = stubArtTag(`Tag 1`);
-  const tag2 = stubArtTag(`Tag 2`);
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-    artTagData: [tag1, tag2],
-  });
-
-  t.same(album.artTags, [],
-    `artTags #1: defaults to empty array`);
-
-  album.artTags = [`Tag 1`, `Tag 2`];
-
-  t.same(album.artTags, [],
-    `artTags #2: is empty if album doesn't have cover artists`);
-
-  album.coverArtistContribs = contribs;
-
-  t.same(album.artTags, [tag1, tag2],
-    `artTags #3: resolves if album has cover artists`);
-});
-
-t.test(`Album.bannerDimensions`, t => {
-  t.plan(4);
-
-  const album = new Album();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-  });
-
-  t.equal(album.bannerDimensions, null,
-    `Album.bannerDimensions #1: defaults to null`);
-
-  album.bannerDimensions = [1200, 275];
-
-  t.equal(album.bannerDimensions, null,
-    `Album.bannerDimensions #2: is null if bannerArtistContribs empty`);
-
-  album.bannerArtistContribs = badContribs;
-
-  t.equal(album.bannerDimensions, null,
-    `Album.bannerDimensions #3: is null if bannerArtistContribs resolves empty`);
-
-  album.bannerArtistContribs = contribs;
-
-  t.same(album.bannerDimensions, [1200, 275],
-    `Album.bannerDimensions #4: is own value`);
-});
-
-t.test(`Album.bannerFileExtension`, t => {
-  t.plan(5);
-
-  const album = new Album();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-  });
-
-  t.equal(album.bannerFileExtension, null,
-    `Album.bannerFileExtension #1: defaults to null`);
-
-  album.bannerFileExtension = 'png';
-
-  t.equal(album.bannerFileExtension, null,
-    `Album.bannerFileExtension #2: is null if bannerArtistContribs empty`);
-
-  album.bannerArtistContribs = badContribs;
-
-  t.equal(album.bannerFileExtension, null,
-    `Album.bannerFileExtension #3: is null if bannerArtistContribs resolves empty`);
-
-  album.bannerArtistContribs = contribs;
-
-  t.equal(album.bannerFileExtension, 'png',
-    `Album.bannerFileExtension #4: is own value`);
-
-  album.bannerFileExtension = null;
-
-  t.equal(album.bannerFileExtension, 'jpg',
-    `Album.bannerFileExtension #5: defaults to jpg`);
-});
-
-t.test(`Album.bannerStyle`, t => {
-  t.plan(4);
-
-  const album = new Album();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-  });
-
-  t.equal(album.bannerStyle, null,
-    `Album.bannerStyle #1: defaults to null`);
-
-  album.bannerStyle = `opacity: 0.5`;
-
-  t.equal(album.bannerStyle, null,
-    `Album.bannerStyle #2: is null if bannerArtistContribs empty`);
-
-  album.bannerArtistContribs = badContribs;
-
-  t.equal(album.bannerStyle, null,
-    `Album.bannerStyle #3: is null if bannerArtistContribs resolves empty`);
-
-  album.bannerArtistContribs = contribs;
-
-  t.equal(album.bannerStyle, `opacity: 0.5`,
-    `Album.bannerStyle #4: is own value`);
-});
-
-t.test(`Album.coverArtDate`, t => {
-  t.plan(6);
-
-  const album = new Album();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-  });
-
-  t.equal(album.coverArtDate, null,
-    `Album.coverArtDate #1: defaults to null`);
-
-  album.date = new Date('2012-10-25');
-
-  t.equal(album.coverArtDate, null,
-    `Album.coverArtDate #2: is null if coverArtistContribs empty (1/2)`);
-
-  album.coverArtDate = new Date('2011-04-13');
-
-  t.equal(album.coverArtDate, null,
-    `Album.coverArtDate #3: is null if coverArtistContribs empty (2/2)`);
-
-  album.coverArtistContribs = contribs;
-
-  t.same(album.coverArtDate, new Date('2011-04-13'),
-    `Album.coverArtDate #4: is own value`);
-
-  album.coverArtDate = null;
-
-  t.same(album.coverArtDate, new Date(`2012-10-25`),
-    `Album.coverArtDate #5: inherits album release date`);
-
-  album.coverArtistContribs = badContribs;
-
-  t.equal(album.coverArtDate, null,
-    `Album.coverArtDate #6: is null if coverArtistContribs resolves empty`);
-});
-
-t.test(`Album.coverArtFileExtension`, t => {
-  t.plan(5);
-
-  const album = new Album();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-  });
-
-  t.equal(album.coverArtFileExtension, null,
-    `Album.coverArtFileExtension #1: is null if coverArtistContribs empty (1/2)`);
-
-  album.coverArtFileExtension = 'png';
-
-  t.equal(album.coverArtFileExtension, null,
-    `Album.coverArtFileExtension #2: is null if coverArtistContribs empty (2/2)`);
-
-  album.coverArtFileExtension = null;
-  album.coverArtistContribs = contribs;
-
-  t.equal(album.coverArtFileExtension, 'jpg',
-    `Album.coverArtFileExtension #3: defaults to jpg`);
-
-  album.coverArtFileExtension = 'png';
-
-  t.equal(album.coverArtFileExtension, 'png',
-    `Album.coverArtFileExtension #4: is own value`);
-
-  album.coverArtistContribs = badContribs;
-
-  t.equal(album.coverArtFileExtension, null,
-    `Album.coverArtFileExtension #5: is null if coverArtistContribs resolves empty`);
-});
-
-t.test(`Album.tracks`, t => {
-  t.plan(5);
-
-  const album = new Album();
-  const track1 = stubTrack('track1');
-  const track2 = stubTrack('track2');
-  const track3 = stubTrack('track3');
-  const tracks = [track1, track2, track3];
-
-  album.ownTrackData = tracks;
-
-  for (const track of tracks) {
-    track.albumData = [album];
-  }
-
-  t.same(album.tracks, [],
-    `Album.tracks #1: defaults to empty array`);
-
-  album.trackSections = [
-    {tracks: ['track:track1', 'track:track2', 'track:track3']},
-  ];
-
-  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']},
-  ];
-
-  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']},
-  ];
-
-  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']},
-  ];
-
-  t.same(album.tracks, [track1, track2, track3],
-    `Album.tracks #5: skips missing tracks property`);
-});
-
-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;
-
-  for (const track of tracks) {
-    track.albumData = [album];
-  }
-
-  album.trackSections = [
-    {tracks: ['track:track1', 'track:track2']},
-    {tracks: ['track:track3', 'track:track4']},
-  ];
-
-  t.match(album.trackSections, [
-    {tracks: [track1, track2]},
-    {tracks: [track3, track4]},
-  ], `Album.trackSections #1: exposes tracks`);
-
-  t.match(album.trackSections, [
-    {tracks: [track1, track2], startIndex: 0},
-    {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']},
-  ];
-
-  t.match(album.trackSections, [
-    {name: 'First section', tracks: [track1]},
-    {name: 'Second section', tracks: [track2]},
-    {name: 'Unnamed Track Section', tracks: [track3]},
-  ], `Album.trackSections #3: exposes name, with fallback value`);
-
-  album.color = '#123456';
-
-  album.trackSections = [
-    {tracks: ['track:track1'], color: null},
-    {tracks: ['track:track2'], color: '#abcdef'},
-    {tracks: ['track:track3'], color: null},
-  ];
-
-  t.match(album.trackSections, [
-    {tracks: [track1], color: '#123456'},
-    {tracks: [track2], color: '#abcdef'},
-    {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},
-  ];
-
-  t.match(album.trackSections, [
-    {tracks: [track1], dateOriginallyReleased: null},
-    {tracks: [track2], dateOriginallyReleased: new Date('2009-04-11')},
-    {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},
-  ];
-
-  t.match(album.trackSections, [
-    {tracks: [track1], isDefaultTrackSection: true},
-    {tracks: [track2], isDefaultTrackSection: false},
-    {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'},
-  ];
-
-  t.match(album.trackSections, [
-    {tracks: [track1, track2], color: '#112233'},
-    {tracks: [track3],         color: '#334455'},
-    {tracks: [track4],         color: '#778899'},
-  ], `Album.trackSections #7: filters out references without matches & empty sections`);
-});
-
-t.test(`Album.wallpaperFileExtension`, t => {
-  t.plan(5);
-
-  const album = new Album();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-  });
-
-  t.equal(album.wallpaperFileExtension, null,
-    `Album.wallpaperFileExtension #1: defaults to null`);
-
-  album.wallpaperFileExtension = 'png';
-
-  t.equal(album.wallpaperFileExtension, null,
-    `Album.wallpaperFileExtension #2: is null if wallpaperArtistContribs empty`);
-
-  album.wallpaperArtistContribs = contribs;
-
-  t.equal(album.wallpaperFileExtension, 'png',
-    `Album.wallpaperFileExtension #3: is own value`);
-
-  album.wallpaperFileExtension = null;
-
-  t.equal(album.wallpaperFileExtension, 'jpg',
-    `Album.wallpaperFileExtension #4: defaults to jpg`);
-
-  album.wallpaperArtistContribs = badContribs;
-
-  t.equal(album.wallpaperFileExtension, null,
-    `Album.wallpaperFileExtension #5: is null if wallpaperArtistContribs resolves empty`);
-});
-
-t.test(`Album.wallpaperStyle`, t => {
-  t.plan(4);
-
-  const album = new Album();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-  });
-
-  t.equal(album.wallpaperStyle, null,
-    `Album.wallpaperStyle #1: defaults to null`);
-
-  album.wallpaperStyle = `opacity: 0.5`;
-
-  t.equal(album.wallpaperStyle, null,
-    `Album.wallpaperStyle #2: is null if wallpaperArtistContribs empty`);
-
-  album.wallpaperArtistContribs = badContribs;
-
-  t.equal(album.wallpaperStyle, null,
-    `Album.wallpaperStyle #3: is null if wallpaperArtistContribs resolves empty`);
-
-  album.wallpaperArtistContribs = contribs;
-
-  t.equal(album.wallpaperStyle, `opacity: 0.5`,
-    `Album.wallpaperStyle #4: is own value`);
-});
diff --git a/test/unit/data/things/art-tag.js b/test/unit/data/things/art-tag.js
deleted file mode 100644
index 836bb1c3..00000000
--- a/test/unit/data/things/art-tag.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import t from 'tap';
-
-import {linkAndBindWikiData} from '#test-lib';
-import thingConstructors from '#things';
-
-const {
-  Album,
-  Artist,
-  ArtTag,
-  Track,
-} = 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}];
-
-  return album;
-}
-
-function stubTrack(directory = 'foo') {
-  const track = new Track();
-  track.directory = directory;
-
-  return track;
-}
-
-function stubTrackAndAlbum(trackDirectory = 'foo', albumDirectory = 'bar') {
-  const track = stubTrack(trackDirectory);
-  const album = stubAlbum([track], albumDirectory);
-
-  return {track, album};
-}
-
-function stubArtist(artistName = `Test Artist`) {
-  const artist = new Artist();
-  artist.name = artistName;
-
-  return artist;
-}
-
-function stubArtistAndContribs(artistName = `Test Artist`) {
-  const artist = stubArtist(artistName);
-  const contribs = [{artist: artistName, annotation: null}];
-  const badContribs = [{artist: `Figment of Your Imagination`, annotation: null}];
-
-  return {artist, contribs, badContribs};
-}
-
-t.test(`ArtTag.nameShort`, t => {
-  t.plan(3);
-
-  const artTag = new ArtTag();
-
-  artTag.name = `Dave Strider`;
-
-  t.equal(artTag.nameShort, `Dave Strider`,
-    `ArtTag #1: defaults to name`);
-
-  artTag.name = `Dave Strider (Homestuck)`;
-
-  t.equal(artTag.nameShort, `Dave Strider`,
-    `ArtTag #2: trims parenthical part at end`);
-
-  artTag.name = `This (And) That (Then)`;
-
-  t.equal(artTag.nameShort, `This (And) That`,
-    `ArtTag #2: doesn't trim midlde parenthical part`);
-});
diff --git a/test/unit/data/things/flash.js b/test/unit/data/things/flash.js
deleted file mode 100644
index 62059604..00000000
--- a/test/unit/data/things/flash.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import t from 'tap';
-
-import {linkAndBindWikiData} from '#test-lib';
-import thingConstructors from '#things';
-
-const {
-  Flash,
-  FlashAct,
-  Thing,
-} = thingConstructors;
-
-function stubFlash(directory = 'foo') {
-  const flash = new Flash();
-  flash.directory = directory;
-
-  return flash;
-}
-
-function stubFlashAct(flashes, directory = 'bar') {
-  const flashAct = new FlashAct();
-  flashAct.directory = directory;
-  flashAct.flashes = flashes.map(flash => Thing.getReference(flash));
-
-  return flashAct;
-}
-
-t.test(`Flash.color`, t => {
-  t.plan(4);
-
-  const flash = stubFlash();
-  const flashAct = stubFlashAct([flash]);
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    flashData: [flash],
-    flashActData: [flashAct],
-  });
-
-  t.equal(flash.color, null,
-    `color #1: defaults to null`);
-
-  flashAct.color = '#abcdef';
-  XXX_decacheWikiData();
-
-  t.equal(flash.color, '#abcdef',
-    `color #2: inherits from flash act`);
-
-  flash.color = '#123456';
-
-  t.equal(flash.color, '#123456',
-    `color #3: is own value`);
-
-  t.throws(() => { flash.color = '#aeiouw'; },
-    {cause: TypeError},
-    `color #4: must be set to valid color`);
-});
diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js
deleted file mode 100644
index 14d724bf..00000000
--- a/test/unit/data/things/track.js
+++ /dev/null
@@ -1,815 +0,0 @@
-import t from 'tap';
-
-import {linkAndBindWikiData} from '#test-lib';
-import thingConstructors from '#things';
-
-const {
-  Album,
-  ArtTag,
-  Artist,
-  Flash,
-  FlashAct,
-  Thing,
-  Track,
-} = 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}];
-
-  return album;
-}
-
-function stubTrack(directory = 'foo') {
-  const track = new Track();
-  track.directory = directory;
-
-  return track;
-}
-
-function stubTrackAndAlbum(trackDirectory = 'foo', albumDirectory = 'bar') {
-  const track = stubTrack(trackDirectory);
-  const album = stubAlbum([track], albumDirectory);
-
-  return {track, album};
-}
-
-function stubArtist(artistName = `Test Artist`) {
-  const artist = new Artist();
-  artist.name = artistName;
-
-  return artist;
-}
-
-function stubArtistAndContribs(artistName = `Test Artist`) {
-  const artist = stubArtist(artistName);
-  const contribs = [{artist: artistName, annotation: null}];
-  const badContribs = [{artist: `Figment of Your Imagination`, annotation: null}];
-
-  return {artist, contribs, badContribs};
-}
-
-function stubArtTag(tagName = `Test Art Tag`) {
-  const tag = new ArtTag();
-  tag.name = tagName;
-
-  return tag;
-}
-
-function stubFlashAndAct(directory = 'zam') {
-  const flash = new Flash();
-  flash.directory = directory;
-
-  const flashAct = new FlashAct();
-  flashAct.flashes = [Thing.getReference(flash)];
-
-  return {flash, flashAct};
-}
-
-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.ownTrackData = [track1, track2];
-  album2.ownTrackData = [track1, track2];
-  album1.trackSections = [{tracks: ['track:track1']}];
-  album2.trackSections = [{tracks: ['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.ownTrackData = [];
-  track1.albumData = [album1, album2];
-
-  t.equal(track1.album, null,
-    `album #5: is null when album missing ownTrackData`);
-
-  album1.ownTrackData = [track1, track2];
-  album1.trackSections = [{tracks: ['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.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 => {
-  t.plan(6);
-
-  const {track, album} = stubTrackAndAlbum();
-  const {artist, contribs} = stubArtistAndContribs();
-  const tag1 = stubArtTag(`Tag 1`);
-  const tag2 = stubArtTag(`Tag 2`);
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-    artTagData: [tag1, tag2],
-    trackData: [track],
-  });
-
-  t.same(track.artTags, [],
-    `artTags #1: defaults to empty array`);
-
-  track.artTags = [`Tag 1`, `Tag 2`];
-
-  t.same(track.artTags, [],
-    `artTags #2: is empty if track doesn't have cover artists`);
-
-  track.coverArtistContribs = contribs;
-
-  t.same(track.artTags, [tag1, tag2],
-    `artTags #3: resolves if track has cover artists`);
-
-  track.coverArtistContribs = null;
-  album.trackCoverArtistContribs = contribs;
-
-  XXX_decacheWikiData();
-
-  t.same(track.artTags, [tag1, tag2],
-    `artTags #4: resolves if track inherits cover artists`);
-
-  track.disableUniqueCoverArt = true;
-
-  t.same(track.artTags, [],
-    `artTags #5: is empty if track disables unique cover artwork`);
-
-  album.coverArtistContribs = contribs;
-  album.artTags = [`Tag 2`];
-
-  XXX_decacheWikiData();
-
-  t.notSame(track.artTags, [tag2],
-    `artTags #6: doesn't inherit from album's art tags`);
-});
-
-t.test(`Track.artistContribs`, t => {
-  t.plan(4);
-
-  const {track, album} = stubTrackAndAlbum();
-  const artist1 = stubArtist(`Artist 1`);
-  const artist2 = stubArtist(`Artist 2`);
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist1, artist2],
-    trackData: [track],
-  });
-
-  t.same(track.artistContribs, [],
-    `artistContribs #1: defaults to empty array`);
-
-  album.artistContribs = [
-    {artist: `Artist 1`, annotation: `composition`},
-    {artist: `Artist 2`, annotation: null},
-  ];
-
-  XXX_decacheWikiData();
-
-  t.same(track.artistContribs,
-    [{artist: artist1, annotation: `composition`}, {artist: artist2, annotation: null}],
-    `artistContribs #2: inherits album artistContribs`);
-
-  track.artistContribs = [
-    {artist: `Artist 1`, annotation: `arrangement`},
-  ];
-
-  t.same(track.artistContribs, [{artist: artist1, annotation: `arrangement`}],
-    `artistContribs #3: resolves from own value`);
-
-  track.artistContribs = [
-    {artist: `Artist 1`, annotation: `snooping`},
-    {artist: `Artist 413`, annotation: `as`},
-    {artist: `Artist 2`, annotation: `usual`},
-  ];
-
-  t.same(track.artistContribs,
-    [{artist: artist1, annotation: `snooping`}, {artist: artist2, annotation: `usual`}],
-    `artistContribs #4: filters out names without matches`);
-});
-
-t.test(`Track.color`, t => {
-  t.plan(5);
-
-  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';
-  album.trackSections = [{
-    color: '#beeeef',
-    tracks: [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.
-  track.albumData = [
-    {
-      constructor: {[Thing.referenceType]: 'album'},
-      [Thing.isThing]: true,
-      color: '#abcdef',
-      tracks: [track],
-      trackSections: [
-        {color: '#baaaad', tracks: []},
-      ],
-    },
-  ];
-
-  t.equal(track.color, '#abcdef',
-    `color #3: inherits from album without matching track section`);
-
-  track.color = '#123456';
-
-  t.equal(track.color, '#123456',
-    `color #4: is own value`);
-
-  t.throws(() => { track.color = '#aeiouw'; },
-    {cause: TypeError},
-    `color #5: must be set to valid color`);
-});
-
-t.test(`Track.commentatorArtists`, t => {
-  t.plan(8);
-
-  const track = new Track();
-  const artist1 = stubArtist(`SnooPING`);
-  const artist2 = stubArtist(`ASUsual`);
-  const artist3 = stubArtist(`Icy`);
-
-  linkAndBindWikiData({
-    trackData: [track],
-    artistData: [artist1, artist2, artist3],
-  });
-
-  // Keep track of the last commentary string in a separate value, since
-  // the track.commentary property exposes as a completely different format
-  // (i.e. an array of objects, one for each entry), and so isn't compatible
-  // with the += operator on its own.
-  let commentary;
-
-  track.commentary = commentary =
-    `<i>SnooPING:</i>\n` +
-    `Wow.\n`;
-
-  t.same(track.commentatorArtists, [artist1],
-    `Track.commentatorArtists #1: works with one commentator`);
-
-  track.commentary = commentary +=
-    `<i>ASUsual:</i>\n` +
-    `Yes!\n`;
-
-  t.same(track.commentatorArtists, [artist1, artist2],
-    `Track.commentatorArtists #2: works with two commentators`);
-
-  track.commentary = commentary +=
-    `<i>Icy|<b>Icy annotation You Did There</b>:</i>\n` +
-    `Incredible.\n`;
-
-  t.same(track.commentatorArtists, [artist1, artist2, artist3],
-    `Track.commentatorArtists #3: works with custom artist text`);
-
-  track.commentary = commentary =
-    `<i>Icy:</i> (project manager)\n` +
-    `Very good track.\n`;
-
-  t.same(track.commentatorArtists, [artist3],
-    `Track.commentatorArtists #4: works with annotation`);
-
-  track.commentary = commentary =
-    `<i>Icy:</i> (project manager, 08/15/2023)\n` +
-    `Very very good track.\n`;
-
-  t.same(track.commentatorArtists, [artist3],
-    `Track.commentatorArtists #5: works with date`);
-
-  track.commentary = commentary +=
-    `<i>Ohohohoho:</i>\n` +
-    `OHOHOHOHOHOHO...\n`;
-
-  t.same(track.commentatorArtists, [artist3],
-    `Track.commentatorArtists #6: ignores artist names not found`);
-
-  track.commentary = commentary +=
-    `<i>Icy:</i>\n` +
-    `I'm back!\n`;
-
-  t.same(track.commentatorArtists, [artist3],
-    `Track.commentatorArtists #7: ignores duplicate artist`);
-
-  track.commentary = commentary +=
-    `<i>SNooPING, ASUsual, Icy:</i>\n` +
-    `WITH ALL THREE POWERS COMBINED...`;
-
-  t.same(track.commentatorArtists, [artist3, artist1, artist2],
-    `Track.commentatorArtists #8: works with more than one artist in one entry`);
-});
-
-t.test(`Track.coverArtistContribs`, t => {
-  t.plan(5);
-
-  const {track, album} = stubTrackAndAlbum();
-  const artist1 = stubArtist(`Artist 1`);
-  const artist2 = stubArtist(`Artist 2`);
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist1, artist2],
-    trackData: [track],
-  });
-
-  t.same(track.coverArtistContribs, [],
-    `coverArtistContribs #1: defaults to empty array`);
-
-  album.trackCoverArtistContribs = [
-    {artist: `Artist 1`, annotation: `lines`},
-    {artist: `Artist 2`, annotation: null},
-  ];
-
-  XXX_decacheWikiData();
-
-  t.same(track.coverArtistContribs,
-    [{artist: artist1, annotation: `lines`}, {artist: artist2, annotation: null}],
-    `coverArtistContribs #2: inherits album trackCoverArtistContribs`);
-
-  track.coverArtistContribs = [
-    {artist: `Artist 1`, annotation: `collage`},
-  ];
-
-  t.same(track.coverArtistContribs, [{artist: artist1, annotation: `collage`}],
-    `coverArtistContribs #3: resolves from own value`);
-
-  track.coverArtistContribs = [
-    {artist: `Artist 1`, annotation: `snooping`},
-    {artist: `Artist 413`, annotation: `as`},
-    {artist: `Artist 2`, annotation: `usual`},
-  ];
-
-  t.same(track.coverArtistContribs,
-    [{artist: artist1, annotation: `snooping`}, {artist: artist2, annotation: `usual`}],
-    `coverArtistContribs #4: filters out names without matches`);
-
-  track.disableUniqueCoverArt = true;
-
-  t.same(track.coverArtistContribs, [],
-    `coverArtistContribs #5: is empty if track disables unique cover artwork`);
-});
-
-t.test(`Track.coverArtDate`, t => {
-  t.plan(8);
-
-  const {track, album} = stubTrackAndAlbum();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-    trackData: [track],
-  });
-
-  track.coverArtistContribs = contribs;
-
-  t.equal(track.coverArtDate, null,
-    `coverArtDate #1: defaults to null`);
-
-  album.trackArtDate = new Date('2012-12-12');
-
-  XXX_decacheWikiData();
-
-  t.same(track.coverArtDate, new Date('2012-12-12'),
-    `coverArtDate #2: inherits album trackArtDate`);
-
-  track.coverArtDate = new Date('2009-09-09');
-
-  t.same(track.coverArtDate, new Date('2009-09-09'),
-    `coverArtDate #3: is own value`);
-
-  track.coverArtistContribs = [];
-
-  t.equal(track.coverArtDate, null,
-    `coverArtDate #4: is null if track coverArtistContribs empty`);
-
-  album.trackCoverArtistContribs = contribs;
-
-  XXX_decacheWikiData();
-
-  t.same(track.coverArtDate, new Date('2009-09-09'),
-    `coverArtDate #5: is not null if album trackCoverArtistContribs specified`);
-
-  album.trackCoverArtistContribs = badContribs;
-
-  XXX_decacheWikiData();
-
-  t.equal(track.coverArtDate, null,
-    `coverArtDate #6: is null if album trackCoverArtistContribs resolves empty`);
-
-  track.coverArtistContribs = badContribs;
-
-  t.equal(track.coverArtDate, null,
-    `coverArtDate #7: is null if track coverArtistContribs resolves empty`);
-
-  track.coverArtistContribs = contribs;
-  track.disableUniqueCoverArt = true;
-
-  t.equal(track.coverArtDate, null,
-    `coverArtDate #8: is null if track disables unique cover artwork`);
-});
-
-t.test(`Track.coverArtFileExtension`, t => {
-  t.plan(8);
-
-  const {track, album} = stubTrackAndAlbum();
-  const {artist, contribs} = stubArtistAndContribs();
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-    trackData: [track],
-  });
-
-  t.equal(track.coverArtFileExtension, null,
-    `coverArtFileExtension #1: defaults to null`);
-
-  track.coverArtistContribs = contribs;
-
-  t.equal(track.coverArtFileExtension, 'jpg',
-    `coverArtFileExtension #2: is jpg if has cover art and not further specified`);
-
-  track.coverArtistContribs = [];
-
-  album.coverArtistContribs = contribs;
-  XXX_decacheWikiData();
-
-  t.equal(track.coverArtFileExtension, null,
-    `coverArtFileExtension #3: only has value for unique cover art`);
-
-  track.coverArtistContribs = contribs;
-
-  album.trackCoverArtFileExtension = 'png';
-  XXX_decacheWikiData();
-
-  t.equal(track.coverArtFileExtension, 'png',
-    `coverArtFileExtension #4: inherits album trackCoverArtFileExtension (1/2)`);
-
-  track.coverArtFileExtension = 'gif';
-
-  t.equal(track.coverArtFileExtension, 'gif',
-    `coverArtFileExtension #5: is own value (1/2)`);
-
-  track.coverArtistContribs = [];
-
-  album.trackCoverArtistContribs = contribs;
-  XXX_decacheWikiData();
-
-  t.equal(track.coverArtFileExtension, 'gif',
-    `coverArtFileExtension #6: is own value (2/2)`);
-
-  track.coverArtFileExtension = null;
-
-  t.equal(track.coverArtFileExtension, 'png',
-    `coverArtFileExtension #7: inherits album trackCoverArtFileExtension (2/2)`);
-
-  track.disableUniqueCoverArt = true;
-
-  t.equal(track.coverArtFileExtension, null,
-    `coverArtFileExtension #8: is null if track disables unique cover art`);
-});
-
-t.test(`Track.date`, t => {
-  t.plan(3);
-
-  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_decacheWikiData();
-
-  t.same(track.date, album.date,
-    `date #2: inherits from album`);
-
-  track.dateFirstReleased = new Date('2009-09-09');
-
-  t.same(track.date, new Date('2009-09-09'),
-    `date #3: is own dateFirstReleased`);
-});
-
-t.test(`Track.featuredInFlashes`, t => {
-  t.plan(2);
-
-  const {track, album} = stubTrackAndAlbum('track1');
-
-  const {flash: flash1, flashAct: flashAct1} = stubFlashAndAct('flash1');
-  const {flash: flash2, flashAct: flashAct2} = stubFlashAndAct('flash2');
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    trackData: [track],
-    flashData: [flash1, flash2],
-    flashActData: [flashAct1, flashAct2],
-  });
-
-  t.same(track.featuredInFlashes, [],
-    `featuredInFlashes #1: defaults to empty array`);
-
-  flash1.featuredTracks = ['track:track1'];
-  flash2.featuredTracks = ['track:track1'];
-  XXX_decacheWikiData();
-
-  t.same(track.featuredInFlashes, [flash1, flash2],
-    `featuredInFlashes #2: matches flashes' featuredTracks`);
-});
-
-t.test(`Track.hasUniqueCoverArt`, t => {
-  t.plan(7);
-
-  const {track, album} = stubTrackAndAlbum();
-  const {artist, contribs, badContribs} = stubArtistAndContribs();
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album],
-    artistData: [artist],
-    trackData: [track],
-  });
-
-  t.equal(track.hasUniqueCoverArt, false,
-    `hasUniqueCoverArt #1: defaults to false`);
-
-  album.trackCoverArtistContribs = contribs;
-  XXX_decacheWikiData();
-
-  t.equal(track.hasUniqueCoverArt, true,
-    `hasUniqueCoverArt #2: is true if album specifies trackCoverArtistContribs`);
-
-  track.disableUniqueCoverArt = true;
-
-  t.equal(track.hasUniqueCoverArt, false,
-    `hasUniqueCoverArt #3: is false if disableUniqueCoverArt is true (1/2)`);
-
-  track.disableUniqueCoverArt = false;
-
-  album.trackCoverArtistContribs = badContribs;
-  XXX_decacheWikiData();
-
-  t.equal(track.hasUniqueCoverArt, false,
-    `hasUniqueCoverArt #4: is false if album's trackCoverArtistContribs resolve empty`);
-
-  track.coverArtistContribs = contribs;
-
-  t.equal(track.hasUniqueCoverArt, true,
-    `hasUniqueCoverArt #5: is true if track specifies coverArtistContribs`);
-
-  track.disableUniqueCoverArt = true;
-
-  t.equal(track.hasUniqueCoverArt, false,
-    `hasUniqueCoverArt #6: is false if disableUniqueCoverArt is true (2/2)`);
-
-  track.disableUniqueCoverArt = false;
-
-  track.coverArtistContribs = badContribs;
-
-  t.equal(track.hasUniqueCoverArt, false,
-    `hasUniqueCoverArt #7: is false if track's coverArtistContribs resolve empty`);
-});
-
-t.test(`Track.originalReleaseTrack`, t => {
-  t.plan(3);
-
-  const {track: track1, album: album1} = stubTrackAndAlbum('track1');
-  const {track: track2, album: album2} = stubTrackAndAlbum('track2');
-
-  const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album1, album2],
-    trackData: [track1, track2],
-  });
-
-  t.equal(track2.originalReleaseTrack, null,
-    `originalReleaseTrack #1: defaults to null`);
-
-  track2.originalReleaseTrack = 'track:track1';
-
-  t.equal(track2.originalReleaseTrack, track1,
-    `originalReleaseTrack #2: is resolved from own value`);
-
-  track2.trackData = [];
-
-  t.equal(track2.originalReleaseTrack, null,
-    `originalReleaseTrack #3: is null when track missing trackData`);
-});
-
-t.test(`Track.otherReleases`, t => {
-  t.plan(6);
-
-  const {track: track1, album: album1} = stubTrackAndAlbum('track1');
-  const {track: track2, album: album2} = stubTrackAndAlbum('track2');
-  const {track: track3, album: album3} = stubTrackAndAlbum('track3');
-  const {track: track4, album: album4} = stubTrackAndAlbum('track4');
-
-  const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album1, album2, album3, album4],
-    trackData: [track1, track2, track3, track4],
-  });
-
-  t.same(track1.otherReleases, [],
-    `otherReleases #1: defaults to empty array`);
-
-  track2.originalReleaseTrack = 'track:track1';
-  track3.originalReleaseTrack = 'track:track1';
-  track4.originalReleaseTrack = 'track:track1';
-  XXX_decacheWikiData();
-
-  t.same(track1.otherReleases, [track2, track3, track4],
-    `otherReleases #2: otherReleases of original release are its rereleases`);
-
-  wikiData.trackData = [track1, track3, track2, track4];
-  linkWikiDataArrays();
-
-  t.same(track1.otherReleases, [track3, track2, track4],
-    `otherReleases #3: otherReleases matches trackData order`);
-
-  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)`);
-
-  t.same(track3.otherReleases, [track1, track2, track4],
-    `otherReleases #5: otherReleases of rerelease are original track then other rereleases (2/3)`);
-
-  t.same(track4.otherReleases, [track1, track3, track2],
-    `otherReleases #6: otherReleases of rerelease are original track then other rereleases (3/3)`);
-});
-
-t.test(`Track.referencedByTracks`, t => {
-  t.plan(4);
-
-  const {track: track1, album: album1} = stubTrackAndAlbum('track1');
-  const {track: track2, album: album2} = stubTrackAndAlbum('track2');
-  const {track: track3, album: album3} = stubTrackAndAlbum('track3');
-  const {track: track4, album: album4} = stubTrackAndAlbum('track4');
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album1, album2, album3, album4],
-    trackData: [track1, track2, track3, track4],
-  });
-
-  t.same(track1.referencedByTracks, [],
-    `referencedByTracks #1: defaults to empty array`);
-
-  track2.referencedTracks = ['track:track1'];
-  track3.referencedTracks = ['track:track1'];
-  XXX_decacheWikiData();
-
-  t.same(track1.referencedByTracks, [track2, track3],
-    `referencedByTracks #2: matches tracks' referencedTracks`);
-
-  track4.sampledTracks = ['track:track1'];
-  XXX_decacheWikiData();
-
-  t.same(track1.referencedByTracks, [track2, track3],
-    `referencedByTracks #3: doesn't match tracks' sampledTracks`);
-
-  track3.originalReleaseTrack = 'track:track2';
-  XXX_decacheWikiData();
-
-  t.same(track1.referencedByTracks, [track2],
-    `referencedByTracks #4: doesn't include rereleases`);
-});
-
-t.test(`Track.sampledByTracks`, t => {
-  t.plan(4);
-
-  const {track: track1, album: album1} = stubTrackAndAlbum('track1');
-  const {track: track2, album: album2} = stubTrackAndAlbum('track2');
-  const {track: track3, album: album3} = stubTrackAndAlbum('track3');
-  const {track: track4, album: album4} = stubTrackAndAlbum('track4');
-
-  const {XXX_decacheWikiData} = linkAndBindWikiData({
-    albumData: [album1, album2, album3, album4],
-    trackData: [track1, track2, track3, track4],
-  });
-
-  t.same(track1.sampledByTracks, [],
-    `sampledByTracks #1: defaults to empty array`);
-
-  track2.sampledTracks = ['track:track1'];
-  track3.sampledTracks = ['track:track1'];
-  XXX_decacheWikiData();
-
-  t.same(track1.sampledByTracks, [track2, track3],
-    `sampledByTracks #2: matches tracks' sampledTracks`);
-
-  track4.referencedTracks = ['track:track1'];
-  XXX_decacheWikiData();
-
-  t.same(track1.sampledByTracks, [track2, track3],
-    `sampledByTracks #3: doesn't match tracks' referencedTracks`);
-
-  track3.originalReleaseTrack = 'track:track2';
-  XXX_decacheWikiData();
-
-  t.same(track1.sampledByTracks, [track2],
-    `sampledByTracks #4: doesn't include rereleases`);
-});
diff --git a/test/unit/data/things/validators.js b/test/unit/data/validators.js
index 3a217d6f..02f94866 100644
--- a/test/unit/data/things/validators.js
+++ b/test/unit/data/validators.js
@@ -339,7 +339,7 @@ t.test('isName', t => {
   t.plan(4);
   t.ok(isName('Dogz 2.0'));
   t.ok(isName('album:this-track-is-only-named-thusly-to-give-niklink-a-headache'));
-  t.throws(() => isName(''));
+  t.ok(() => isName(''));
   t.throws(() => isName(612));
 });
 
@@ -374,8 +374,8 @@ test(t, 'validateReference', t => {
 
   t.ok(typeless('Hopes and Dreams'));
   t.ok(typeless('track:snowdin-town'));
+  t.ok(typeless('album:undertale-soundtrack'));
   t.throws(() => typeless(''), TypeError);
-  t.throws(() => typeless('album:undertale-soundtrack'));
 });
 
 test(t, 'validateReferenceList', t => {