« get me outta code hell

data steps: more "quick" functions & basic snapshot test demo - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-03-25 15:00:01 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-03-25 15:05:30 -0300
commitd1e586bd401a6d83ecabafb3b833a5ae65b6e05f (patch)
tree81a97f0f678f9618ed0a66d1e5ef9d35dbf79a2a
parentfc5d4d057b1e18e3e5c91bce1ddb545bc9d91db9 (diff)
data steps: more "quick" functions & basic snapshot test demo
-rw-r--r--src/content-function.js4
-rw-r--r--src/content/dependencies/index.js89
-rw-r--r--tap-snapshots/test/snapshots/linkArtist.js.test.cjs14
-rw-r--r--test/snapshots/_support.js55
-rw-r--r--test/snapshots/linkArtist.js26
5 files changed, 177 insertions, 11 deletions
diff --git a/src/content-function.js b/src/content-function.js
index dbac691b..0a217800 100644
--- a/src/content-function.js
+++ b/src/content-function.js
@@ -383,6 +383,7 @@ export function quickEvaluate({
   // provided as part of allContentDependencies or allExtraDependencies.
   // Catch and report these early, together in an aggregate error.
   const unfulfilledErrors = [];
+  const unfulfilledNames = [];
   for (const name of neededContentDependencyNames) {
     const contentFunction = fulfilledContentDependencies[name];
     if (!contentFunction) continue;
@@ -392,12 +393,13 @@ export function quickEvaluate({
       } catch (error) {
         error.message = `(${name}) ${error.message}`;
         unfulfilledErrors.push(error);
+        unfulfilledNames.push(name);
       }
     }
   }
 
   if (!empty(unfulfilledErrors)) {
-    throw new AggregateError(unfulfilledErrors, `Content functions unfulfilled`);
+    throw new AggregateError(unfulfilledErrors, `Content functions unfulfilled (${unfulfilledNames.join(', ')})`);
   }
 
   const slotResults = {};
diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js
index 5cd116d4..7f86abb1 100644
--- a/src/content/dependencies/index.js
+++ b/src/content/dependencies/index.js
@@ -7,12 +7,19 @@ import contentFunction from '../../content-function.js';
 import {color, logWarn} from '../../util/cli.js';
 import {annotateFunction} from '../../util/sugar.js';
 
-export function watchContentDependencies() {
+export function watchContentDependencies({
+  logging = true,
+} = {}) {
   const events = new EventEmitter();
   const contentDependencies = {};
 
+  let emittedReady = false;
+  let initialScanComplete = false;
+  let allDependenciesFulfilled = false;
+
   Object.assign(events, {
     contentDependencies,
+    close,
   });
 
   // Watch adjacent files
@@ -32,10 +39,42 @@ export function watchContentDependencies() {
       return;
     }
     handlePathRemoved(filePath);
-  })
+  });
+
+  watcher.on('ready', () => {
+    initialScanComplete = true;
+    checkReadyConditions();
+  });
 
   return events;
 
+  async function close() {
+    return watcher.close();
+  }
+
+  function checkReadyConditions() {
+    if (emittedReady) {
+      return;
+    }
+
+    if (!initialScanComplete) {
+      return;
+    }
+
+    checkAllDependenciesFulfilled();
+
+    if (!allDependenciesFulfilled) {
+      return;
+    }
+
+    events.emit('ready');
+    emittedReady = true;
+  }
+
+  function checkAllDependenciesFulfilled() {
+    allDependenciesFulfilled = !Object.values(contentDependencies).includes(null);
+  }
+
   function getFunctionName(filePath) {
     const shortPath = path.basename(filePath);
     const functionName = shortPath.slice(0, -path.extname(shortPath).length);
@@ -85,24 +124,54 @@ export function watchContentDependencies() {
       }
 
       contentDependencies[functionName] = fn;
+
+      events.emit('update', functionName);
+      checkReadyConditions();
     }
 
     if (!error) {
       return true;
     }
 
-    if (contentDependencies[functionName]) {
-      logWarn`Failed to import ${functionName} - using existing version`;
-    } else {
-      logWarn`Failed to import ${functionName} - no prior version loaded`;
+    if (!(functionName in contentDependencies)) {
+      contentDependencies[functionName] = null;
     }
 
-    if (typeof error === 'string') {
-      console.error(color.yellow(error));
-    } else {
-      console.error(error);
+    events.emit('error', functionName, error);
+
+    if (logging) {
+      if (contentDependencies[functionName]) {
+        logWarn`Failed to import ${functionName} - using existing version`;
+      } else {
+        logWarn`Failed to import ${functionName} - no prior version loaded`;
+      }
+
+      if (typeof error === 'string') {
+        console.error(color.yellow(error));
+      } else {
+        console.error(error);
+      }
     }
 
     return false;
   }
 }
