« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/test/snapshot
diff options
context:
space:
mode:
Diffstat (limited to 'test/snapshot')
-rw-r--r--test/snapshot/generateAdditionalFilesShortcut.js36
-rw-r--r--test/snapshot/generateAlbumAdditionalFilesList.js84
-rw-r--r--test/snapshot/generateAlbumBanner.js34
-rw-r--r--test/snapshot/generateAlbumCoverArtwork.js36
-rw-r--r--test/snapshot/generateAlbumReleaseInfo.js74
-rw-r--r--test/snapshot/generateAlbumSecondaryNav.js55
-rw-r--r--test/snapshot/generateAlbumSidebarGroupBox.js57
-rw-r--r--test/snapshot/generateAlbumTrackList.js104
-rw-r--r--test/snapshot/generateBanner.js22
-rw-r--r--test/snapshot/generateCoverArtwork.js31
-rw-r--r--test/snapshot/generatePreviousNextLinks.js35
-rw-r--r--test/snapshot/generateTrackAdditionalNamesBox.js107
-rw-r--r--test/snapshot/generateTrackCoverArtwork.js61
-rw-r--r--test/snapshot/generateTrackReleaseInfo.js51
-rw-r--r--test/snapshot/image.js125
-rw-r--r--test/snapshot/linkArtist.js30
-rw-r--r--test/snapshot/linkContribution.js104
-rw-r--r--test/snapshot/linkExternal.js225
-rw-r--r--test/snapshot/linkTemplate.js63
-rw-r--r--test/snapshot/linkThing.js94
-rw-r--r--test/snapshot/transformContent.js161
21 files changed, 1589 insertions, 0 deletions
diff --git a/test/snapshot/generateAdditionalFilesShortcut.js b/test/snapshot/generateAdditionalFilesShortcut.js
new file mode 100644
index 0000000..9825efa
--- /dev/null
+++ b/test/snapshot/generateAdditionalFilesShortcut.js
@@ -0,0 +1,36 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateAdditionalFilesShortcut (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  evaluate.snapshot('no additional files', {
+    name: 'generateAdditionalFilesShortcut',
+    args: [[]],
+  });
+
+  evaluate.snapshot('basic behavior', {
+    name: 'generateAdditionalFilesShortcut',
+    args: [
+      [
+        {
+          title: 'SBURB Wallpaper',
+          files: [
+            'sburbwp_1280x1024.jpg',
+            'sburbwp_1440x900.jpg',
+            'sburbwp_1920x1080.jpg',
+          ],
+        },
+        {
+          title: 'Alternate Covers',
+          description: 'This is just an example description.',
+          files: [
+            'Homestuck_Vol4_alt1.jpg',
+            'Homestuck_Vol4_alt2.jpg',
+            'Homestuck_Vol4_alt3.jpg',
+          ],
+        },
+      ],
+    ],
+  });
+});
diff --git a/test/snapshot/generateAlbumAdditionalFilesList.js b/test/snapshot/generateAlbumAdditionalFilesList.js
new file mode 100644
index 0000000..c25e568
--- /dev/null
+++ b/test/snapshot/generateAlbumAdditionalFilesList.js
@@ -0,0 +1,84 @@
+import t from 'tap';
+
+import {testContentFunctions} from '#test-lib';
+import thingConstructors from '#things';
+
+const {Album} = thingConstructors;
+
+testContentFunctions(t, 'generateAlbumAdditionalFilesList (snapshot)', async (t, evaluate) => {
+  const sizeMap = {
+    'sburbwp_1280x1024.jpg': 2500,
+    'sburbwp_1440x900.jpg': null,
+    'sburbwp_1920x1080.jpg': null,
+    'Internet Explorer.gif': 1,
+    'Homestuck_Vol4_alt1.jpg': 1234567,
+    'Homestuck_Vol4_alt2.jpg': 1234567,
+    'Homestuck_Vol4_alt3.jpg': 1234567,
+  };
+
+  const extraDependencies = {
+    getSizeOfAdditionalFile: file =>
+      Object.entries(sizeMap)
+        .find(key => file.includes(key))
+        ?.at(1) ?? null,
+  };
+
+  await evaluate.load({
+    mock: {
+      image: evaluate.stubContentFunction('image'),
+    },
+  });
+
+  const album = new Album();
+  album.directory = 'exciting-album';
+
+  evaluate.snapshot('no additional files', {
+    extraDependencies,
+    name: 'generateAlbumAdditionalFilesList',
+    args: [album, []],
+  });
+
+  try {
+    evaluate.snapshot('basic behavior', {
+      extraDependencies,
+      name: 'generateAlbumAdditionalFilesList',
+      args: [
+        album,
+        [
+          {
+            title: 'SBURB Wallpaper',
+            files: [
+              'sburbwp_1280x1024.jpg',
+              'sburbwp_1440x900.jpg',
+              'sburbwp_1920x1080.jpg',
+            ],
+          },
+          {
+            title: 'Fake Section',
+            description: 'No sizes for these files',
+            files: [
+              'oops.mp3',
+              'Internet Explorer.gif',
+              'daisy.mp3',
+            ],
+          },
+          {
+            title: `Empty Section`,
+            description: `These files haven't been made available.`,
+          },
+          {
+            title: 'Alternate Covers',
+            description: 'This is just an example description.',
+            files: [
+              'Homestuck_Vol4_alt1.jpg',
+              'Homestuck_Vol4_alt2.jpg',
+              'Homestuck_Vol4_alt3.jpg',
+            ],
+          },
+        ],
+      ],
+    });
+  } catch (error) {
+    console.log(error);
+  }
+});
diff --git a/test/snapshot/generateAlbumBanner.js b/test/snapshot/generateAlbumBanner.js
new file mode 100644
index 0000000..8e63308
--- /dev/null
+++ b/test/snapshot/generateAlbumBanner.js
@@ -0,0 +1,34 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateAlbumBanner (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  evaluate.snapshot('basic behavior', {
+    name: 'generateAlbumBanner',
+    args: [{
+      directory: 'cool-album',
+      hasBannerArt: true,
+      bannerDimensions: [800, 200],
+      bannerFileExtension: 'png',
+    }],
+  });
+
+  evaluate.snapshot('no dimensions', {
+    name: 'generateAlbumBanner',
+    args: [{
+      directory: 'cool-album',
+      hasBannerArt: true,
+      bannerDimensions: null,
+      bannerFileExtension: 'png',
+    }],
+  });
+
+  evaluate.snapshot('no banner', {
+    name: 'generateAlbumBanner',
+    args: [{
+      directory: 'cool-album',
+      hasBannerArt: false,
+    }],
+  });
+});
diff --git a/test/snapshot/generateAlbumCoverArtwork.js b/test/snapshot/generateAlbumCoverArtwork.js
new file mode 100644
index 0000000..9244c03
--- /dev/null
+++ b/test/snapshot/generateAlbumCoverArtwork.js
@@ -0,0 +1,36 @@
+import t from 'tap';
+
+import contentFunction from '#content-function';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateAlbumCoverArtwork (snapshot)', async (t, evaluate) => {
+  await evaluate.load({
+    mock: {
+      image: evaluate.stubContentFunction('image'),
+    },
+  });
+
+  const album = {
+    directory: 'bee-forus-seatbelt-safebee',
+    coverArtFileExtension: 'png',
+    color: '#f28514',
+    artTags: [
+      {name: 'Damara', directory: 'damara', isContentWarning: false},
+      {name: 'Cronus', directory: 'cronus', isContentWarning: false},
+      {name: 'Bees', directory: 'bees', isContentWarning: false},
+      {name: 'creepy crawlies', isContentWarning: true},
+    ],
+  };
+
+  evaluate.snapshot('display: primary', {
+    name: 'generateAlbumCoverArtwork',
+    args: [album],
+    slots: {mode: 'primary'},
+  });
+
+  evaluate.snapshot('display: thumbnail', {
+    name: 'generateAlbumCoverArtwork',
+    args: [album],
+    slots: {mode: 'thumbnail'},
+  });
+});
diff --git a/test/snapshot/generateAlbumReleaseInfo.js b/test/snapshot/generateAlbumReleaseInfo.js
new file mode 100644
index 0000000..3dea119
--- /dev/null
+++ b/test/snapshot/generateAlbumReleaseInfo.js
@@ -0,0 +1,74 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateAlbumReleaseInfo (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  evaluate.snapshot('basic behavior', {
+    name: 'generateAlbumReleaseInfo',
+    args: [{
+      artistContribs: [
+        {who: {name: 'Toby Fox', directory: 'toby-fox', urls: null}, what: 'music probably'},
+        {who: {name: 'Tensei', directory: 'tensei', urls: ['https://tenseimusic.bandcamp.com/']}, what: 'hot jams'},
+      ],
+
+      coverArtistContribs: [
+        {who: {name: 'Hanni Brosh', directory: 'hb', urls: null}, what: null},
+      ],
+
+      wallpaperArtistContribs: [
+        {who: {name: 'Hanni Brosh', directory: 'hb', urls: null}, what: null},
+        {who: {name: 'Niklink', directory: 'niklink', urls: null}, what: 'edits'},
+      ],
+
+      bannerArtistContribs: [
+        {who: {name: 'Hanni Brosh', directory: 'hb', urls: null}, what: null},
+        {who: {name: 'Niklink', directory: 'niklink', urls: null}, what: 'edits'},
+      ],
+
+      name: 'AlterniaBound',
+      date: new Date('March 14, 2011'),
+      coverArtDate: new Date('April 1, 1991'),
+      urls: [
+        'https://homestuck.bandcamp.com/album/alterniabound-with-alternia',
+        'https://www.youtube.com/playlist?list=PLnVpmehyaOFZWO9QOZmD6A3TIK0wZ6xE2',
+        'https://www.youtube.com/watch?v=HO5V2uogkYc',
+      ],
+
+      tracks: [{duration: 253}, {duration: 372}],
+    }],
+  });
+
+  const sparse = {
+    artistContribs: [],
+    coverArtistContribs: [],
+    wallpaperArtistContribs: [],
+    bannerArtistContribs: [],
+
+    name: 'Suspicious Album',
+    urls: [],
+    tracks: [],
+  };
+
+  evaluate.snapshot('reduced details', {
+    name: 'generateAlbumReleaseInfo',
+    args: [sparse],
+  });
+
+  evaluate.snapshot('URLs only', {
+    name: 'generateAlbumReleaseInfo',
+    args: [{
+      ...sparse,
+      urls: ['https://homestuck.bandcamp.com/foo', 'https://soundcloud.com/bar'],
+    }],
+  });
+
+  evaluate.snapshot('equal cover art date', {
+    name: 'generateAlbumReleaseInfo',
+    args: [{
+      ...sparse,
+      date: new Date('2020-04-13'),
+      coverArtDate: new Date('2020-04-13'),
+    }],
+  });
+});
diff --git a/test/snapshot/generateAlbumSecondaryNav.js b/test/snapshot/generateAlbumSecondaryNav.js
new file mode 100644
index 0000000..709b062
--- /dev/null
+++ b/test/snapshot/generateAlbumSecondaryNav.js
@@ -0,0 +1,55 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateAlbumSecondaryNav (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  let album, group1, group2;
+
+  group1 = {name: 'VCG', directory: 'vcg', color: '#abcdef'};
+  group2 = {name: 'Bepis', directory: 'bepis', color: '#123456'};
+
+  album = {
+    date: new Date('2010-04-13'),
+    groups: [group1, group2],
+  };
+
+  group1.albums = [
+    {name: 'First', directory: 'first', date: new Date('2010-04-10')},
+    album,
+    {name: 'Last', directory: 'last', date: new Date('2010-06-12')},
+  ];
+
+  group2.albums = [
+    album,
+    {name: 'Second', directory: 'second', date: new Date('2011-04-13')},
+  ];
+
+  evaluate.snapshot('basic behavior, mode: album', {
+    name: 'generateAlbumSecondaryNav',
+    args: [album],
+    slots: {mode: 'album'},
+  });
+
+  evaluate.snapshot('basic behavior, mode: track', {
+    name: 'generateAlbumSecondaryNav',
+    args: [album],
+    slots: {mode: 'track'},
+  });
+
+  album = {
+    date: null,
+    groups: [group1, group2],
+  };
+
+  group1.albums = [
+    ...group1.albums,
+    album,
+  ];
+
+  evaluate.snapshot('dateless album in mixed group', {
+    name: 'generateAlbumSecondaryNav',
+    args: [album],
+    slots: {mode: 'album'},
+  });
+});
diff --git a/test/snapshot/generateAlbumSidebarGroupBox.js b/test/snapshot/generateAlbumSidebarGroupBox.js
new file mode 100644
index 0000000..f920bd9
--- /dev/null
+++ b/test/snapshot/generateAlbumSidebarGroupBox.js
@@ -0,0 +1,57 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateAlbumSidebarGroupBox (snapshot)', async (t, evaluate) => {
+  await evaluate.load({
+    mock: {
+      ...evaluate.mock.transformContent,
+    },
+  });
+
+  let album, group;
+
+  album = {
+    name: 'Middle',
+    directory: 'middle',
+    date: new Date('2010-04-13'),
+  };
+
+  group = {
+    name: 'VCG',
+    directory: 'vcg',
+    descriptionShort: 'Very cool group.',
+    urls: ['https://vcg.bandcamp.com/', 'https://youtube.com/@vcg'],
+    albums: [
+      {name: 'First', directory: 'first', date: new Date('2010-04-10')},
+      album,
+      {name: 'Last', directory: 'last', date: new Date('2010-06-12')},
+    ],
+  };
+
+  evaluate.snapshot('basic behavior, mode: album', {
+    name: 'generateAlbumSidebarGroupBox',
+    args: [album, group],
+    slots: {mode: 'album'},
+  });
+
+  evaluate.snapshot('basic behavior, mode: track', {
+    name: 'generateAlbumSidebarGroupBox',
+    args: [album, group],
+    slots: {mode: 'track'},
+  });
+
+  album = {
+    date: null,
+  };
+
+  group.albums = [
+    ...group.albums,
+    album,
+  ];
+
+  evaluate.snapshot('dateless album in mixed group', {
+    name: 'generateAlbumSidebarGroupBox',
+    args: [album, group],
+    slots: {mode: 'album'},
+  });
+});
diff --git a/test/snapshot/generateAlbumTrackList.js b/test/snapshot/generateAlbumTrackList.js
new file mode 100644
index 0000000..181cc1d
--- /dev/null
+++ b/test/snapshot/generateAlbumTrackList.js
@@ -0,0 +1,104 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateAlbumTrackList (snapshot)', async (t, evaluate) => {
+  await evaluate.load({
+    mock: {
+      generateAlbumTrackListMissingDuration:
+        evaluate.stubContentFunction('generateAlbumTrackListMissingDuration'),
+    },
+  });
+
+  const contribs1 = [
+    {who: {name: 'Apricot', directory: 'apricot', urls: null}},
+  ];
+
+  const contribs2 = [
+    {who: {name: 'Apricot', directory: 'apricot', urls: null}},
+    {who: {name: 'Peach', directory: 'peach', urls: ['https://peach.bandcamp.com/']}},
+  ];
+
+  const color1 = '#fb07ff';
+  const color2 = '#ea2e83';
+
+  const tracks = [
+    {name: 'Track 1', directory: 't1', duration: 20, artistContribs: contribs1, color: color1},
+    {name: 'Track 2', directory: 't2', duration: 0, artistContribs: contribs1, color: color1},
+    {name: 'Track 3', directory: 't3', duration: 40, artistContribs: contribs1, color: color1},
+    {name: 'Track 4', directory: 't4', duration: 0, artistContribs: contribs2, color: color2},
+  ];
+
+  const albumWithTrackSections = {
+    color: color1,
+    artistContribs: contribs1,
+    trackSections: [
+      {name: 'First section', tracks: tracks.slice(0, 3)},
+      {name: 'Second section', tracks: tracks.slice(3)},
+    ],
+    tracks,
+  };
+
+  const albumWithoutTrackSections = {
+    color: color1,
+    artistContribs: contribs1,
+    trackSections: [{isDefaultTrackSection: true, tracks}],
+    tracks,
+  };
+
+  const albumWithNoDuration = {
+    color: color1,
+    artistContribs: contribs1,
+    trackSections: [{isDefaultTrackSection: true, tracks: [tracks[1], tracks[3]]}],
+    tracks: [tracks[1], tracks[3]],
+  };
+
+  evaluate.snapshot(`basic behavior, with track sections`, {
+    name: 'generateAlbumTrackList',
+    args: [albumWithTrackSections],
+  });
+
+  evaluate.snapshot(`basic behavior, default track section`, {
+    name: 'generateAlbumTrackList',
+    args: [albumWithoutTrackSections],
+  });
+
+  evaluate.snapshot(`collapseDurationScope: never`, {
+    name: 'generateAlbumTrackList',
+    slots: {collapseDurationScope: 'never'},
+    multiple: [
+      {args: [albumWithTrackSections]},
+      {args: [albumWithoutTrackSections]},
+      {args: [albumWithNoDuration]},
+    ],
+  });
+
+  evaluate.snapshot(`collapseDurationScope: track`, {
+    name: 'generateAlbumTrackList',
+    slots: {collapseDurationScope: 'track'},
+    multiple: [
+      {args: [albumWithTrackSections]},
+      {args: [albumWithoutTrackSections]},
+      {args: [albumWithNoDuration]},
+    ],
+  });
+
+  evaluate.snapshot(`collapseDurationScope: section`, {
+    name: 'generateAlbumTrackList',
+    slots: {collapseDurationScope: 'section'},
+    multiple: [
+      {args: [albumWithTrackSections]},
+      {args: [albumWithoutTrackSections]},
+      {args: [albumWithNoDuration]},
+    ],
+  });
+
+  evaluate.snapshot(`collapseDurationScope: album`, {
+    name: 'generateAlbumTrackList',
+    slots: {collapseDurationScope: 'album'},
+    multiple: [
+      {args: [albumWithTrackSections]},
+      {args: [albumWithoutTrackSections]},
+      {args: [albumWithNoDuration]},
+    ],
+  });
+});
diff --git a/test/snapshot/generateBanner.js b/test/snapshot/generateBanner.js
new file mode 100644
index 0000000..ab57c3c
--- /dev/null
+++ b/test/snapshot/generateBanner.js
@@ -0,0 +1,22 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateBanner (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  evaluate.snapshot('basic behavior', {
+    name: 'generateBanner',
+    slots: {
+      path: ['media.albumBanner', 'cool-album', 'png'],
+      alt: 'Very cool banner art.',
+      dimensions: [800, 200],
+    },
+  });
+
+  evaluate.snapshot('no dimensions', {
+    name: 'generateBanner',
+    slots: {
+      path: ['media.albumBanner', 'cool-album', 'png'],
+    },
+  });
+});
diff --git a/test/snapshot/generateCoverArtwork.js b/test/snapshot/generateCoverArtwork.js
new file mode 100644
index 0000000..e35dd8d
--- /dev/null
+++ b/test/snapshot/generateCoverArtwork.js
@@ -0,0 +1,31 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateCoverArtwork (snapshot)', async (t, evaluate) => {
+  await evaluate.load({
+    mock: {
+      image: evaluate.stubContentFunction('image', {mock: true}),
+    },
+  });
+
+  const artTags = [
+    {name: 'Damara', directory: 'damara', isContentWarning: false},
+    {name: 'Cronus', directory: 'cronus', isContentWarning: false},
+    {name: 'Bees', directory: 'bees', isContentWarning: false},
+    {name: 'creepy crawlies', isContentWarning: true},
+  ];
+
+  const path = ['media.albumCover', 'bee-forus-seatbelt-safebee', 'png'];
+
+  evaluate.snapshot('display: primary', {
+    name: 'generateCoverArtwork',
+    args: [artTags],
+    slots: {path, mode: 'primary'},
+  });
+
+  evaluate.snapshot('display: thumbnail', {
+    name: 'generateCoverArtwork',
+    args: [artTags],
+    slots: {path, mode: 'thumbnail'},
+  });
+});
diff --git a/test/snapshot/generatePreviousNextLinks.js b/test/snapshot/generatePreviousNextLinks.js
new file mode 100644
index 0000000..0d952f5
--- /dev/null
+++ b/test/snapshot/generatePreviousNextLinks.js
@@ -0,0 +1,35 @@
+import t from 'tap';
+import * as html from '#html';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generatePreviousNextLinks (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  const quickSnapshot = (message, slots) =>
+    evaluate.snapshot(message, {
+      name: 'generatePreviousNextLinks',
+      slots,
+      postprocess: template => template.content.join('\n'),
+    });
+
+  quickSnapshot('basic behavior', {
+    previousLink: evaluate.stubTemplate('previous'),
+    nextLink: evaluate.stubTemplate('next'),
+  });
+
+  quickSnapshot('previous missing', {
+    nextLink: evaluate.stubTemplate('next'),
+  });
+
+  quickSnapshot('next missing', {
+    previousLink: evaluate.stubTemplate('previous'),
+  });
+
+  quickSnapshot('neither link present', {});
+
+  quickSnapshot('disable id', {
+    previousLink: evaluate.stubTemplate('previous'),
+    nextLink: evaluate.stubTemplate('next'),
+    id: false,
+  });
+});
diff --git a/test/snapshot/generateTrackAdditionalNamesBox.js b/test/snapshot/generateTrackAdditionalNamesBox.js
new file mode 100644
index 0000000..9c1e359
--- /dev/null
+++ b/test/snapshot/generateTrackAdditionalNamesBox.js
@@ -0,0 +1,107 @@
+import t from 'tap';
+
+import contentFunction from '#content-function';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateTrackAdditionalNamesBox (snapshot)', async (t, evaluate) => {
+  await evaluate.load({
+    mock: {
+      generateAdditionalNamesBox:
+        evaluate.stubContentFunction('generateAdditionalNamesBox'),
+    },
+  });
+
+  const stubTrack = {
+    additionalNames: [],
+    sharedAdditionalNames: [],
+    inferredAdditionalNames: [],
+  };
+
+  const quickSnapshot = (message, trackProperties) =>
+    evaluate.snapshot(message, {
+      name: 'generateTrackAdditionalNamesBox',
+      args: [{...stubTrack, ...trackProperties}],
+    });
+
+  quickSnapshot(`no additional names`, {});
+
+  quickSnapshot(`own additional names only`, {
+    additionalNames: [
+      {name: `Foo Bar`, annotation: `the Alps`},
+    ],
+  });
+
+  quickSnapshot(`shared additional names only`, {
+    sharedAdditionalNames: [
+      {name: `Bar Foo`, annotation: `the Rockies`},
+    ],
+  });
+
+  quickSnapshot(`inferred additional names only`, {
+    inferredAdditionalNames: [
+      {name: `Baz Baz`, from: [{directory: `the-pyrenees`}]},
+    ],
+  });
+
+  quickSnapshot(`multiple own`, {
+    additionalNames: [
+      {name: `Apple Time!`},
+      {name: `Pterodactyl Time!`},
+      {name: `Banana Time!`},
+    ],
+  });
+
+  quickSnapshot(`own and shared, some overlap`, {
+    additionalNames: [
+      {name: `weed dreams..`, annotation: `own annotation`},
+      {name: `夜間のMOON汗`, annotation: `own annotation`},
+    ],
+    sharedAdditionalNames: [
+      {name: `weed dreams..`, annotation: `shared annotation`},
+      {name: `GAMINGブラザー96`, annotation: `shared annotation`},
+    ],
+  });
+
+  quickSnapshot(`shared and inferred, some overlap`, {
+    sharedAdditionalNames: [
+      {name: `Coruscate`, annotation: `shared annotation`},
+      {name: `Arbroath`, annotation: `shared annotation`},
+    ],
+    inferredAdditionalNames: [
+      {name: `Arbroath`, from: [{directory: `inferred-from`}]},
+      {name: `Prana Ferox`, from: [{directory: `inferred-from`}]},
+    ],
+  });
+
+  quickSnapshot(`own and inferred, some overlap`, {
+    additionalNames: [
+      {name: `Ke$halo Strike Back`, annotation: `own annotation`},
+      {name: `Ironic Mania`, annotation: `own annotation`},
+    ],
+    inferredAdditionalNames: [
+      {name: `Ironic Mania`, from: [{directory: `inferred-from`}]},
+      {name: `ANARCHY::MEGASTRIFE`, from: [{directory: `inferred-from`}]},
+    ],
+  });
+
+  quickSnapshot(`own and shared and inferred, various overlap`, {
+    additionalNames: [
+      {name: `Own!`, annotation: `own annotation`},
+      {name: `Own! Shared!`, annotation: `own annotation`},
+      {name: `Own! Inferred!`, annotation: `own annotation`},
+      {name: `Own! Shared! Inferred!`, annotation: `own annotation`},
+    ],
+    sharedAdditionalNames: [
+      {name: `Shared!`, annotation: `shared annotation`},
+      {name: `Own! Shared!`, annotation: `shared annotation`},
+      {name: `Shared! Inferred!`, annotation: `shared annotation`},
+      {name: `Own! Shared! Inferred!`, annotation: `shared annotation`},
+    ],
+    inferredAdditionalNames: [
+      {name: `Inferred!`, from: [{directory: `inferred-from`}]},
+      {name: `Own! Inferred!`, from: [{directory: `inferred-from`}]},
+      {name: `Shared! Inferred!`, from: [{directory: `inferred-from`}]},
+      {name: `Own! Shared! Inferred!`, from: [{directory: `inferred-from`}]},
+    ],
+  });
+});
diff --git a/test/snapshot/generateTrackCoverArtwork.js b/test/snapshot/generateTrackCoverArtwork.js
new file mode 100644
index 0000000..1e651eb
--- /dev/null
+++ b/test/snapshot/generateTrackCoverArtwork.js
@@ -0,0 +1,61 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateTrackCoverArtwork (snapshot)', async (t, evaluate) => {
+  await evaluate.load({
+    mock: {
+      image: evaluate.stubContentFunction('image'),
+    },
+  });
+
+  const album = {
+    directory: 'bee-forus-seatbelt-safebee',
+    coverArtFileExtension: 'png',
+    artTags: [
+      {name: 'Damara', directory: 'damara', isContentWarning: false},
+      {name: 'Cronus', directory: 'cronus', isContentWarning: false},
+      {name: 'Bees', directory: 'bees', isContentWarning: false},
+      {name: 'creepy crawlies', isContentWarning: true},
+    ],
+  };
+
+  const track1 = {
+    directory: 'beesmp3',
+    hasUniqueCoverArt: true,
+    coverArtFileExtension: 'jpg',
+    color: '#f28514',
+    artTags: [{name: 'Bees', directory: 'bees', isContentWarning: false}],
+    album,
+  };
+
+  const track2 = {
+    directory: 'fake-bonus-track',
+    hasUniqueCoverArt: false,
+    color: '#abcdef',
+    album,
+  };
+
+  evaluate.snapshot('display: primary - unique art', {
+    name: 'generateTrackCoverArtwork',
+    args: [track1],
+    slots: {mode: 'primary'},
+  });
+
+  evaluate.snapshot('display: thumbnail - unique art', {
+    name: 'generateTrackCoverArtwork',
+    args: [track1],
+    slots: {mode: 'thumbnail'},
+  });
+
+  evaluate.snapshot('display: primary - no unique art', {
+    name: 'generateTrackCoverArtwork',
+    args: [track2],
+    slots: {mode: 'primary'},
+  });
+
+  evaluate.snapshot('display: thumbnail - no unique art', {
+    name: 'generateTrackCoverArtwork',
+    args: [track2],
+    slots: {mode: 'thumbnail'},
+  });
+});
diff --git a/test/snapshot/generateTrackReleaseInfo.js b/test/snapshot/generateTrackReleaseInfo.js
new file mode 100644
index 0000000..c72344b
--- /dev/null
+++ b/test/snapshot/generateTrackReleaseInfo.js
@@ -0,0 +1,51 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'generateTrackReleaseInfo (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  const artistContribs = [{who: {name: 'Toby Fox', directory: 'toby-fox', urls: null}, what: null}];
+  const coverArtistContribs = [{who: {name: 'Alpaca', directory: 'alpaca', urls: null}, what: '🔥'}];
+
+  evaluate.snapshot('basic behavior', {
+    name: 'generateTrackReleaseInfo',
+    args: [{
+      artistContribs,
+      name: 'An Apple Disaster!!',
+      date: new Date('2011-11-30'),
+      duration: 58,
+      urls: ['https://soundcloud.com/foo', 'https://youtube.com/watch?v=bar'],
+    }],
+  });
+
+  const sparse = {
+    artistContribs,
+    name: 'Suspicious Track',
+    date: null,
+    duration: null,
+    urls: [],
+  };
+
+  evaluate.snapshot('reduced details', {
+    name: 'generateTrackReleaseInfo',
+    args: [sparse],
+  });
+
+  evaluate.snapshot('cover artist contribs, non-unique', {
+    name: 'generateTrackReleaseInfo',
+    args: [{
+      ...sparse,
+      coverArtistContribs,
+      hasUniqueCoverArt: false,
+    }],
+  });
+
+  evaluate.snapshot('cover artist contribs, unique', {
+    name: 'generateTrackReleaseInfo',
+    args: [{
+      ...sparse,
+      coverArtistContribs,
+      hasUniqueCoverArt: true,
+    }],
+  });
+});
diff --git a/test/snapshot/image.js b/test/snapshot/image.js
new file mode 100644
index 0000000..447e7fa
--- /dev/null
+++ b/test/snapshot/image.js
@@ -0,0 +1,125 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'image (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  const quickSnapshot = (message, {extraDependencies, ...opts}) =>
+    evaluate.snapshot(message, {
+      name: 'image',
+      extraDependencies: {
+        checkIfImagePathHasCachedThumbnails: path => !path.endsWith('.gif'),
+        getSizeOfImagePath: () => 0,
+        getDimensionsOfImagePath: () => [600, 600],
+        getThumbnailEqualOrSmaller: () => 'medium',
+        getThumbnailsAvailableForDimensions: () =>
+          [['large', 800], ['medium', 400], ['small', 250]],
+        missingImagePaths: ['album-art/missing/cover.png'],
+        ...extraDependencies,
+      },
+      ...opts,
+    });
+
+  quickSnapshot('source via path', {
+    slots: {
+      path: ['media.albumCover', 'beyond-canon', 'png'],
+    },
+  });
+
+  quickSnapshot('source via src', {
+    slots: {
+      src: 'https://example.com/bananas.gif',
+    },
+  });
+
+  quickSnapshot('source missing', {
+    slots: {
+      missingSourceContent: 'Example of missing source message.',
+    },
+  });
+
+  quickSnapshot('width & height', {
+    slots: {
+      src: 'foobar',
+      width: 600,
+      height: 400,
+    },
+  });
+
+  quickSnapshot('square', {
+    slots: {
+      src: 'foobar',
+      square: true,
+    },
+  });
+
+  quickSnapshot('lazy with square', {
+    slots: {
+      src: 'foobar',
+      lazy: true,
+      square: true,
+    },
+  });
+
+  quickSnapshot('link with file size', {
+    extraDependencies: {
+      getSizeOfImagePath: () => 10 ** 6,
+    },
+    slots: {
+      path: ['media.albumCover', 'pingas', 'png'],
+      link: true,
+    },
+  });
+
+  quickSnapshot('content warnings via tags', {
+    args: [
+      [
+        {name: 'Dirk Strider', directory: 'dirk'},
+        {name: 'too cool for school', isContentWarning: true},
+      ],
+    ],
+    slots: {
+      path: ['media.albumCover', 'beyond-canon', 'png'],
+    },
+  });
+
+  evaluate.snapshot('thumbnail details', {
+    name: 'image',
+    extraDependencies: {
+      checkIfImagePathHasCachedThumbnails: () => true,
+      getSizeOfImagePath: () => 0,
+      getDimensionsOfImagePath: () => [900, 1200],
+      getThumbnailsAvailableForDimensions: () =>
+        [['voluminous', 1200], ['middling', 900], ['petite', 20]],
+      getThumbnailEqualOrSmaller: () => 'voluminous',
+      missingImagePaths: [],
+    },
+    slots: {
+      thumb: 'gargantuan',
+      path: ['media.albumCover', 'beyond-canon', 'png'],
+    },
+  });
+
+  quickSnapshot('thumb requested but source is gif', {
+    slots: {
+      thumb: 'medium',
+      path: ['media.flashArt', '5426', 'gif'],
+    },
+  });
+
+  quickSnapshot('missing image path', {
+    slots: {
+      thumb: 'medium',
+      path: ['media.albumCover', 'missing', 'png'],
+      link: true,
+    },
+  });
+
+  quickSnapshot('missing image path w/ missingSourceContent', {
+    slots: {
+      thumb: 'medium',
+      path: ['media.albumCover', 'missing', 'png'],
+      missingSourceContent: `Cover's missing, whoops`,
+    },
+  });
+});
diff --git a/test/snapshot/linkArtist.js b/test/snapshot/linkArtist.js
new file mode 100644
index 0000000..7b2114b
--- /dev/null
+++ b/test/snapshot/linkArtist.js
@@ -0,0 +1,30 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'linkArtist (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  evaluate.snapshot('basic behavior', {
+    name: 'linkArtist',
+    args: [
+      {
+        name: `Toby Fox`,
+        directory: `toby-fox`,
+      }
+    ],
+  });
+
+  evaluate.snapshot('prefer short name', {
+    name: 'linkArtist',
+    args: [
+      {
+        name: 'ICCTTCMDMIROTMCWMWFTPFTDDOTARHPOESWGBTWEATFCWSEBTSSFOFG',
+        nameShort: '55gore',
+        directory: '55gore',
+      },
+    ],
+    slots: {
+      preferShortName: true,
+    },
+  });
+});
diff --git a/test/snapshot/linkContribution.js b/test/snapshot/linkContribution.js
new file mode 100644
index 0000000..ebd3be5
--- /dev/null
+++ b/test/snapshot/linkContribution.js
@@ -0,0 +1,104 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'linkContribution (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  const quickSnapshot = (message, slots) =>
+    evaluate.snapshot(message, {
+      name: 'linkContribution',
+      multiple: [
+        {args: [
+          {who: {
+            name: 'Clark Powell',
+            directory: 'clark-powell',
+            urls: ['https://soundcloud.com/plazmataz'],
+          }, what: null},
+        ]},
+        {args: [
+          {who: {
+            name: 'Grounder & Scratch',
+            directory: 'the-big-baddies',
+            urls: [],
+          }, what: 'Snooping'},
+        ]},
+        {args: [
+          {who: {
+            name: 'Toby Fox',
+            directory: 'toby-fox',
+            urls: ['https://tobyfox.bandcamp.com/', 'https://toby.fox/'],
+          }, what: 'Arrangement'},
+        ]},
+      ],
+      slots,
+    });
+
+  quickSnapshot('showContribution & showIcons (inline)', {
+    showContribution: true,
+    showIcons: true,
+    iconMode: 'inline',
+  });
+
+  quickSnapshot('showContribution & showIcons (tooltip)', {
+    showContribution: true,
+    showIcons: true,
+    iconMode: 'tooltip',
+  });
+
+  quickSnapshot('only showContribution', {
+    showContribution: true,
+  });
+
+  quickSnapshot('only showIcons (inline)', {
+    showIcons: true,
+    iconMode: 'inline',
+  });
+
+  quickSnapshot('only showIcons (tooltip)', {
+    showContribution: true,
+    showIcons: true,
+    iconMode: 'tooltip',
+  });
+
+  quickSnapshot('no accents', {});
+
+  evaluate.snapshot('loads of links (inline)', {
+    name: 'linkContribution',
+    args: [
+      {who: {name: 'Lorem Ipsum Lover', directory: 'lorem-ipsum-lover', urls: [
+        'https://loremipsum.io',
+        'https://loremipsum.io/generator/',
+        'https://loremipsum.io/#meaning',
+        'https://loremipsum.io/#usage-and-examples',
+        'https://loremipsum.io/#controversy',
+        'https://loremipsum.io/#when-to-use-lorem-ipsum',
+        'https://loremipsum.io/#lorem-ipsum-all-the-things',
+        'https://loremipsum.io/#original-source',
+      ]}, what: null},
+    ],
+    slots: {showIcons: true},
+  });
+
+  evaluate.snapshot('loads of links (tooltip)', {
+    name: 'linkContribution',
+    args: [
+      {who: {name: 'Lorem Ipsum Lover', directory: 'lorem-ipsum-lover', urls: [
+        'https://loremipsum.io',
+        'https://loremipsum.io/generator/',
+        'https://loremipsum.io/#meaning',
+        'https://loremipsum.io/#usage-and-examples',
+        'https://loremipsum.io/#controversy',
+        'https://loremipsum.io/#when-to-use-lorem-ipsum',
+        'https://loremipsum.io/#lorem-ipsum-all-the-things',
+        'https://loremipsum.io/#original-source',
+      ]}, what: null},
+    ],
+    slots: {showIcons: true, iconMode: 'tooltip'},
+  });
+
+  quickSnapshot('no preventWrapping', {
+    showContribution: true,
+    showIcons: true,
+    preventWrapping: false,
+  });
+});
diff --git a/test/snapshot/linkExternal.js b/test/snapshot/linkExternal.js
new file mode 100644
index 0000000..90c98f4
--- /dev/null
+++ b/test/snapshot/linkExternal.js
@@ -0,0 +1,225 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'linkExternal (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  evaluate.snapshot('unknown domain (arbitrary world wide web path)', {
+    name: 'linkExternal',
+    args: ['https://snoo.ping.as/usual/i/see/'],
+  });
+
+  const urlsToArgs = urls =>
+    urls.map(url => ({args: [url]}));
+
+  const quickSnapshot = (message, urls, slots) =>
+    evaluate.snapshot(message, {
+      name: 'linkExternal',
+      slots,
+      multiple: urlsToArgs(urls),
+    });
+
+  const quickSnapshotAllStyles = (context, urls) => {
+    for (const style of ['platform', 'handle']) {
+      const message = `context: ${context}, style: ${style}`;
+      quickSnapshot(message, urls, {context, style});
+    }
+  };
+
+  // Try to comprehensively test every regular expression
+  // (in `match` and extractions like `handle` or `details`).
+
+  // Try to *also* represent a reasonable variety of what kinds
+  // of URLs appear throughout the wiki. (This should serve to
+  // identify areas which #external-links is expected to
+  // accommodate, regardless whether or not there is special
+  // attention given in the actual descriptors.)
+
+  // For normal custom-domain matches (e.g. Mastodon),
+  // it's OK to just test one custom domain in the list.
+
+  // Generally match the sorting order in externalLinkSpec,
+  // so corresponding and missing test cases are easy to locate.
+
+  quickSnapshotAllStyles('generic', [
+    // platform: appleMusic
+    'https://music.apple.com/us/artist/system-of-a-down/462715',
+
+    // platform: artstation
+    'https://www.artstation.com/eevaningtea',
+    'https://witnesstheabsurd.artstation.com/',
+
+    // platform: bandcamp
+    'https://music.solatrus.com/',
+    'https://homestuck.bandcamp.com/',
+
+    // platform: bluesky
+    'https://bsky.app/profile/jacobtheloofah.bsky.social',
+
+    // platform: carrd
+    'https://aliceflare.carrd.co',
+    'https://bigchaslappa.carrd.co/',
+
+    // platform: cohost
+    'https://cohost.org/cosmoptera',
+
+    // platform: deconreconstruction.music
+    'https://music.deconreconstruction.com/albums/catch-322',
+    'https://music.deconreconstruction.com/albums/catch-322?track=arcjecs-theme',
+
+    // platform: deconreconstruction
+    'https://www.deconreconstruction.com/',
+
+    // platform: deviantart
+    'https://culdhira.deviantart.com',
+    'https://www.deviantart.com/chesswanderlust-sama',
+    'https://www.deviantart.com/shilloshilloh/art/Homestuck-Jake-English-268874606',
+
+    // platform: facebook
+    'https://www.facebook.com/DoomedCloud/',
+    'https://www.facebook.com/pages/WoodenToaster/280642235307371',
+    'https://www.facebook.com/Svixy/posts/400018786702633',
+
+    // platform: fandom.mspaintadventures
+    'https://mspaintadventures.fandom.com/wiki/Draconian_Dignitary',
+    'https://mspaintadventures.fandom.com/wiki/',
+    'https://mspaintadventures.fandom.com/',
+
+    // platform: fandom
+    'https://community.fandom.com/',
+    'https://community.fandom.com/wiki/',
+    'https://community.fandom.com/wiki/Community_Central',
+
+    // platform: gamebanana
+    'https://gamebanana.com/members/2028092',
+    'https://gamebanana.com/mods/459476',
+
+    // platform: homestuck
+    'https://homestuck.com/',
+
+    // platform: hsmusic.archive
+    'https://hsmusic.wiki/media/misc/archive/Firefly%20Cloud%20Remix.mp3',
+
+    // platform: hsmusic
+    'https://hsmusic.wiki/feedback/',
+
+    // platform: internetArchive
+    'https://archive.org/details/a-life-well-lived',
+    'https://archive.org/details/VastError_Volume1/11+Renaissance.mp3',
+
+    // platform: instagram
+    'https://instagram.com/bass.and.noises',
+    'https://www.instagram.com/levc_egm/',
+
+    // platform: itch
+    'https://tuyoki.itch.io/',
+    'https://itch.io/profile/bravelittletoreador',
+
+    // platform: ko-fi
+    'https://ko-fi.com/gnaach',
+
+    // platform: linktree
+    'https://linktr.ee/bbpanzu',
+
+    // platform: mastodon
+    'https://types.pl/',
+
+    // platform: mspfa
+    'https://canwc.mspfa.com/',
+    'https://mspfa.com/?s=12003&p=1045',
+    'https://mspfa.com/user/?u=103334508819793669241',
+
+    // platform: neocities
+    'https://wodaro.neocities.org',
+    'https://neomints.neocities.org/',
+
+    // platform: newgrounds
+    'https://buzinkai.newgrounds.com/',
+    'https://www.newgrounds.com/audio/listen/1256058',
+
+    // platform: patreon
+    'https://www.patreon.com/CecilyRenns',
+
+    // platform: poetryFoundation
+    'https://www.poetryfoundation.org/poets/christina-rossetti',
+    'https://www.poetryfoundation.org/poems/45000/remember-56d224509b7ae',
+
+    // platform: soundcloud
+    'https://soundcloud.com/plazmataz',
+    'https://soundcloud.com/worthikids/1-i-accidentally-broke-my',
+
+    // platform: spotify
+    'https://open.spotify.com/artist/63SNNpNOicDzG3LY82G4q3',
+    'https://open.spotify.com/album/0iHvPD8rM3hQa0qeVtPQ3t',
+    'https://open.spotify.com/track/6YEGQH32aAXb9vQQbBrPlw',
+
+    // platform: tiktok
+    'https://www.tiktok.com/@richaadeb',
+
+    // platform: toyhouse
+    'https://toyhou.se/ghastaboo',
+
+    // platform: tumblr
+    'https://aeritus.tumblr.com/',
+    'https://vol5anthology.tumblr.com/post/159528808107/hey-everyone-its-413-and-that-means-we-have',
+    'https://www.tumblr.com/electricwestern',
+    'https://www.tumblr.com/spellmynamewithabang/142767566733/happy-413-this-is-the-first-time-anyones-heard',
+
+    // platform: twitch
+    'https://www.twitch.tv/ajhebard',
+    'https://www.twitch.tv/vargskelethor/',
+
+    // platform: twitter
+    'https://twitter.com/awkwarddoesart',
+    'https://twitter.com/purenonsens/',
+    'https://twitter.com/circlejourney/status/1202265927183548416',
+
+    // platform: waybackMachine
+    'https://web.archive.org/web/20120405160556/https://homestuck.bandcamp.com/album/colours-and-mayhem-universe-a',
+    'https://web.archive.org/web/20160807111207/http://griffinspacejam.com:80/',
+
+    // platform: wikipedia
+    'https://en.wikipedia.org/wiki/Haydn_Quartet_(vocal_ensemble)',
+
+    // platform: youtube
+    'https://youtube.com/@bani-chan8949',
+    'https://www.youtube.com/@Razzie16',
+    'https://www.youtube.com/channel/UCQXfvlKkpbOqEz4BepHqK7g',
+    'https://www.youtube.com/watch?v=6ekVnZm29kw',
+    'https://youtu.be/WBkC038wSio',
+    'https://www.youtube.com/playlist?list=PLy5UGIMKOXpONMExgI7lVYFwQa54QFp_H',
+  ]);
+
+  quickSnapshotAllStyles('album', [
+    'https://youtu.be/abc',
+    'https://youtube.com/watch?v=abc',
+    'https://youtube.com/Playlist?list=kweh',
+  ]);
+
+  quickSnapshotAllStyles('albumNoTracks', [
+    'https://youtu.be/abc',
+    'https://youtube.com/watch?v=abc',
+    'https://youtube.com/Playlist?list=kweh',
+  ]);
+
+  quickSnapshotAllStyles('albumOneTrack', [
+    'https://youtu.be/abc',
+    'https://youtube.com/watch?v=abc',
+    'https://youtube.com/Playlist?list=kweh',
+  ]);
+
+  quickSnapshotAllStyles('albumMultipleTracks', [
+    'https://youtu.be/abc',
+    'https://youtube.com/watch?v=abc',
+    'https://youtube.com/Playlist?list=kweh',
+  ]);
+
+  quickSnapshotAllStyles('flash', [
+    'https://www.bgreco.net/hsflash/002238.html',
+    'https://homestuck.com/story/1234',
+    'https://homestuck.com/story/pony',
+    'https://www.youtube.com/watch?v=wKgOp3Kg2wI',
+    'https://youtu.be/IOcvkkklWmY',
+    'https://some.external.site/foo/bar/',
+  ]);
+});
diff --git a/test/snapshot/linkTemplate.js b/test/snapshot/linkTemplate.js
new file mode 100644
index 0000000..300065e
--- /dev/null
+++ b/test/snapshot/linkTemplate.js
@@ -0,0 +1,63 @@
+import t from 'tap';
+import * as html from '#html';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'linkTemplate (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  evaluate.snapshot('fill many slots', {
+    name: 'linkTemplate',
+
+    slots: {
+      'href': 'https://hsmusic.wiki/media/cool file.pdf',
+      'hash': 'fooey',
+      'attributes': {class: 'dog', id: 'cat1'},
+      'content': 'My Cool Link',
+    },
+  });
+
+  evaluate.snapshot('fill path slot & provide appendIndexHTML', {
+    name: 'linkTemplate',
+
+    extraDependencies: {
+      to: (...path) => '/c*lzone/' + path.join('/') + '/',
+      appendIndexHTML: true,
+    },
+
+    slots: {
+      path: ['myCoolPath', 'ham', 'pineapple', 'tomato'],
+      content: 'delish',
+    },
+  });
+
+  evaluate.snapshot('special characters in path argument', {
+    name: 'linkTemplate',
+    slots: {
+      path: [
+        'media.albumAdditionalFile',
+        'homestuck-vol-1',
+        'Showtime (Piano Refrain) - #xXxAwesomeSheetMusick?rxXx#.pdf',
+      ],
+      content: `Damn, that's some good sheet music`,
+    },
+  });
+
+  evaluate.snapshot('missing content', {
+    name: 'linkTemplate',
+    slots: {href: 'banana'},
+  });
+
+  evaluate.snapshot('link in content', {
+    name: 'linkTemplate',
+    slots: {
+      hash: 'the-more-ye-know',
+      content: [
+        `Oh geez oh heck`,
+        html.tag('a', {href: 'dogs'}, `There's a link in here!!`),
+        `But here's <b>a normal tag.</b>`,
+        html.tag('div', `Gotta keep them normal tags.`),
+        html.tag('div', `But not... <a href="#">NESTED LINKS, OOO.</a>`),
+      ],
+    },
+  });
+});
diff --git a/test/snapshot/linkThing.js b/test/snapshot/linkThing.js
new file mode 100644
index 0000000..502db6d
--- /dev/null
+++ b/test/snapshot/linkThing.js
@@ -0,0 +1,94 @@
+import t from 'tap';
+import * as html from '#html';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'linkThing (snapshot)', async (t, evaluate) => {
+  await evaluate.load();
+
+  const quickSnapshot = (message, oneOrMultiple) =>
+    evaluate.snapshot(message,
+      (Array.isArray(oneOrMultiple)
+        ? {name: 'linkThing', multiple: oneOrMultiple}
+        : {name: 'linkThing', ...oneOrMultiple}));
+
+  quickSnapshot('basic behavior', {
+    args: ['localized.track', {
+      directory: 'foo',
+      color: '#abcdef',
+      name: `Cool track!`,
+    }],
+  });
+
+  quickSnapshot('preferShortName', {
+    args: ['localized.tag', {
+      directory: 'five-oceanfalls',
+      name: 'Five (Oceanfalls)',
+      nameShort: 'Five',
+    }],
+    slots: {preferShortName: true},
+  });
+
+  quickSnapshot('tooltip & content', {
+    args: ['localized.album', {
+      directory: 'beyond-canon',
+      name: 'Beyond Canon',
+      nameShort: 'BC',
+    }],
+    multiple: [
+      {slots: {tooltipStyle: 'none'}},
+      {slots: {tooltipStyle: 'browser'}},
+      {slots: {tooltipStyle: 'browser', content: 'Next'}},
+      {slots: {tooltipStyle: 'auto'}},
+      {slots: {tooltipStyle: 'auto', preferShortName: true}},
+      {slots: {tooltipStyle: 'auto', preferShortName: true, content: 'Next'}},
+      {slots: {tooltipStyle: 'auto', content: 'Next'}},
+      {slots: {tooltipStyle: 'wiki'}},
+      {slots: {tooltipStyle: 'wiki', content: 'Next'}},
+      {slots: {content: 'Banana'}},
+    ],
+  });
+
+  quickSnapshot('color', {
+    args: ['localized.track', {
+      directory: 'showtime-piano-refrain',
+      name: 'Showtime (Piano Refrain)',
+      color: '#38f43d',
+    }],
+    multiple: [
+      {slots: {color: false}},
+      {slots: {color: true}},
+      {slots: {color: '#aaccff'}},
+      {slots: {color: '#aaccff', tooltipStyle: 'wiki'}},
+    ],
+  });
+
+  quickSnapshot('tags in name escaped', [
+    {args: ['localized.track', {
+      directory: 'foo',
+      name: `<a href="SNOOPING">AS USUAL</a> I SEE`,
+    }]},
+    {args: ['localized.track', {
+      directory: 'bar',
+      name: `<b>boldface</b>`,
+    }]},
+    {args: ['localized.album', {
+      directory: 'exile',
+      name: '>Exile<',
+    }]},
+    {args: ['localized.track', {
+      directory: 'heart',
+      name: '<3',
+    }]},
+  ]);
+
+  quickSnapshot('nested links in content stripped', {
+    args: ['localized.staticPage', {directory: 'foo', name: 'Foo'}],
+    slots: {
+      content:
+        html.tag('b', {[html.joinChildren]: ''}, [
+          html.tag('a', {href: 'bar'}, `Oooo!`),
+          ` Very spooky.`,
+        ]),
+    },
+  });
+});
diff --git a/test/snapshot/transformContent.js b/test/snapshot/transformContent.js
new file mode 100644
index 0000000..87e337e
--- /dev/null
+++ b/test/snapshot/transformContent.js
@@ -0,0 +1,161 @@
+import t from 'tap';
+import {testContentFunctions} from '#test-lib';
+
+testContentFunctions(t, 'transformContent (snapshot)', async (t, evaluate) => {
+  await evaluate.load({
+    mock: {
+      image: evaluate.stubContentFunction('image'),
+    },
+  });
+
+  const extraDependencies = {
+    wikiData: {
+      albumData: [
+        {directory: 'cool-album', name: 'Cool Album', color: '#123456'},
+      ],
+    },
+
+    to: (key, ...args) => `to-${key}/${args.join('/')}`,
+  };
+
+  const quickSnapshot = (message, content, slots) =>
+    evaluate.snapshot(message, {
+      name: 'transformContent',
+      args: [content],
+      extraDependencies,
+      slots,
+    });
+
+  quickSnapshot(
+    'two text paragraphs',
+      `Hello, world!\n` +
+      `Wow, this is very cool.`);
+
+  quickSnapshot(
+    'links to a thing',
+      `This is [[album:cool-album|my favorite album]].\n` +
+      `That's right, [[album:cool-album]]!`);
+
+  quickSnapshot(
+    'indent on a directly following line',
+      `<div>\n` +
+      `    <span>Wow!</span>\n` +
+      `</div>`);
+
+  quickSnapshot(
+    'indent on an indierctly following line',
+      `Some text.\n` +
+      `Yes, some more text.\n` +
+      `\n` +
+      `    I am hax0rz!!\n` +
+      `    All yor base r blong 2 us.\n` +
+      `\n` +
+      `Aye.\n` +
+      `Aye aye aye.`);
+
+  quickSnapshot(
+    'hanging indent list',
+      `Hello!\n` +
+      `\n` +
+      `* I am a list item and I\n` +
+      `  go on and on and on\n` +
+      `  and on and on and on.\n` +
+      `\n` +
+      `* I am another list item.\n` +
+      `  Yeah.\n` +
+      `\n` +
+      `In-between!\n` +
+      `\n` +
+      `* Spooky,\n` +
+      `  spooky, I say!\n` +
+      `* Following list item.\n` +
+      `  No empty line around me.\n` +
+      `* Very cool.\n` +
+      `  So, so cool.\n` +
+      `\n` +
+      `Goodbye!`);
+
+  quickSnapshot(
+    'inline images',
+      `<img src="snooping.png"> as USUAL...\n` +
+      `What do you know? <img src="cowabunga.png" width="24" height="32">\n` +
+      `[[album:cool-album|I'm on the left.]]<img src="im-on-the-right.jpg">\n` +
+      `<img src="im-on-the-left.jpg">[[album:cool-album|I'm on the right.]]\n` +
+      `Media time! <img src="media/misc/interesting.png"> Oh yeah!\n` +
+      `<img src="must.png"><img src="stick.png"><img src="together.png">\n` +
+      `And... all done! <img src="end-of-source.png">`);
+
+  quickSnapshot(
+    'non-inline image #1',
+      `<img src="spark.png">`);
+
+  quickSnapshot(
+    'non-inline image #2',
+      `Rad.\n` +
+      `<img src="spark.png">`);
+
+  quickSnapshot(
+    'non-inline image #3',
+      `<img src="spark.png">\n` +
+      `Baller.`);
+
+  quickSnapshot(
+    'dates',
+      `[[date:2023-04-13]] Yep!\n` +
+      `Very nice: [[date:25 October 2413]]`);
+
+  quickSnapshot(
+    'super basic string',
+      `Neat listing: [[string:listingPage.listAlbums.byDate.title]]`);
+
+  quickSnapshot(
+    'basic markdown',
+      `Hello *world!* This is **SO COOL.**`);
+
+  quickSnapshot(
+    'escape entire tag',
+      `\\[[album:cool-album|spooky]] [[album:cool-album|scary]]`);
+
+  quickSnapshot(
+    'escape end of tag',
+      `My favorite album is [[album:cool-album|[Tactical Omission\\]]].\n` +
+      `Your favorite album is [[album:cool-album|[Tactical Wha-Huh-Now]]].`);
+
+  quickSnapshot(
+    'escape markdown',
+      `What will it be, *ye fool?* \\*arr*`);
+
+  quickSnapshot(
+    'lyrics - basic line breaks',
+      `Hey, ho\n` +
+      `And away we go\n` +
+      `Truly, music\n` +
+      `\n` +
+      `(Oh yeah)\n` +
+      `(That's right)`,
+      {mode: 'lyrics'});
+
+  quickSnapshot(
+    'lyrics - repeated and edge line breaks',
+      `\n\nWell, you know\nHow it goes\n\n\nYessiree\n\n\n`,
+      {mode: 'lyrics'});
+
+  quickSnapshot(
+    'lyrics - line breaks around tags',
+      `The date be [[date:13 April 2004]]\n` +
+      `I say, the date be [[date:13 April 2004]]\n` +
+      `[[date:13 April 2004]]\n` +
+      `[[date:13 April 2004]][[date:13 April 2004]][[date:13 April 2004]]\n` +
+      `(Aye!)\n` +
+      `\n` +
+      `[[date:13 April 2004]]\n` +
+      `[[date:13 April 2004]][[date:13 April 2004]]\n` +
+      `[[date:13 April 2004]]\n` +
+      `\n` +
+      `[[date:13 April 2004]]\n` +
+      `[[date:13 April 2004]], and don't ye forget it`,
+      {mode: 'lyrics'});
+
+  // TODO: Snapshots for mode: inline
+  // TODO: Snapshots for mode: single-link
+});