diff options
Diffstat (limited to 'test/unit/data/composite')
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`}, + ], + }, + }, + }); +}); |