« 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/composite/common-utilities
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/data/composite/common-utilities')
-rw-r--r--test/unit/data/composite/common-utilities/exposeConstant.js52
-rw-r--r--test/unit/data/composite/common-utilities/exposeDependency.js74
-rw-r--r--test/unit/data/composite/common-utilities/withPropertiesFromObject.js224
-rw-r--r--test/unit/data/composite/common-utilities/withPropertyFromObject.js125
-rw-r--r--test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js200
5 files changed, 675 insertions, 0 deletions
diff --git a/test/unit/data/composite/common-utilities/exposeConstant.js b/test/unit/data/composite/common-utilities/exposeConstant.js
new file mode 100644
index 00000000..829dc706
--- /dev/null
+++ b/test/unit/data/composite/common-utilities/exposeConstant.js
@@ -0,0 +1,52 @@
+import t from 'tap';
+
+import {
+  compositeFrom,
+  continuationSymbol,
+  exposeConstant,
+  input,
+} from '#composite';
+
+t.test(`exposeConstant: basic behavior`, t => {
+  t.plan(2);
+
+  const composite1 = compositeFrom({
+    compose: false,
+
+    steps: [
+      exposeConstant({
+        value: input.value('foo'),
+      }),
+    ],
+  });
+
+  t.match(composite1, {
+    expose: {
+      dependencies: [],
+    },
+  });
+
+  t.equal(composite1.expose.compute(), 'foo');
+});
+
+t.test(`exposeConstant: validate inputs`, t => {
+  t.plan(2);
+
+  t.throws(
+    () => exposeConstant({}),
+    {
+      message: `Errors in input options passed to exposeConstant`,
+      errors: [
+        {message: `Required these inputs: value`},
+      ],
+    });
+
+  t.throws(
+    () => exposeConstant({value: 'some dependency'}),
+    {
+      message: `Errors in input options passed to exposeConstant`,
+      errors: [
+        {message: `value: Expected input.value() call, got dependency name`},
+      ],
+    });
+});
diff --git a/test/unit/data/composite/common-utilities/exposeDependency.js b/test/unit/data/composite/common-utilities/exposeDependency.js
new file mode 100644
index 00000000..78801343
--- /dev/null
+++ b/test/unit/data/composite/common-utilities/exposeDependency.js
@@ -0,0 +1,74 @@
+import t from 'tap';
+
+import {
+  compositeFrom,
+  continuationSymbol,
+  exposeDependency,
+  input,
+} from '#composite';
+
+t.test(`exposeDependency: basic behavior`, t => {
+  t.plan(4);
+
+  const composite1 = compositeFrom({
+    compose: false,
+
+    steps: [
+      exposeDependency({dependency: 'foo'}),
+    ],
+  });
+
+  t.match(composite1, {
+    expose: {
+      dependencies: ['foo'],
+    },
+  });
+
+  t.equal(composite1.expose.compute({foo: 'bar'}), 'bar');
+
+  const composite2 = compositeFrom({
+    compose: false,
+
+    steps: [
+      {
+        dependencies: ['foo'],
+        compute: (continuation, {foo}) =>
+          continuation({'#bar': foo.toUpperCase()}),
+      },
+
+      exposeDependency({dependency: '#bar'}),
+    ],
+  });
+
+  t.match(composite2, {
+    expose: {
+      dependencies: ['foo'],
+    },
+  });
+
+  t.equal(composite2.expose.compute({foo: 'bar'}), 'BAR');
+});
+
+t.test(`exposeDependency: validate inputs`, t => {
+  t.plan(2);
+
+  t.throws(
+    () => exposeDependency({}),
+    {
+      message: `Errors in input options passed to exposeDependency`,
+      errors: [
+        {message: `Required these inputs: dependency`},
+      ],
+    });
+
+  t.throws(
+    () => exposeDependency({
+      dependency: input.value('some static value'),
+    }),
+    {
+      message: `Errors in input options passed to exposeDependency`,
+      errors: [
+        {message: `dependency: Expected dependency name, got input.value() call`},
+      ],
+    });
+});
diff --git a/test/unit/data/composite/common-utilities/withPropertiesFromObject.js b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js
new file mode 100644
index 00000000..b1b8be7a
--- /dev/null
+++ b/test/unit/data/composite/common-utilities/withPropertiesFromObject.js
@@ -0,0 +1,224 @@
+import t from 'tap';
+
+import {
+  compositeFrom,
+  exposeDependency,
+  input,
+  withPropertiesFromObject,
+} from '#composite';
+
+t.test(`withPropertiesFromObject: basic behavior`, t => {
+  t.plan(4);
+
+  const composite = compositeFrom({
+    compose: false,
+
+    steps: [
+      withPropertiesFromObject({
+        object: 'object',
+        properties: 'properties',
+      }),
+
+      exposeDependency({dependency: '#object'}),
+    ],
+  });
+
+  t.match(composite, {
+    expose: {
+      dependencies: ['object', 'properties'],
+    },
+  });
+
+  t.same(
+    composite.expose.compute({
+      object: {foo: 'bar', bim: 'BOOM', bam: 'baz'},
+      properties: ['foo', 'bim'],
+    }),
+    {foo: 'bar', bim: 'BOOM'});
+
+  t.same(
+    composite.expose.compute({
+      object: {value1: 'uwah', value2: 'arah'},
+      properties: ['value1', 'value3'],
+    }),
+    {value1: 'uwah', value3: null});
+
+  t.same(
+    composite.expose.compute({
+      object: null,
+      properties: ['ohMe', 'ohMy', 'ohDear'],
+    }),
+    {ohMe: null, ohMy: null, ohDear: null});
+});
+
+t.test(`withPropertiesFromObject: output shapes & values`, t => {
+  t.plan(2 * 2 * 3 ** 2);
+
+  const dependencies = {
+    ['object_dependency']:
+      {foo: 'apple', bar: 'banana', baz: 'orange'},
+    [input('object_neither')]:
+      {foo: 'koala', bar: 'okapi', baz: 'mongoose'},
+    ['properties_dependency']:
+      ['foo', 'bar', 'missing1'],
+    [input('properties_neither')]:
+      ['foo', 'baz', 'missing3'],
+  };
+
+  const mapLevel1 = [
+    [input.value('prefix_value'), [
+      ['object_dependency', [
+        ['properties_dependency', {
+          '#object': {foo: 'apple', bar: 'banana', missing1: null},
+        }],
+        [input.value(['bar', 'baz', 'missing2']), {
+          '#prefix_value.bar': 'banana',
+          '#prefix_value.baz': 'orange',
+          '#prefix_value.missing2': null,
+        }],
+        [input('properties_neither'), {
+          '#object': {foo: 'apple', baz: 'orange', missing3: null},
+        }]]],
+
+      [input.value({foo: 'ouh', bar: 'rah', baz: 'nyu'}), [
+        ['properties_dependency', {
+          '#object': {foo: 'ouh', bar: 'rah', missing1: null},
+        }],
+        [input.value(['bar', 'baz', 'missing2']), {
+          '#prefix_value.bar': 'rah',
+          '#prefix_value.baz': 'nyu',
+          '#prefix_value.missing2': null,
+        }],
+        [input('properties_neither'), {
+          '#object': {foo: 'ouh', baz: 'nyu', missing3: null},
+        }]]],
+
+      [input('object_neither'), [
+        ['properties_dependency', {
+          '#object': {foo: 'koala', bar: 'okapi', missing1: null},
+        }],
+        [input.value(['bar', 'baz', 'missing2']), {
+          '#prefix_value.bar': 'okapi',
+          '#prefix_value.baz': 'mongoose',
+          '#prefix_value.missing2': null,
+        }],
+        [input('properties_neither'), {
+          '#object': {foo: 'koala', baz: 'mongoose', missing3: null},
+        }]]]]],
+
+    [input.value(null), [
+      ['object_dependency', [
+        ['properties_dependency', {
+          '#object': {foo: 'apple', bar: 'banana', missing1: null},
+        }],
+        [input.value(['bar', 'baz', 'missing2']), {
+          '#object_dependency.bar': 'banana',
+          '#object_dependency.baz': 'orange',
+          '#object_dependency.missing2': null,
+        }],
+        [input('properties_neither'), {
+          '#object': {foo: 'apple', baz: 'orange', missing3: null},
+        }]]],
+
+      [input.value({foo: 'ouh', bar: 'rah', baz: 'nyu'}), [
+        ['properties_dependency', {
+          '#object': {foo: 'ouh', bar: 'rah', missing1: null},
+        }],
+        [input.value(['bar', 'baz', 'missing2']), {
+          '#object.bar': 'rah',
+          '#object.baz': 'nyu',
+          '#object.missing2': null,
+        }],
+        [input('properties_neither'), {
+          '#object': {foo: 'ouh', baz: 'nyu', missing3: null},
+        }]]],
+
+      [input('object_neither'), [
+        ['properties_dependency', {
+          '#object': {foo: 'koala', bar: 'okapi', missing1: null},
+        }],
+        [input.value(['bar', 'baz', 'missing2']), {
+          '#object.bar': 'okapi',
+          '#object.baz': 'mongoose',
+          '#object.missing2': null,
+        }],
+        [input('properties_neither'), {
+          '#object': {foo: 'koala', baz: 'mongoose', missing3: null},
+        }]]]]],
+  ];
+
+  for (const [prefixInput, mapLevel2] of mapLevel1) {
+    for (const [objectInput, mapLevel3] of mapLevel2) {
+      for (const [propertiesInput, outputDict] of mapLevel3) {
+        const step = withPropertiesFromObject({
+          prefix: prefixInput,
+          object: objectInput,
+          properties: propertiesInput,
+        });
+
+        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);
+  }
+});
+
+t.test(`withPropertiesFromObject: validate static inputs`, t => {
+  t.plan(3);
+
+  t.throws(
+    () => withPropertiesFromObject({}),
+    {
+      message: `Errors in input options passed to withPropertiesFromObject`,
+      errors: [
+        {message: `Required these inputs: object, properties`},
+      ],
+    });
+
+  t.throws(
+    () => withPropertiesFromObject({
+      object: input.value('intriguing'),
+      properties: input.value('very'),
+      prefix: input.value({yes: 'yup'}),
+    }),
+    {
+      message: `Errors in input options passed to withPropertiesFromObject`,
+      errors: [
+        {message: `object: Expected an object, got string`},
+        {message: 'properties: Expected an array, got string'},
+        {message: 'prefix: Expected a string, got object'},
+      ],
+    });
+
+  t.throws(
+    () => withPropertiesFromObject({
+      object: input.value([['abc', 1], ['def', 2], [123, 3]]),
+      properties: input.value(['abc', 'def', 123]),
+    }),
+    {message: `Errors in input options passed to withPropertiesFromObject`, errors: [
+      {message: 'object: Expected an object, got array'},
+      {message: 'properties: Errors validating array items', errors: [
+        {
+          [Symbol.for('hsmusic.decorate.indexInSourceArray')]: 2,
+          message: /Expected a string, got number/,
+        },
+      ]},
+    ]});
+});
diff --git a/test/unit/data/composite/common-utilities/withPropertyFromObject.js b/test/unit/data/composite/common-utilities/withPropertyFromObject.js
new file mode 100644
index 00000000..11487226
--- /dev/null
+++ b/test/unit/data/composite/common-utilities/withPropertyFromObject.js
@@ -0,0 +1,125 @@
+import t from 'tap';
+
+import {
+  compositeFrom,
+  exposeDependency,
+  input,
+  withPropertyFromObject,
+} from '#composite';
+
+t.test(`withPropertyFromObject: basic behavior`, t => {
+  t.plan(4);
+
+  const composite = compositeFrom({
+    compose: false,
+
+    steps: [
+      withPropertyFromObject({
+        object: 'object',
+        property: 'property',
+      }),
+
+      exposeDependency({dependency: '#value'}),
+    ],
+  });
+
+  t.match(composite, {
+    expose: {
+      dependencies: ['object', 'property'],
+    },
+  });
+
+  t.equal(composite.expose.compute({
+    object: {foo: 'bar', bim: 'BOOM'},
+    property: 'bim',
+  }), 'BOOM');
+
+  t.equal(composite.expose.compute({
+    object: {value1: 'uwah'},
+    property: 'value2',
+  }), null);
+
+  t.equal(composite.expose.compute({
+    object: null,
+    property: 'oml where did me object go',
+  }), null);
+});
+
+t.test(`withPropertyFromObject: output shapes & values`, t => {
+  t.plan(2 * 3 ** 2);
+
+  const dependencies = {
+    ['object_dependency']:
+      {foo: 'apple', bar: 'banana', baz: 'orange'},
+    [input('object_neither')]:
+      {foo: 'koala', bar: 'okapi', baz: 'mongoose'},
+    ['property_dependency']:
+      'foo',
+    [input('property_neither')]:
+      'baz',
+  };
+
+  const mapLevel1 = [
+    ['object_dependency', [
+      ['property_dependency', {
+        '#value': 'apple',
+      }],
+      [input.value('bar'), {
+        '#object_dependency.bar': 'banana',
+      }],
+      [input('property_neither'), {
+        '#value': 'orange',
+      }]]],
+
+    [input.value({foo: 'ouh', bar: 'rah', baz: 'nyu'}), [
+      ['property_dependency', {
+        '#value': 'ouh',
+      }],
+      [input.value('bar'), {
+        '#value': 'rah',
+      }],
+      [input('property_neither'), {
+        '#value': 'nyu',
+      }]]],
+
+    [input('object_neither'), [
+      ['property_dependency', {
+        '#value': 'koala',
+      }],
+      [input.value('bar'), {
+        '#value': 'okapi',
+      }],
+      [input('property_neither'), {
+        '#value': 'mongoose',
+      }]]],
+  ];
+
+  for (const [objectInput, mapLevel2] of mapLevel1) {
+    for (const [propertyInput, outputDict] of mapLevel2) {
+      const step = withPropertyFromObject({
+        object: objectInput,
+        property: propertyInput,
+      });
+
+      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);
+  }
+});
diff --git a/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js
new file mode 100644
index 00000000..dacf60f0
--- /dev/null
+++ b/test/unit/data/composite/common-utilities/withResultOfAvailabilityCheck.js
@@ -0,0 +1,200 @@
+import t from 'tap';
+
+import {
+  compositeFrom,
+  continuationSymbol,
+  withResultOfAvailabilityCheck,
+  input,
+} from '#composite';
+
+const composite = compositeFrom({
+  compose: false,
+
+  steps: [
+    withResultOfAvailabilityCheck({
+      from: 'from',
+      mode: 'mode',
+    }).outputs({
+      ['#availability']: '#result',
+    }),
+
+    {
+      dependencies: ['#result'],
+      compute: ({'#result': result}) => result,
+    },
+  ],
+});
+
+t.test(`withResultOfAvailabilityCheck: basic behavior`, t => {
+  t.plan(1);
+
+  t.match(composite, {
+    expose: {
+      dependencies: ['from', 'mode'],
+    },
+  });
+});
+
+const quickCompare = (t, expect, {from, mode}) =>
+  t.equal(composite.expose.compute({from, mode}), expect);
+
+const quickThrows = (t, {from, mode}) =>
+  t.throws(() => composite.expose.compute({from, mode}));
+
+t.test(`withResultOfAvailabilityCheck: mode = null`, t => {
+  t.plan(10);
+
+  quickCompare(t, true,  {mode: 'null', from: 'truthy string'});
+  quickCompare(t, true,  {mode: 'null', from: 123});
+  quickCompare(t, true,  {mode: 'null', from: true});
+
+  quickCompare(t, true,  {mode: 'null', from: ''});
+  quickCompare(t, true,  {mode: 'null', from: 0});
+  quickCompare(t, true,  {mode: 'null', from: false});
+
+  quickCompare(t, true,  {mode: 'null', from: [1, 2, 3]});
+  quickCompare(t, true,  {mode: 'null', from: []});
+
+  quickCompare(t, false, {mode: 'null', from: null});
+  quickCompare(t, false, {mode: 'null', from: undefined});
+});
+
+t.test(`withResultOfAvailabilityCheck: mode = empty`, t => {
+  t.plan(10);
+
+  quickThrows(t, {mode: 'empty', from: 'truthy string'});
+  quickThrows(t, {mode: 'empty', from: 123});
+  quickThrows(t, {mode: 'empty', from: true});
+
+  quickThrows(t, {mode: 'empty', from: ''});
+  quickThrows(t, {mode: 'empty', from: 0});
+  quickThrows(t, {mode: 'empty', from: false});
+
+  quickCompare(t, true,  {mode: 'empty', from: [1, 2, 3]});
+  quickCompare(t, false, {mode: 'empty', from: []});
+
+  quickCompare(t, false, {mode: 'empty', from: null});
+  quickCompare(t, false, {mode: 'empty', from: undefined});
+});
+
+t.test(`withResultOfAvailabilityCheck: mode = falsy`, t => {
+  t.plan(10);
+
+  quickCompare(t, true,  {mode: 'falsy', from: 'truthy string'});
+  quickCompare(t, true,  {mode: 'falsy', from: 123});
+  quickCompare(t, true,  {mode: 'falsy', from: true});
+
+  quickCompare(t, false, {mode: 'falsy', from: ''});
+  quickCompare(t, false, {mode: 'falsy', from: 0});
+  quickCompare(t, false, {mode: 'falsy', from: false});
+
+  quickCompare(t, true,  {mode: 'falsy', from: [1, 2, 3]});
+  quickCompare(t, false, {mode: 'falsy', from: []});
+
+  quickCompare(t, false, {mode: 'falsy', from: null});
+  quickCompare(t, false, {mode: 'falsy', from: undefined});
+});
+
+t.test(`withResultOfAvailabilityCheck: default mode`, t => {
+  t.plan(1);
+
+  const template = withResultOfAvailabilityCheck({
+    from: 'foo',
+  });
+
+  t.match(template.toDescription(), {
+    inputMapping: {
+      from: input.dependency('foo'),
+      mode: input.value('null'),
+    },
+  });
+});
+
+t.test(`withResultOfAvailabilityCheck: validate static inputs`, t => {
+  t.plan(5);
+
+  t.throws(
+    () => withResultOfAvailabilityCheck({}),
+    {
+      message: `Errors in input options passed to withResultOfAvailabilityCheck`,
+      errors: [
+        {message: `Required these inputs: from`}
+      ],
+    });
+
+  t.doesNotThrow(() =>
+    withResultOfAvailabilityCheck({
+      from: 'dependency1',
+      mode: 'dependency2',
+    }));
+
+  t.doesNotThrow(() =>
+    withResultOfAvailabilityCheck({
+      from: input.value('some static value'),
+      mode: input.value('null'),
+    }));
+
+  t.throws(
+    () => withResultOfAvailabilityCheck({
+      from: 'foo',
+      mode: input.value('invalid'),
+    }),
+    {
+      message: `Errors in input options passed to withResultOfAvailabilityCheck`,
+      errors: [
+        {message: `mode: Expected one of null empty falsy, got invalid`},
+      ],
+    });
+
+  t.throws(() =>
+    withResultOfAvailabilityCheck({
+      from: input.value(null),
+      mode: input.value(null),
+    }),
+    {
+      message: `Errors in input options passed to withResultOfAvailabilityCheck`,
+      errors: [
+        {message: `mode: Expected a value, got null`},
+      ],
+    });
+});
+
+t.test(`withResultOfAvailabilityCheck: validate dynamic inputs`, t => {
+  t.plan(2);
+
+  t.throws(
+    () => composite.expose.compute({
+      from: 'apple',
+      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, got banana`},
+          ],
+        },
+      },
+    });
+
+  t.throws(
+    () => composite.expose.compute({
+      from: null,
+      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`},
+          ],
+        },
+      },
+    });
+});