+
+export function quickLoadContentDependencies() {
+  return new Promise((resolve, reject) => {
+    const watcher = watchContentDependencies();
+
+    watcher.on('error', (name, error) => {
+      watcher.close().then(() => {
+        error.message = `Error loading dependency ${name}: ${error}`;
+        reject(error);
+      });
+    });
+
+    watcher.on('ready', () => {
+      watcher.close().then(() => {
+        resolve(watcher.contentDependencies);
+      });
+    });
+  });
+}
diff --git a/tap-snapshots/test/snapshots/linkArtist.js.test.cjs b/tap-snapshots/test/snapshots/linkArtist.js.test.cjs
new file mode 100644
index 00000000..7ca52796
--- /dev/null
+++ b/tap-snapshots/test/snapshots/linkArtist.js.test.cjs
@@ -0,0 +1,14 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below.  Do not ignore changes!
+ */
+'use strict'
+exports[`test/snapshots/linkArtist.js TAP linkArtist > output 1`] = `
+<a href="artist/toby-fox/">Toby Fox</a>
+`
+
+exports[`test/snapshots/linkArtist.js TAP linkArtist > output 2`] = `
+<a href="artist/55gore/">55gore</a>
+`
diff --git a/test/snapshots/_support.js b/test/snapshots/_support.js
new file mode 100644
index 00000000..b51f2847
--- /dev/null
+++ b/test/snapshots/_support.js
@@ -0,0 +1,55 @@
+import {quickEvaluate} from '../../src/content-function.js';
+import {quickLoadContentDependencies} from '../../src/content/dependencies/index.js';
+
+import chroma from 'chroma-js';
+import * as html from '../../src/util/html.js';
+import urlSpec from '../../src/url-spec.js';
+import {getColors} from '../../src/util/colors.js';
+import {generateURLs} from '../../src/util/urls.js';
+
+export function testContentFunctions(t, message, fn) {
+  const urls = generateURLs(urlSpec);
+
+  t.test(message, async t => {
+    const loadedContentDependencies = await quickLoadContentDependencies();
+
+    const evaluate = ({
+      from = 'localized.home',
+      contentDependencies = {},
+      extraDependencies = {},
+      ...opts
+    }) => {
+      const {to} = urls.from(from);
+
+      try {
+        return quickEvaluate({
+          ...opts,
+          contentDependencies: {
+            ...contentDependencies,
+            ...loadedContentDependencies,
+          },
+          extraDependencies: {
+            html,
+            to,
+            urls,
+            appendIndexHTML: false,
+            getColors: c => getColors(c, {chroma}),
+            ...extraDependencies,
+          },
+        });
+      } catch (error) {
+        if (error instanceof AggregateError) {
+          error = new Error(`AggregateError: ${error.message}\n${error.errors.map(err => `** ${err}`).join('\n')}`);
+        }
+        throw error;
+      }
+    };
+
+    evaluate.snapshot = (opts, fn) => {
+      const result = (fn ? fn(evaluate(opts)) : evaluate(opts));
+      t.matchSnapshot(result.toString(), 'output');
+    };
+
+    return fn(t, evaluate);
+  });
+}
diff --git a/test/snapshots/linkArtist.js b/test/snapshots/linkArtist.js
new file mode 100644
index 00000000..43fee88e
--- /dev/null
+++ b/test/snapshots/linkArtist.js
@@ -0,0 +1,26 @@
+import t from 'tap';
+
+import {testContentFunctions} from './_support.js';
+
+testContentFunctions(t, 'linkArtist', (t, evaluate) => {
+  evaluate.snapshot({
+    name: 'linkArtist',
+    args: [
+      {
+        name: `Toby Fox`,
+        directory: `toby-fox`,
+      }
+    ],
+  });
+
+  evaluate.snapshot({
+    name: 'linkArtist',
+    args: [
+      {
+        name: 'ICCTTCMDMIROTMCWMWFTPFTDDOTARHPOESWGBTWEATFCWSEBTSSFOFG',
+        nameShort: '55gore',
+        directory: '55gore',
+      },
+    ],
+  }, v => v.slot('preferShortName', true));
+});