| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- import {expect} from 'chai';
- import {format} from '@e22m4u/js-format';
- import {projectData} from './project-data.js';
- describe('projectData', function () {
- it('should require the options argument to be an object', function () {
- const throwable = v => () => projectData({}, 10, v);
- const error = s =>
- format('Projection options must be an Object, but %s was given.', s);
- expect(throwable('str')).to.throw(error('"str"'));
- expect(throwable('')).to.throw(error('""'));
- expect(throwable(10)).to.throw(error('10'));
- expect(throwable(0)).to.throw(error('0'));
- expect(throwable(true)).to.throw(error('true'));
- expect(throwable(false)).to.throw(error('false'));
- expect(throwable([])).to.throw(error('Array'));
- expect(throwable(null)).to.throw(error('null'));
- throwable({})();
- throwable(undefined)();
- });
- it('should require the strict option to be a boolean', function () {
- const throwable = v => () => projectData({}, 10, {strict: v});
- const error = s =>
- format(
- 'Projection option "strict" must be a Boolean, but %s was given.',
- s,
- );
- expect(throwable('str')).to.throw(error('"str"'));
- expect(throwable('')).to.throw(error('""'));
- expect(throwable(10)).to.throw(error('10'));
- expect(throwable(0)).to.throw(error('0'));
- expect(throwable([])).to.throw(error('Array'));
- expect(throwable({})).to.throw(error('Object'));
- expect(throwable(null)).to.throw(error('null'));
- throwable(true)();
- throwable(false)();
- throwable(undefined)();
- });
- it('should require the scope option to be a non-empty string', function () {
- const throwable = v => () => projectData({}, 10, {scope: v});
- const error = s =>
- format(
- 'Projection option "scope" must be a non-empty String, ' +
- 'but %s was given.',
- s,
- );
- expect(throwable('')).to.throw(error('""'));
- expect(throwable(10)).to.throw(error('10'));
- expect(throwable(0)).to.throw(error('0'));
- expect(throwable(true)).to.throw(error('true'));
- expect(throwable(false)).to.throw(error('false'));
- expect(throwable([])).to.throw(error('Array'));
- expect(throwable({})).to.throw(error('Object'));
- expect(throwable(null)).to.throw(error('null'));
- throwable('str')();
- throwable(undefined)();
- });
- it('should require the resolver option to be a function', function () {
- const throwable = v => () => projectData({}, 10, {resolver: v});
- const error = s =>
- format(
- 'Projection option "resolver" must be a Function, but %s was given.',
- s,
- );
- expect(throwable('str')).to.throw(error('"str"'));
- expect(throwable('')).to.throw(error('""'));
- expect(throwable(10)).to.throw(error('10'));
- expect(throwable(0)).to.throw(error('0'));
- expect(throwable(true)).to.throw(error('true'));
- expect(throwable(false)).to.throw(error('false'));
- expect(throwable([])).to.throw(error('Array'));
- expect(throwable({})).to.throw(error('Object'));
- expect(throwable(null)).to.throw(error('null'));
- throwable(() => ({}))();
- throwable(undefined)();
- });
- it('should project with the schema factory', function () {
- let invoked = 0;
- const factory = () => {
- invoked++;
- return {foo: true, bar: false};
- };
- const res = projectData(factory, {foo: 10, bar: 20});
- expect(res).to.be.eql({foo: 10});
- expect(invoked).to.be.eq(1);
- });
- it('should require a factory value to be an object or a non-empty string', function () {
- const throwable = v => () => projectData(() => v, {});
- const error = s =>
- format(
- 'Schema factory must return an Object ' +
- 'or a non-empty String, but %s was given.',
- s,
- );
- expect(throwable('')).to.throw(error('""'));
- expect(throwable(10)).to.throw(error('10'));
- expect(throwable(0)).to.throw(error('0'));
- expect(throwable(true)).to.throw(error('true'));
- expect(throwable(false)).to.throw(error('false'));
- expect(throwable([])).to.throw(error('Array'));
- expect(throwable(undefined)).to.throw(error('undefined'));
- expect(throwable(null)).to.throw(error('null'));
- expect(throwable(() => undefined)).to.throw(error('Function'));
- projectData(() => ({}), {});
- projectData(() => 'str', {}, {resolver: () => ({})});
- });
- it('should resolve the schema name by the given resolver', function () {
- let invoked = 0;
- const resolver = name => {
- invoked++;
- expect(name).to.be.eql('mySchema');
- return {foo: true, bar: false};
- };
- const res = projectData('mySchema', {foo: 10, bar: 20}, {resolver});
- expect(res).to.be.eql({foo: 10});
- expect(invoked).to.be.eq(1);
- });
- it('should require the resolver option when the schema name is given', function () {
- const throwable = () => projectData('mySchema', {});
- expect(throwable).to.throw(
- 'Projection option "resolver" is required to resolve "mySchema" schema.',
- );
- });
- it('should require a resolver result to be an object', function () {
- const throwable = v => () =>
- projectData('mySchema', {}, {resolver: () => v});
- const error = s =>
- format('Schema resolver must return an Object, but %s was given.', s);
- expect(throwable('str')).to.throw(error('"str"'));
- expect(throwable('')).to.throw(error('""'));
- expect(throwable(10)).to.throw(error('10'));
- expect(throwable(0)).to.throw(error('0'));
- expect(throwable(true)).to.throw(error('true'));
- expect(throwable(false)).to.throw(error('false'));
- expect(throwable([])).to.throw(error('Array'));
- expect(throwable(undefined)).to.throw(error('undefined'));
- expect(throwable(null)).to.throw(error('null'));
- expect(throwable(() => undefined)).to.throw(error('Function'));
- throwable({})();
- });
- it('should resolver the schema name from the factory function', function () {
- let invoked = 0;
- const resolver = name => {
- invoked++;
- expect(name).to.be.eql('mySchema');
- return {foo: true, bar: false};
- };
- const res = projectData(() => 'mySchema', {foo: 10, bar: 20}, {resolver});
- expect(res).to.be.eql({foo: 10});
- expect(invoked).to.be.eq(1);
- });
- it('should resolve the named schema in the the nested object', function () {
- let invoked = 0;
- const resolver = name => {
- invoked++;
- if (name === 'schema1') {
- return {foo: true, bar: {schema: 'schema2'}};
- } else if (name === 'schema2') {
- return {baz: true, qux: false};
- }
- };
- const data = {foo: 10, bar: {baz: 20, qux: 30}};
- const res = projectData('schema1', data, {resolver});
- expect(res).to.be.eql({foo: 10, bar: {baz: 20}});
- expect(invoked).to.be.eq(2);
- });
- it('should validate the given schema in the shallow mode', function () {
- const schema1 = {foo: '?'};
- const schema2 = {foo: true, bar: {schema: {baz: '?'}}};
- expect(() => projectData(schema1, {foo: 10})).to.throw(
- 'Property options must be an Object or a Boolean, but "?" was given.',
- );
- const res = projectData(schema2, {foo: 10});
- expect(res).to.be.eql({foo: 10});
- expect(() => projectData(schema2, {bar: {baz: 20}})).to.throw(
- 'Property options must be an Object or a Boolean, but "?" was given.',
- );
- });
- it('should return primitive value as is', function () {
- expect(projectData({}, 'str')).to.be.eq('str');
- expect(projectData({}, '')).to.be.eq('');
- expect(projectData({}, 10)).to.be.eq(10);
- expect(projectData({}, 0)).to.be.eq(0);
- expect(projectData({}, true)).to.be.eq(true);
- expect(projectData({}, false)).to.be.eq(false);
- expect(projectData({}, undefined)).to.be.eq(undefined);
- expect(projectData({}, null)).to.be.eq(null);
- });
- it('should project an array items', function () {
- const list = [{foo: 10, bar: 20, baz: 30}, {qux: 30}];
- const expectedList = [{foo: 10, baz: 30}, {qux: 30}];
- const res = projectData({foo: true, bar: false}, list);
- expect(res).to.be.eql(expectedList);
- });
- it('should project an array items in the strict mode', function () {
- const list = [{foo: 10, bar: 20, baz: 30}, {qux: 30}];
- const expectedList = [{foo: 10}, {}];
- const res = projectData({foo: true, bar: false}, list, {strict: true});
- expect(res).to.be.eql(expectedList);
- });
- it('should exclude properties without rules when the strict mode is enabled', function () {
- const res = projectData(
- {foo: true, bar: false},
- {foo: 10, bar: 20, baz: 30},
- {strict: true},
- );
- expect(res).to.be.eql({foo: 10});
- });
- it('should ignore prototype properties', function () {
- const data = Object.create({baz: 30});
- data.foo = 10;
- data.bar = 20;
- expect(data).to.be.eql({foo: 10, bar: 20, baz: 30});
- const res = projectData({foo: true, bar: false}, data);
- expect(res).to.be.eql({foo: 10});
- });
- it('should project the property by a boolean rule', function () {
- const res = projectData({foo: true, bar: false}, {foo: 10, bar: 20});
- expect(res).to.be.eql({foo: 10});
- });
- it('should project the property by the select option', function () {
- const res = projectData(
- {foo: {select: true}, bar: {select: false}},
- {foo: 10, bar: 20},
- );
- expect(res).to.be.eql({foo: 10});
- });
- it('should ignore scope options when no active scope is provided', function () {
- const schema = {
- foo: {select: true, scopes: {input: false}},
- bar: {select: false, scopes: {output: true}},
- };
- const res = projectData(schema, {foo: 10, bar: 20});
- expect(res).to.be.eql({foo: 10});
- });
- it('should project the active scope by the boolean rule', function () {
- const schema = {
- foo: {scopes: {input: true}},
- bar: {scopes: {input: false}},
- };
- const res = projectData(schema, {foo: 10, bar: 20}, {scope: 'input'});
- expect(res).to.be.eql({foo: 10});
- });
- it('should project the active scope by the select option', function () {
- const schema = {
- foo: {scopes: {input: {select: true}}},
- bar: {scopes: {input: {select: false}}},
- };
- const res = projectData(schema, {foo: 10, bar: 20}, {scope: 'input'});
- expect(res).to.be.eql({foo: 10});
- });
- it('should prioritize the scope rule over the general options', function () {
- const schema = {
- foo: {select: false, scopes: {input: true}},
- bar: {select: true, scopes: {input: false}},
- };
- const res = projectData(schema, {foo: 10, bar: 20}, {scope: 'input'});
- expect(res).to.be.eql({foo: 10});
- });
- it('should ignore scope options not matched with the active scope', function () {
- const schema = {
- foo: {scopes: {input: true, output: false}},
- bar: {scopes: {input: false, output: true}},
- };
- const res = projectData(schema, {foo: 10, bar: 20}, {scope: 'input'});
- expect(res).to.be.eql({foo: 10});
- });
- it('should exclude properties without selection rule in the strict mode when the active scope is provided', function () {
- const schema = {
- foo: {scopes: {input: true}},
- bar: {scopes: {input: false}},
- baz: {scopes: {output: true}},
- };
- const data = {foo: 10, bar: 20, baz: 30, qux: 40};
- const res = projectData(schema, data, {strict: true, scope: 'input'});
- expect(res).to.be.eql({foo: 10});
- });
- it('should prioritize the scope options over the general options in the strict mode', function () {
- const schema = {
- foo: {select: false, scopes: {input: true}},
- bar: {select: false, scopes: {input: {select: true}}},
- };
- const data = {foo: 10, bar: 20, baz: 30};
- const res = projectData(schema, data, {strict: true, scope: 'input'});
- expect(res).to.be.eql({foo: 10, bar: 20});
- });
- it('should project the nested object by the given schema', function () {
- const schema = {
- foo: true,
- bar: {schema: {baz: true, qux: false}},
- };
- const data = {foo: 10, bar: {baz: 20, qux: 30, buz: 40}};
- const res = projectData(schema, data, {scope: 'input'});
- expect(res).to.be.eql({foo: 10, bar: {baz: 20, buz: 40}});
- });
- it('should exclude nested properties without rules in the strict mode', function () {
- const schema = {
- foo: true,
- bar: {select: true, schema: {baz: true, qux: false}},
- };
- const data = {foo: 10, bar: {baz: 20, qux: 30, buz: 40}};
- const res = projectData(schema, data, {strict: true, scope: 'input'});
- expect(res).to.be.eql({foo: 10, bar: {baz: 20}});
- });
- });
|