| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- import {expect} from 'chai';
- import {format} from '@e22m4u/js-format';
- import {projectData} from './project-data.js';
- describe('projectData', function () {
- it('should require the parameter "schemaOrName" to be a non-empty string or an object', function () {
- const resolver = () => ({});
- const throwable = v => () => projectData(v, {}, {resolver});
- const error = s =>
- format(
- 'Projection schema must be an Object or a non-empty String ' +
- 'that represents a schema name, 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(null)).to.throw(error('null'));
- expect(throwable(undefined)).to.throw(error('undefined'));
- throwable('mySchema')();
- throwable({})();
- });
- it('should require the parameter "options" to be an object', function () {
- const throwable = v => () => projectData({}, {}, v);
- const error = s =>
- format('Parameter "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 option "strict" to be a boolean', function () {
- const throwable = v => () => projectData({}, {}, {strict: v});
- const error = s =>
- format('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 option "scope" to be a non-empty string', function () {
- const throwable = v => () => projectData({}, {}, {scope: v});
- const error = s =>
- format('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 option "resolver" to be a function', function () {
- const throwable = v => () => projectData({}, {}, {resolver: v});
- const error = s =>
- format('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(() => undefined)();
- throwable(undefined)();
- });
- it('should throw an error if no resolver specified when a schema name is provided', function () {
- expect(() => projectData('mySchema', {})).to.throw(
- 'Unable to resolve the named schema "mySchema" without ' +
- 'a specified projection schema resolver.',
- );
- });
- it('should throw an error if the schema resolver returns an invalid value', function () {
- const throwable = v => () =>
- projectData('mySchema', {}, {resolver: () => v});
- const error = s =>
- format(
- 'Projection 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(null)).to.throw(error('null'));
- expect(throwable(undefined)).to.throw(error('undefined'));
- throwable({})();
- });
- it('should return a non-object and non-array data as is', function () {
- const fn = v => projectData({foo: true}, v);
- expect(fn('str')).to.be.eql('str');
- expect(fn('')).to.be.eql('');
- expect(fn(10)).to.be.eql(10);
- expect(fn(0)).to.be.eql(0);
- expect(fn(true)).to.be.eql(true);
- expect(fn(false)).to.be.eql(false);
- expect(fn(null)).to.be.eql(null);
- expect(fn(undefined)).to.be.eql(undefined);
- });
- it('should apply projection for each array element', function () {
- const schema = {foo: true, bar: false};
- const data = [
- {foo: 10, bar: 20, baz: 30},
- {foo: 40, bar: 50, baz: 60},
- ];
- const res = projectData(schema, data);
- expect(res).to.be.eql([
- {foo: 10, baz: 30},
- {foo: 40, baz: 60},
- ]);
- });
- it('should add fields without rules by default', function () {
- const res = projectData({}, {foo: 10, bar: 20});
- expect(res).to.be.eql({foo: 10, bar: 20});
- });
- it('should project fields by a boolean value', function () {
- const res = projectData({foo: true, bar: false}, {foo: 10, bar: 20});
- expect(res).to.be.eql({foo: 10});
- });
- it('should project fields 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-related rules by default', function () {
- const res = projectData(
- {foo: {scopes: {input: false, output: false}}},
- {foo: 10},
- );
- expect(res).to.be.eql({foo: 10});
- });
- it('should create nested projection by the schema option', function () {
- const res = projectData(
- {foo: true, bar: false, qux: {schema: {abc: true, def: false}}},
- {foo: 10, bar: 20, qux: {abc: 30, def: 40}},
- );
- expect(res).to.be.eql({foo: 10, qux: {abc: 30}});
- });
- describe('schema name', function () {
- it('should pass the schema name to the schema resolver', function () {
- let invoked = 0;
- const resolver = name => {
- expect(name).to.be.eq('mySchema');
- invoked++;
- 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 use the schema resolver in the nested schema', function () {
- let invoked = 0;
- const resolver = name => {
- expect(name).to.be.eq('mySchema');
- invoked++;
- return {baz: true, qux: false};
- };
- const res = projectData(
- {foo: true, bar: {schema: 'mySchema'}},
- {foo: 10, bar: {baz: 20, qux: 30}},
- {resolver},
- );
- expect(res).to.be.eql({foo: 10, bar: {baz: 20}});
- expect(invoked).to.be.eq(1);
- });
- });
- describe('strict mode', function () {
- it('should preserve fields not defined in the schema when the strict option is false', function () {
- const res = projectData({}, {foo: 10});
- expect(res).to.be.eql({foo: 10});
- });
- it('should remove fields without rules when the strict mode is enabled', function () {
- const res = projectData({}, {foo: 10}, {strict: true});
- expect(res).to.be.eql({});
- });
- it('should project fields by a boolean value', function () {
- const res = projectData(
- {foo: true, bar: false},
- {foo: 10, bar: 20},
- {strict: true},
- );
- expect(res).to.be.eql({foo: 10});
- });
- it('should project fields by the select option', function () {
- const res = projectData(
- {foo: {select: true}, bar: {select: false}},
- {foo: 10, bar: 20},
- {strict: true},
- );
- expect(res).to.be.eql({foo: 10});
- });
- it('should propagate the strict mode to nested schema', function () {
- const res = projectData(
- {foo: false, bar: {select: true, schema: {baz: true}}},
- {foo: 10, bar: {baz: 20, qux: 30}},
- {strict: true},
- );
- expect(res).to.be.eql({bar: {baz: 20}});
- });
- });
- describe('projection scope', function () {
- it('should apply scope-specific selection rule by a boolean value', function () {
- const schema = {
- foo: {
- select: false,
- scopes: {
- input: true,
- },
- },
- bar: true,
- };
- const data = {foo: 10, bar: 20};
- const res1 = projectData(schema, data);
- const res2 = projectData(schema, data, {scope: 'input'});
- expect(res1).to.be.eql({bar: 20});
- expect(res2).to.be.eql({foo: 10, bar: 20});
- });
- it('should apply scope-specific selection rule by the select option', function () {
- const schema = {
- foo: {
- select: false,
- scopes: {
- input: {select: true},
- },
- },
- bar: {select: true},
- };
- const data = {foo: 10, bar: 20};
- const res1 = projectData(schema, data);
- const res2 = projectData(schema, data, {scope: 'input'});
- expect(res1).to.be.eql({bar: 20});
- expect(res2).to.be.eql({foo: 10, bar: 20});
- });
- it('should fallback to general rule if scope rule is missing', function () {
- const schema = {
- foo: {
- select: true,
- scopes: {
- output: {select: false},
- },
- },
- };
- const data = {foo: 10};
- const res = projectData(schema, data, {scope: 'input'});
- expect(res).to.be.eql({foo: 10});
- });
- it('should fallback to the general rule if the scope options exists but lacks the select option', function () {
- const schema = {
- foo: {
- select: true,
- scopes: {
- input: {},
- },
- },
- bar: {
- select: false,
- scopes: {
- input: {},
- },
- },
- };
- const data = {foo: 10, bar: 20};
- const res = projectData(schema, data, {scope: 'input'});
- expect(res).to.be.eql({foo: 10});
- });
- });
- });
|