diff options
Diffstat (limited to 'test/unit/data/composite/data')
3 files changed, 460 insertions, 0 deletions
diff --git a/test/unit/data/composite/data/withPropertiesFromObject.js b/test/unit/data/composite/data/withPropertiesFromObject.js new file mode 100644 index 0000000..750dc8c --- /dev/null +++ b/test/unit/data/composite/data/withPropertiesFromObject.js @@ -0,0 +1,254 @@ +import t from 'tap'; + +import {compositeFrom, input} from '#composite'; +import {exposeDependency} from '#composite/control-flow'; +import {withPropertiesFromObject} from '#composite/data'; + +const composite = compositeFrom({ + compose: false, + + steps: [ + withPropertiesFromObject({ + object: 'object', + properties: 'properties', + }), + + exposeDependency({dependency: '#object'}), + ], +}); + +t.test(`withPropertiesFromObject: basic behavior`, t => { + t.plan(4); + + 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.annotateError.indexInSourceArray')]: 2, + message: `Error at zero-index 2: 123`, + cause: { + message: `Expected a string, got number`, + }, + }, + ]}, + ]}); +}); + +t.test(`withPropertiesFromObject: validate dynamic inputs`, t => { + t.plan(2); + + t.throws( + () => composite.expose.compute({ + object: 'intriguing', + properties: 'onceMore', + }), + {message: `Error computing composition`, cause: + {message: `Error computing composition withPropertiesFromObject`, cause: + {message: `Errors in input values provided to withPropertiesFromObject`, errors: [ + {message: `object: Expected an object, got string`}, + {message: `properties: Expected an array, got string`}, + ]}}}); + + t.throws( + () => composite.expose.compute({ + object: [['abc', 1], ['def', 2], [123, 3]], + properties: ['abc', 'def', 123], + }), + {message: `Error computing composition`, cause: + {message: `Error computing composition withPropertiesFromObject`, cause: + {message: `Errors in input values provided to withPropertiesFromObject`, errors: [ + {message: `object: Expected an object, got array`}, + {message: `properties: Errors validating array items`, errors: [ + { + [Symbol.for('hsmusic.annotateError.indexInSourceArray')]: 2, + message: `Error at zero-index 2: 123`, + cause: { + message: `Expected a string, got number`, + }, + }, + ]}, + ]}}}); +}); diff --git a/test/unit/data/composite/data/withPropertyFromObject.js b/test/unit/data/composite/data/withPropertyFromObject.js new file mode 100644 index 0000000..6a772c3 --- /dev/null +++ b/test/unit/data/composite/data/withPropertyFromObject.js @@ -0,0 +1,122 @@ +import t from 'tap'; + +import {compositeFrom, input} from '#composite'; +import {exposeDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +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/data/withUniqueItemsOnly.js b/test/unit/data/composite/data/withUniqueItemsOnly.js new file mode 100644 index 0000000..965b14b --- /dev/null +++ b/test/unit/data/composite/data/withUniqueItemsOnly.js @@ -0,0 +1,84 @@ +import t from 'tap'; + +import {compositeFrom, input} from '#composite'; +import {exposeDependency} from '#composite/control-flow'; +import {withUniqueItemsOnly} from '#composite/data'; + +t.test(`withUniqueItemsOnly: basic behavior`, t => { + t.plan(3); + + const composite = compositeFrom({ + compose: false, + + steps: [ + withUniqueItemsOnly({ + list: 'list', + }), + + exposeDependency({dependency: '#list'}), + ], + }); + + t.match(composite, { + expose: { + dependencies: ['list'], + }, + }); + + t.same(composite.expose.compute({ + list: ['apple', 'banana', 'banana', 'banana', 'apple', 'watermelon'], + }), ['apple', 'banana', 'watermelon']); + + t.same(composite.expose.compute({ + list: [], + }), []); +}); + +t.test(`withUniqueItemsOnly: output shapes & values`, t => { + t.plan(2 * 3 ** 1); + + const dependencies = { + ['list_dependency']: + [1, 1, 2, 3, 3, 4, 'foo', false, false, 4], + [input('list_neither')]: + [8, 8, 7, 6, 6, 5, 'bar', true, true, 5], + }; + + const mapLevel1 = [ + ['list_dependency', { + '#list_dependency': [1, 2, 3, 4, 'foo', false], + }], + [input.value([-1, -1, 'interesting', 'very', 'interesting']), { + '#uniqueItems': [-1, 'interesting', 'very'], + }], + [input('list_neither'), { + '#uniqueItems': [8, 7, 6, 5, 'bar', true], + }], + ]; + + for (const [listInput, outputDict] of mapLevel1) { + const step = withUniqueItemsOnly({ + list: listInput, + }); + + 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); + } +}); |