« get me outta code hell

data steps: unfinished behavior & fixes in test lib - 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-28 19:49:53 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-03-28 19:49:53 -0300
commitb6393b1d3fc9adc59bc387b8013cbad30e3a164f (patch)
treea7ef59111434bb83e85eed47603d47ca4d645fec
parentc6f1011722dc6fe50afb3a63ee414c70dbfd6abf (diff)
data steps: unfinished behavior & fixes in test lib
-rw-r--r--src/util/sugar.js9
-rw-r--r--test/lib/content-function.js36
-rw-r--r--test/lib/generic-mock.js92
3 files changed, 98 insertions, 39 deletions
diff --git a/src/util/sugar.js b/src/util/sugar.js
index ad36d16b..f3b9e732 100644
--- a/src/util/sugar.js
+++ b/src/util/sugar.js
@@ -431,6 +431,7 @@ export function _withAggregate(mode, aggregateOpts, fn) {
 export function showAggregate(topError, {
   pathToFileURL = f => f,
   showTraces = true,
+  print = true,
 } = {}) {
   const recursive = (error, {level}) => {
     let header = showTraces
@@ -475,7 +476,13 @@ export function showAggregate(topError, {
     }
   };
 
-  console.error(recursive(topError, {level: 0}));
+  const message = recursive(topError, {level: 0});
+
+  if (print) {
+    console.error(message);
+  } else {
+    return message;
+  }
 }
 
 export function decorateErrorWithIndex(fn) {
diff --git a/test/lib/content-function.js b/test/lib/content-function.js
index 21af0e5a..a6ff64af 100644
--- a/test/lib/content-function.js
+++ b/test/lib/content-function.js
@@ -1,13 +1,19 @@
+import chroma from 'chroma-js';
+import * as path from 'path';
+import {fileURLToPath} from 'url';
+
+import mock from './generic-mock.js';
 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 * as html from '../../src/util/html.js';
+import {empty, showAggregate} from '../../src/util/sugar.js';
 import {getColors} from '../../src/util/colors.js';
 import {generateURLs} from '../../src/util/urls.js';
+import {processLanguageFile} from '../../src/data/language.js';
 
-import mock from './generic-mock.js';
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
 
 export function testContentFunctions(t, message, fn) {
   const urls = generateURLs(urlSpec);
@@ -15,6 +21,7 @@ export function testContentFunctions(t, message, fn) {
   t.test(message, async t => {
     let loadedContentDependencies;
 
+    const language = await processLanguageFile('./src/strings-default.json');
     const mocks = [];
 
     const evaluate = ({
@@ -38,6 +45,7 @@ export function testContentFunctions(t, message, fn) {
           },
           extraDependencies: {
             html,
+            language,
             to,
             urls,
             appendIndexHTML: false,
@@ -74,7 +82,7 @@ export function testContentFunctions(t, message, fn) {
 
     await fn(t, evaluate);
 
-    if (mocks.length) {
+    if (!empty(mocks)) {
       cleanCatchAggregate(() => {
         const errors = [];
         for (const {close} of mocks) {
@@ -84,7 +92,7 @@ export function testContentFunctions(t, message, fn) {
             errors.push(error);
           }
         }
-        if (errors.length) {
+        if (!empty(errors)) {
           throw new AggregateError(errors, `Errors closing mocks`);
         }
       });
@@ -92,21 +100,13 @@ export function testContentFunctions(t, message, fn) {
   });
 }
 
-function cleanAggregate(error) {
-  if (error instanceof AggregateError) {
-    return new Error(`[AggregateError: ${error.message}\n${
-      error.errors
-        .map(cleanAggregate)
-        .map(err => ` * ${err.message.split('\n').map((l, i) => (i > 0 ? '   ' + l : l)).join('\n')}`)
-        .join('\n')}]`);
-  } else {
-    return error;
-  }
-}
-
 function printAggregate(error) {
   if (error instanceof AggregateError) {
-    const {message} = cleanAggregate(error);
+    const message = showAggregate(error, {
+      showTraces: true,
+      print: false,
+      pathToFileURL: f => path.relative(path.join(__dirname, '../..'), fileURLToPath(f)),
+    });
     for (const line of message.split('\n')) {
       console.error(line);
     }
diff --git a/test/lib/generic-mock.js b/test/lib/generic-mock.js
index 841ba462..2a346448 100644
--- a/test/lib/generic-mock.js
+++ b/test/lib/generic-mock.js
@@ -1,5 +1,7 @@
 import {same} from 'tcompare';
 
+import {empty} from '../../src/util/sugar.js';
+
 export default function mock(callback) {
   const mocks = [];
 
@@ -24,7 +26,7 @@ export default function mock(callback) {
           errors.push(error);
         }
       }
-      if (errors.length) {
+      if (!empty(errors)) {
         throw new AggregateError(errors, `Errors closing sub-mocks`);
       }
     },
@@ -120,8 +122,29 @@ export function mockFunction(...args) {
     return fn;
   };
 
+  fn.neverCalled = (...args) => {
+    if (!empty(args)) {
+      throw new TypeError(`Didn't expect any arguments`);
+    }
+
+    if (allCallDescriptions[0].described) {
+      throw new TypeError(`Unexpected .neverCalled() when any descriptions provided`);
+    }
+
+    limitCallCount = true;
+    allCallDescriptions.splice(0, allCallDescriptions.length);
+
+    currentCallDescription = new Proxy({}, {
+      set() {
+        throw new Error(`Unexpected description when .neverCalled() has been called`);
+      },
+    });
+
+    return fn;
+  };
+
   fn.once = (...args) => {
-    if (args.length) {
+    if (!empty(args)) {
       throw new TypeError(`Didn't expect any arguments`);
     }
 
@@ -129,6 +152,7 @@ export function mockFunction(...args) {
       throw new TypeError(`Unexpected .once() when providing multiple descriptions`);
     }
 
+    currentCallDescription.described = true;
     limitCallCount = true;
     markedAsOnce = true;
 
@@ -136,7 +160,7 @@ export function mockFunction(...args) {
   };
 
   fn.next = (...args) => {
-    if (args.length) {
+    if (!empty(args)) {
       throw new TypeError(`Didn't expect any arguments`);
     }
 
@@ -148,6 +172,7 @@ export function mockFunction(...args) {
     allCallDescriptions.push(currentCallDescription);
 
     limitCallCount = true;
+
     return fn;
   };
 
@@ -156,9 +181,9 @@ export function mockFunction(...args) {
     // call description which is being repeated.
 
     if (!(
-      typeof value === 'number' &&
-      value === parseInt(value) &&
-      value >= 2
+      typeof times === 'number' &&
+      times === parseInt(times) &&
+      times >= 2
     )) {
       throw new TypeError(`Expected whole number of at least 2`);
     }
@@ -185,6 +210,19 @@ export function mockFunction(...args) {
   return {
     value: fn,
     close: () => {
+      const totalCallCount = runningCallCount;
+      const expectedCallCount = countDescribedCalls();
+
+      if (limitCallCount && totalCallCount !== expectedCallCount) {
+        if (expectedCallCount > 1) {
+          topLevelErrors.push(new Error(`Expected ${expectedCallCount} calls, got ${totalCallCount}`));
+        } else if (expectedCallCount === 1) {
+          topLevelErrors.push(new Error(`Expected 1 call, got ${totalCallCount}`));
+        } else {
+          topLevelErrors.push(new Error(`Unexpectedly called at all`));
+        }
+      }
+
       if (topLevelErrors.length) {
         throw new AggregateError(topLevelErrors, `Errors in mock ${name}`);
       }
@@ -204,6 +242,13 @@ export function mockFunction(...args) {
     const callErrors = [];
 
     runningCallCount++;
+
+    // No further processing, this indicates the function shouldn't have been
+    // called at all and there aren't any descriptions to match this call with.
+    if (empty(allCallDescriptions)) {
+      return;
+    }
+
     const currentCallNumber = runningCallCount;
     const currentDescription = selectCallDescription(currentCallNumber);
 
@@ -212,10 +257,9 @@ export function mockFunction(...args) {
       argsPattern,
     } = currentDescription;
 
-    if (argumentCount !== null) {
-      if (args.length !== argumentCount) {
-        callErrors.push(new Error(`Argument count mismatch: expected ${argumentCount}, got ${args.length}`));
-      }
+    if (argumentCount !== null && args.length !== argumentCount) {
+      callErrors.push(
+        new Error(`Argument count mismatch: expected ${argumentCount}, got ${args.length}`));
     }
 
     if (argsPattern !== null) {
@@ -232,7 +276,7 @@ export function mockFunction(...args) {
       }
     }
 
-    if (callErrors.length) {
+    if (!empty(callErrors)) {
       const aggregate = new AggregateError(callErrors, `Errors in call #${currentCallNumber}`);
       topLevelErrors.push(aggregate);
     }
@@ -241,15 +285,8 @@ export function mockFunction(...args) {
   }
 
   function selectCallDescription(currentCallNumber) {
-    // console.log(currentCallNumber, allCallDescriptions[0]);
-
-    const lastDescription = allCallDescriptions[allCallDescriptions.length - 1];
-    const describedCount =
-      (lastDescription.described
-        ? allCallDescriptions.length
-        : allCallDescriptions.length - 1);
-
-    if (currentCallNumber > describedCount) {
+    if (currentCallNumber > countDescribedCalls()) {
+      const lastDescription = lastCallDescription();
       if (lastDescription.described) {
         return newCallDescription();
       } else {
@@ -259,4 +296,19 @@ export function mockFunction(...args) {
       return allCallDescriptions[currentCallNumber - 1];
     }
   }
+
+  function countDescribedCalls() {
+    if (empty(allCallDescriptions)) {
+      return 0;
+    }
+
+    return (
+      (lastCallDescription().described
+        ? allCallDescriptions.length
+        : allCallDescriptions.length - 1));
+  }
+
+  function lastCallDescription() {
+    return allCallDescriptions[allCallDescriptions.length - 1];
+  }
 }