|
|
@@ -3,8 +3,8 @@ 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);
|
|
|
+ it('should require the "options" parameter to be an Object', function () {
|
|
|
+ const throwable = v => () => projectData(10, true, v);
|
|
|
const error = s =>
|
|
|
format('Projection options must be an Object, but %s was given.', s);
|
|
|
expect(throwable('str')).to.throw(error('"str"'));
|
|
|
@@ -19,8 +19,8 @@ describe('projectData', function () {
|
|
|
throwable(undefined)();
|
|
|
});
|
|
|
|
|
|
- it('should require the "keepUnknown" option to be a boolean', function () {
|
|
|
- const throwable = v => () => projectData(10, {}, {keepUnknown: v});
|
|
|
+ it('should require the "keepUnknown" option to be a Boolean', function () {
|
|
|
+ const throwable = v => () => projectData(10, true, {keepUnknown: v});
|
|
|
const error = s =>
|
|
|
format(
|
|
|
'Projection option "keepUnknown" must be a Boolean, but %s was given.',
|
|
|
@@ -38,28 +38,8 @@ describe('projectData', function () {
|
|
|
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 "nameResolver" option to be a function', function () {
|
|
|
- const throwable = v => () => projectData(10, {}, {nameResolver: v});
|
|
|
+ it('should require the "nameResolver" option to be a Function', function () {
|
|
|
+ const throwable = v => () => projectData(10, true, {nameResolver: v});
|
|
|
const error = s =>
|
|
|
format(
|
|
|
'Projection option "nameResolver" must be ' +
|
|
|
@@ -80,7 +60,7 @@ describe('projectData', function () {
|
|
|
});
|
|
|
|
|
|
it('should require the "factoryArgs" option to be an Array', function () {
|
|
|
- const throwable = v => () => projectData(10, {}, {factoryArgs: v});
|
|
|
+ const throwable = v => () => projectData(10, true, {factoryArgs: v});
|
|
|
const error = s =>
|
|
|
format(
|
|
|
'Projection option "factoryArgs" must be an Array, but %s was given.',
|
|
|
@@ -98,449 +78,366 @@ describe('projectData', function () {
|
|
|
throwable([])();
|
|
|
});
|
|
|
|
|
|
- it('should resolve a factory value as the schema object', function () {
|
|
|
+ it('should resolve a boolean value from the schema factory', function () {
|
|
|
let invoked = 0;
|
|
|
const factory = () => {
|
|
|
invoked++;
|
|
|
- return {foo: true, bar: false};
|
|
|
+ return true;
|
|
|
};
|
|
|
const res = projectData({foo: 10, bar: 20}, factory);
|
|
|
- expect(res).to.be.eql({foo: 10});
|
|
|
+ expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
expect(invoked).to.be.eq(1);
|
|
|
});
|
|
|
|
|
|
- it('should pass the "factoryArgs" option to the schema factory', function () {
|
|
|
+ it('should resolve a schema object from the schema factory', function () {
|
|
|
let invoked = 0;
|
|
|
- const factoryArgs = [1, 2, 3];
|
|
|
- const factory = (...args) => {
|
|
|
+ const factory = () => {
|
|
|
invoked++;
|
|
|
- expect(args).to.be.eql(factoryArgs);
|
|
|
return {foo: true, bar: false};
|
|
|
};
|
|
|
- const res = projectData({foo: 10, bar: 20}, factory, {factoryArgs});
|
|
|
+ const res = projectData({foo: 10, bar: 20}, factory);
|
|
|
expect(res).to.be.eql({foo: 10});
|
|
|
expect(invoked).to.be.eq(1);
|
|
|
});
|
|
|
|
|
|
- it('should pass the "factoryArgs" option to the schema factory in the nested schema', function () {
|
|
|
+ it('should resolve a named schema from the schema factory', function () {
|
|
|
+ let factoryInvoked = 0;
|
|
|
+ const factory = () => {
|
|
|
+ factoryInvoked++;
|
|
|
+ return 'mySchema';
|
|
|
+ };
|
|
|
+ let resolverInvoked = 0;
|
|
|
+ const nameResolver = name => {
|
|
|
+ resolverInvoked++;
|
|
|
+ expect(name).to.be.eq('mySchema');
|
|
|
+ return {foo: true, bar: false};
|
|
|
+ };
|
|
|
+ const res = projectData({foo: 10, bar: 20}, factory, {nameResolver});
|
|
|
+ expect(res).to.be.eql({foo: 10});
|
|
|
+ expect(factoryInvoked).to.be.eq(1);
|
|
|
+ expect(resolverInvoked).to.be.eq(1);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('should pass the "factoryArgs" option to the schema factory', function () {
|
|
|
let invoked = 0;
|
|
|
const factoryArgs = [1, 2, 3];
|
|
|
const factory = (...args) => {
|
|
|
invoked++;
|
|
|
expect(args).to.be.eql(factoryArgs);
|
|
|
- return {bar: true, baz: false};
|
|
|
+ return {foo: true, bar: false};
|
|
|
};
|
|
|
- const res = projectData(
|
|
|
- {foo: {bar: 10, baz: 20}},
|
|
|
- {foo: {schema: factory}},
|
|
|
- {factoryArgs},
|
|
|
- );
|
|
|
- expect(res).to.be.eql({foo: {bar: 10}});
|
|
|
+ const res = projectData({foo: 10, bar: 20}, factory, {factoryArgs});
|
|
|
+ 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);
|
|
|
+ it('should require a factory value to be a valid schema', function () {
|
|
|
+ const nameResolver = () => ({});
|
|
|
+ const throwable = v => () => projectData({}, () => v, {nameResolver});
|
|
|
const error = s =>
|
|
|
format(
|
|
|
- 'Schema factory must return an Object ' +
|
|
|
+ 'Schema factory must return a Boolean, 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', {nameResolver: () => ({})});
|
|
|
- });
|
|
|
-
|
|
|
- it('should resolve a schema name by the name resolver', function () {
|
|
|
- let invoked = 0;
|
|
|
- const nameResolver = name => {
|
|
|
- invoked++;
|
|
|
- expect(name).to.be.eql('mySchema');
|
|
|
- return {foo: true, bar: false};
|
|
|
- };
|
|
|
- const res = projectData({foo: 10, bar: 20}, 'mySchema', {nameResolver});
|
|
|
- expect(res).to.be.eql({foo: 10});
|
|
|
- expect(invoked).to.be.eq(1);
|
|
|
+ expect(throwable(() => 'mySchema')).to.throw(error('Function'));
|
|
|
+ throwable('str')();
|
|
|
+ throwable(true)();
|
|
|
+ throwable(false)();
|
|
|
+ throwable({})();
|
|
|
});
|
|
|
|
|
|
- it('should require the "nameResolver" option when a schema name is provided', function () {
|
|
|
+ it('should throw an error if no name resolver is provided for the schema name', function () {
|
|
|
const throwable = () => projectData({}, 'mySchema');
|
|
|
expect(throwable).to.throw(
|
|
|
- 'Projection option "nameResolver" is required ' +
|
|
|
- 'to resolve "mySchema" name.',
|
|
|
+ 'Failed to resolve the schema name "mySchema" because ' +
|
|
|
+ 'the option "nameResolver" is missing.',
|
|
|
);
|
|
|
});
|
|
|
|
|
|
- it('should require the name resolver to return an object', function () {
|
|
|
- const throwable = v => () =>
|
|
|
- projectData({}, 'mySchema', {nameResolver: () => v});
|
|
|
- const error = s =>
|
|
|
- format(
|
|
|
- 'Name resolver must return an Object, a Function ' +
|
|
|
- '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'));
|
|
|
- throwable({})();
|
|
|
- });
|
|
|
-
|
|
|
- it('should resolve a schema name from the factory function', function () {
|
|
|
+ it('should resolve a named schema with a boolean value', function () {
|
|
|
let invoked = 0;
|
|
|
const nameResolver = name => {
|
|
|
+ expect(name).to.be.eq('mySchema');
|
|
|
invoked++;
|
|
|
- expect(name).to.be.eql('mySchema');
|
|
|
- return {foo: true, bar: false};
|
|
|
+ return true;
|
|
|
};
|
|
|
- const res = projectData({foo: 10, bar: 20}, () => 'mySchema', {
|
|
|
- nameResolver,
|
|
|
- });
|
|
|
- expect(res).to.be.eql({foo: 10});
|
|
|
+ const res = projectData({foo: 10, bar: 20}, 'mySchema', {nameResolver});
|
|
|
+ expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
expect(invoked).to.be.eq(1);
|
|
|
});
|
|
|
|
|
|
- it('should resolve a schema name in the nested object', function () {
|
|
|
+ it('should resolve a named schema with a schema object', function () {
|
|
|
let invoked = 0;
|
|
|
const nameResolver = name => {
|
|
|
+ expect(name).to.be.eq('mySchema');
|
|
|
invoked++;
|
|
|
- if (name === 'schema1') {
|
|
|
- return {foo: true, bar: {schema: 'schema2'}};
|
|
|
- } else if (name === 'schema2') {
|
|
|
- return {baz: true, qux: false};
|
|
|
- }
|
|
|
+ return {foo: true, bar: false};
|
|
|
};
|
|
|
- const data = {foo: 10, bar: {baz: 20, qux: 30}};
|
|
|
- const res = projectData(data, 'schema1', {nameResolver});
|
|
|
- expect(res).to.be.eql({foo: 10, bar: {baz: 20}});
|
|
|
- expect(invoked).to.be.eq(2);
|
|
|
+ const res = projectData({foo: 10, bar: 20}, 'mySchema', {nameResolver});
|
|
|
+ expect(res).to.be.eql({foo: 10});
|
|
|
+ expect(invoked).to.be.eq(1);
|
|
|
});
|
|
|
|
|
|
- it('should resolve the schema object through the name chain', function () {
|
|
|
+ it('should resolve a named schema with a factory function', function () {
|
|
|
let invoked = 0;
|
|
|
const nameResolver = name => {
|
|
|
+ expect(name).to.be.eq('mySchema');
|
|
|
invoked++;
|
|
|
- if (name === 'schema1') {
|
|
|
- return 'schema2';
|
|
|
- } else if (name === 'schema2') {
|
|
|
- return {foo: true, bar: false};
|
|
|
- }
|
|
|
- throw new Error('Invalid argument.');
|
|
|
+ return () => ({foo: true, bar: false});
|
|
|
};
|
|
|
- const res = projectData({foo: 10, bar: 20}, 'schema1', {nameResolver});
|
|
|
+ const res = projectData({foo: 10, bar: 20}, 'mySchema', {nameResolver});
|
|
|
expect(res).to.be.eql({foo: 10});
|
|
|
- expect(invoked).to.be.eq(2);
|
|
|
+ expect(invoked).to.be.eq(1);
|
|
|
});
|
|
|
|
|
|
- it('should validate a resolved value from the name chain', function () {
|
|
|
+ it('should require a resolved schema to be a valid value', function () {
|
|
|
const nameResolver = v => name => {
|
|
|
- if (name === 'schema1') {
|
|
|
- return 'schema2';
|
|
|
- } else if (name === 'schema2') {
|
|
|
+ if (name === 'mySchema') {
|
|
|
return v;
|
|
|
- } else if (name === 'schema3') {
|
|
|
+ } else if (name === 'nestedSchema') {
|
|
|
return {};
|
|
|
}
|
|
|
- throw new Error('Invalid argument.');
|
|
|
+ throw new Error('Unknown schema');
|
|
|
};
|
|
|
const throwable = v => () =>
|
|
|
- projectData({foo: 10, bar: 20}, 'schema1', {
|
|
|
- nameResolver: nameResolver(v),
|
|
|
- });
|
|
|
+ projectData({}, 'mySchema', {nameResolver: nameResolver(v)});
|
|
|
const error = s =>
|
|
|
format(
|
|
|
- 'Name resolver must return an Object, a Function ' +
|
|
|
+ 'Name resolver must return a Boolean, an Object, a Function ' +
|
|
|
'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'));
|
|
|
- throwable('schema3')();
|
|
|
+ throwable('nestedSchema')();
|
|
|
+ throwable(true);
|
|
|
+ throwable(false);
|
|
|
+ throwable({});
|
|
|
+ throwable(() => 'nestedSchema');
|
|
|
+ throwable(() => true);
|
|
|
+ throwable(() => false);
|
|
|
+ throwable(() => ({}));
|
|
|
});
|
|
|
|
|
|
- it('should resolve schema names through the factory function', function () {
|
|
|
+ it('should resolve a factory function from a registered schema', function () {
|
|
|
let invoked = 0;
|
|
|
const nameResolver = name => {
|
|
|
invoked++;
|
|
|
- if (name === 'schema1') {
|
|
|
- return () => 'schema2';
|
|
|
- } else if (name === 'schema2') {
|
|
|
+ if (name === 'schemaA') {
|
|
|
+ return () => 'schemaB';
|
|
|
+ } else if (name === 'schemaB') {
|
|
|
return {foo: true, bar: false};
|
|
|
}
|
|
|
- throw new Error('Invalid argument.');
|
|
|
+ throw new Error('Unknown schema');
|
|
|
};
|
|
|
- const res = projectData({foo: 10, bar: 20}, 'schema1', {nameResolver});
|
|
|
+ const res = projectData({foo: 10, bar: 20}, 'schemaA', {nameResolver});
|
|
|
expect(res).to.be.eql({foo: 10});
|
|
|
expect(invoked).to.be.eq(2);
|
|
|
});
|
|
|
|
|
|
- it('should validate a return value of the factory resolved through the name chain', function () {
|
|
|
- const nameResolver = v => name => {
|
|
|
- if (name === 'schema1') {
|
|
|
- return 'schema2';
|
|
|
- } else if (name === 'schema2') {
|
|
|
- return () => v;
|
|
|
- } else if (name === 'schema3') {
|
|
|
- return {};
|
|
|
+ it('should resolve a schema name from another registered schema', function () {
|
|
|
+ let invoked = 0;
|
|
|
+ const nameResolver = name => {
|
|
|
+ invoked++;
|
|
|
+ if (name === 'schemaA') {
|
|
|
+ return 'schemaB';
|
|
|
+ } else if (name === 'schemaB') {
|
|
|
+ return {foo: true, bar: false};
|
|
|
}
|
|
|
- throw new Error('Invalid argument.');
|
|
|
+ throw new Error('Unknown schema');
|
|
|
};
|
|
|
- const throwable = v => () =>
|
|
|
- projectData({foo: 10, bar: 20}, 'schema1', {
|
|
|
- nameResolver: nameResolver(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'));
|
|
|
- throwable('schema3')();
|
|
|
- });
|
|
|
-
|
|
|
- it('should validate the given schema in the shallow mode', function () {
|
|
|
- const schema1 = {foo: '?'};
|
|
|
- const schema2 = {foo: true, bar: {schema: {baz: '?'}}};
|
|
|
- expect(() => projectData({foo: 10}, schema1)).to.throw(
|
|
|
- 'Property options must be an Object or a Boolean, but "?" was given.',
|
|
|
- );
|
|
|
- const res = projectData({foo: 10}, schema2);
|
|
|
- expect(res).to.be.eql({foo: 10});
|
|
|
- expect(() => projectData({bar: {baz: 20}}, schema2)).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 include a property defined with an empty object', function () {
|
|
|
- const schema = {foo: {}, bar: {}};
|
|
|
- const data = {foo: 10, baz: 30};
|
|
|
- const res = projectData(data, schema);
|
|
|
+ const res = projectData({foo: 10, bar: 20}, 'schemaA', {nameResolver});
|
|
|
expect(res).to.be.eql({foo: 10});
|
|
|
+ expect(invoked).to.be.eq(2);
|
|
|
});
|
|
|
|
|
|
- it('should exclude a property when the logical rule is false', function () {
|
|
|
- const schema = {foo: false, bar: false};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema);
|
|
|
- expect(res).to.be.eql({});
|
|
|
- });
|
|
|
-
|
|
|
- it('should include a property when the logical rule is true', function () {
|
|
|
- const schema = {foo: true, bar: true};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema);
|
|
|
- expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
- });
|
|
|
-
|
|
|
- it('should exclude a property when the "select" option is false', function () {
|
|
|
- const schema = {foo: true, bar: {select: false}};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema);
|
|
|
- expect(res).to.be.eql({foo: 10});
|
|
|
+ it('should use a boolean schema as a visibility rule for a non-array value', function () {
|
|
|
+ const values = ['str', '', 10, 0, true, false, {p: 1}, {}, [1], []];
|
|
|
+ values.forEach(value => {
|
|
|
+ const res1 = projectData(value, true);
|
|
|
+ const res2 = projectData(value, false);
|
|
|
+ expect(res1).to.be.eql(value);
|
|
|
+ expect(res2).to.be.undefined;
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
- it('should include a property when the "select" option is true', function () {
|
|
|
- const schema = {foo: true, bar: {select: true}};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema);
|
|
|
- expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
+ it('should validate schema parameters of a schema object', function () {
|
|
|
+ const throwable = () => projectData({}, {prop: 10});
|
|
|
+ expect(throwable).to.throw(
|
|
|
+ 'Projection schema of the property "prop" must be a Boolean, ' +
|
|
|
+ 'an Object, a Function or a non-empty String, but 10 was given.',
|
|
|
+ );
|
|
|
});
|
|
|
|
|
|
- it('should include a property with a nested schema', function () {
|
|
|
- const schema = {user: {schema: {id: true, name: false}}};
|
|
|
- const data = {user: {id: 1, name: 'John Doe'}};
|
|
|
- const res = projectData(data, schema);
|
|
|
- expect(res).to.be.eql({user: {id: 1}});
|
|
|
+ it('should validate schema parameters of a factory value', function () {
|
|
|
+ const throwable = () => projectData({}, () => ({prop: 10}));
|
|
|
+ expect(throwable).to.throw(
|
|
|
+ 'Projection schema of the property "prop" must be a Boolean, ' +
|
|
|
+ 'an Object, a Function or a non-empty String, but 10 was given.',
|
|
|
+ );
|
|
|
});
|
|
|
|
|
|
- it('should exclude properties not defined in a given schema', function () {
|
|
|
- const res = projectData({foo: 10, bar: 20}, {});
|
|
|
- expect(res).to.be.eql({});
|
|
|
+ it('should validate schema parameters from a named schema', function () {
|
|
|
+ let invoked = 0;
|
|
|
+ const nameResolver = name => {
|
|
|
+ expect(name).to.be.eql('mySchema');
|
|
|
+ invoked++;
|
|
|
+ return {prop: 10};
|
|
|
+ };
|
|
|
+ const throwable = () => projectData({}, 'mySchema', {nameResolver});
|
|
|
+ expect(throwable).to.throw(
|
|
|
+ 'Projection schema of the property "prop" must be a Boolean, ' +
|
|
|
+ 'an Object, a Function or a non-empty String, but 10 was given.',
|
|
|
+ );
|
|
|
+ expect(invoked).to.be.eq(1);
|
|
|
});
|
|
|
|
|
|
- it('should project an array items by a given schema', function () {
|
|
|
- const list = [
|
|
|
- {foo: 10, bar: 20, baz: 30},
|
|
|
- {bar: 20, qux: 30},
|
|
|
- ];
|
|
|
- const expectedList = [{foo: 10}, {}];
|
|
|
- const res = projectData(list, {foo: true, bar: false});
|
|
|
- expect(res).to.be.eql(expectedList);
|
|
|
+ it('should return undefined and null as is when an object schema is given', function () {
|
|
|
+ expect(projectData(undefined, {})).to.be.undefined;
|
|
|
+ expect(projectData(null, {})).to.be.null;
|
|
|
});
|
|
|
|
|
|
- it('should include a property defined with an empty object in the keep unknown mode', function () {
|
|
|
- const schema = {foo: {}, bar: {}};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema, {keepUnknown: true});
|
|
|
- expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
+ it('should throw an error for a non-object value when an object schema is given', function () {
|
|
|
+ const throwable = v => () => projectData(v, {});
|
|
|
+ const error = s =>
|
|
|
+ format(
|
|
|
+ 'Data source of the object projection must be an Object or an Array, ' +
|
|
|
+ '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'));
|
|
|
});
|
|
|
|
|
|
- it('should exclude a property when the logical rule is false in the keep unknown mode', function () {
|
|
|
- const schema = {foo: false, bar: false};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema, {keepUnknown: true});
|
|
|
- expect(res).to.be.eql({});
|
|
|
+ it('should apply an object schema for each array item', function () {
|
|
|
+ const schema = {foo: true, bar: false};
|
|
|
+ const res = projectData(
|
|
|
+ [
|
|
|
+ {foo: 10, bar: 20},
|
|
|
+ {foo: 30, bar: 40},
|
|
|
+ ],
|
|
|
+ schema,
|
|
|
+ );
|
|
|
+ expect(res).to.be.eql([{foo: 10}, {foo: 30}]);
|
|
|
});
|
|
|
|
|
|
- it('should include a property when the logical rule is true in the keep unknown mode', function () {
|
|
|
- const schema = {foo: true, bar: true};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema, {keepUnknown: true});
|
|
|
- expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
+ it('should require the property "$keepUnknown" to be a Boolean', function () {
|
|
|
+ const throwable = v => () => projectData({}, {$keepUnknown: v});
|
|
|
+ const error1 = s =>
|
|
|
+ format(
|
|
|
+ 'Schema property "$keepUnknown" must be a Boolean, but %s was given.',
|
|
|
+ s,
|
|
|
+ );
|
|
|
+ const error2 = s =>
|
|
|
+ format(
|
|
|
+ 'Projection schema of the property "$keepUnknown" must be ' +
|
|
|
+ 'a Boolean, an Object, a Function or a non-empty String, ' +
|
|
|
+ 'but %s was given.',
|
|
|
+ s,
|
|
|
+ );
|
|
|
+ expect(throwable('str')).to.throw(error1('"str"'));
|
|
|
+ expect(throwable('')).to.throw(error2('""'));
|
|
|
+ expect(throwable(10)).to.throw(error2('10'));
|
|
|
+ expect(throwable(0)).to.throw(error2('0'));
|
|
|
+ expect(throwable([])).to.throw(error2('Array'));
|
|
|
+ expect(throwable({})).to.throw(error1('Object'));
|
|
|
+ throwable(true)();
|
|
|
+ throwable(false)();
|
|
|
+ throwable(undefined)();
|
|
|
});
|
|
|
|
|
|
- it('should exclude a property when the "select" option is false in the keep unknown mode', function () {
|
|
|
- const schema = {foo: true, bar: {select: false}};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema, {keepUnknown: true});
|
|
|
+ it('should prioritize the option "$keepUnknown" over the function option', function () {
|
|
|
+ const res = projectData(
|
|
|
+ {foo: 10},
|
|
|
+ {$keepUnknown: true},
|
|
|
+ {keepUnknown: false},
|
|
|
+ );
|
|
|
expect(res).to.be.eql({foo: 10});
|
|
|
});
|
|
|
|
|
|
- it('should include a property when the "select" option is true in the keep unknown mode', function () {
|
|
|
- const schema = {foo: true, bar: {select: true}};
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, schema, {keepUnknown: true});
|
|
|
- expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
- });
|
|
|
-
|
|
|
- it('should include a property with a nested schema in the keep unknown mode', function () {
|
|
|
- const schema = {user: {schema: {id: true, name: false}}};
|
|
|
- const data = {user: {id: 1, name: 'John Doe'}};
|
|
|
- const res = projectData(data, schema, {keepUnknown: true});
|
|
|
- expect(res).to.be.eql({user: {id: 1}});
|
|
|
- });
|
|
|
-
|
|
|
- it('should include unknown properties in the keep unknown mode', function () {
|
|
|
- const data = {foo: 10, bar: 20};
|
|
|
- const res = projectData(data, {}, {keepUnknown: true});
|
|
|
- expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
- });
|
|
|
-
|
|
|
- it('should project an array items in the keep unknown mode', function () {
|
|
|
- const list = [{foo: 10, bar: 20, baz: 30}, {qux: 30}];
|
|
|
- const expectedList = [{foo: 10, baz: 30}, {qux: 30}];
|
|
|
- const res = projectData(list, {foo: true, bar: false}, {keepUnknown: true});
|
|
|
- expect(res).to.be.eql(expectedList);
|
|
|
- });
|
|
|
-
|
|
|
- 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(data, {foo: true, bar: false});
|
|
|
+ it('should ignore properties not defined in a given schema by default', function () {
|
|
|
+ const res = projectData({foo: 10, bar: 20}, {foo: true});
|
|
|
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({foo: 10, bar: 20}, schema);
|
|
|
- expect(res).to.be.eql({foo: 10});
|
|
|
+ it('should keep unknown properties when the keep unknown mode is enabled', function () {
|
|
|
+ const res = projectData(
|
|
|
+ {foo: 10, bar: 20},
|
|
|
+ {foo: true},
|
|
|
+ {keepUnknown: true},
|
|
|
+ );
|
|
|
+ expect(res).to.be.eql({foo: 10, bar: 20});
|
|
|
});
|
|
|
|
|
|
- it('should project the active scope by a boolean rule', function () {
|
|
|
- const schema = {
|
|
|
- foo: {scopes: {input: true}},
|
|
|
- bar: {scopes: {input: false}},
|
|
|
- };
|
|
|
- const res = projectData({foo: 10, bar: 20}, schema, {scope: 'input'});
|
|
|
+ it('should not add properties that do not exist in a given data', function () {
|
|
|
+ const res = projectData({foo: 10}, {foo: true, bar: true});
|
|
|
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({foo: 10, bar: 20}, schema, {scope: 'input'});
|
|
|
+ it('should not add inline options to a projection result', function () {
|
|
|
+ const res = projectData(
|
|
|
+ {foo: 10, bar: 20},
|
|
|
+ {$keepUnknown: true, foo: true, bar: false},
|
|
|
+ );
|
|
|
expect(res).to.be.eql({foo: 10});
|
|
|
});
|
|
|
|
|
|
- it('should prioritize the scope rule over common rules', function () {
|
|
|
- const schema = {
|
|
|
- foo: {select: false, scopes: {input: true}},
|
|
|
- bar: {select: true, scopes: {input: false}},
|
|
|
- };
|
|
|
- const res = projectData({foo: 10, bar: 20}, schema, {scope: 'input'});
|
|
|
+ it('should use a boolean rule to project an object property', function () {
|
|
|
+ const res = projectData({foo: 10, bar: 20}, {foo: true, bar: false});
|
|
|
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({foo: 10, bar: 20}, schema, {scope: 'input'});
|
|
|
- expect(res).to.be.eql({foo: 10});
|
|
|
+ it('should project a nested object for a nested schema', function () {
|
|
|
+ const res = projectData(
|
|
|
+ {foo: {bar: 10, baz: 20}},
|
|
|
+ {foo: {bar: true, baz: false}},
|
|
|
+ );
|
|
|
+ expect(res).to.be.eql({foo: {bar: 10}});
|
|
|
});
|
|
|
|
|
|
- it('should include a property in the keep unknown mode if no rule for the active scope is specified', 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(data, schema, {scope: 'input', keepUnknown: true});
|
|
|
- expect(res).to.be.eql({foo: 10, baz: 30, qux: 40});
|
|
|
+ it('should resolve a factory function in a nested schema', function () {
|
|
|
+ const res = projectData(
|
|
|
+ {foo: {bar: 10, baz: 20}},
|
|
|
+ {foo: () => ({bar: true, baz: false})},
|
|
|
+ );
|
|
|
+ expect(res).to.be.eql({foo: {bar: 10}});
|
|
|
});
|
|
|
|
|
|
- it('should prioritize scope options over common options in the keep unknown mode', function () {
|
|
|
- const schema = {
|
|
|
- foo: {select: false, scopes: {input: true}},
|
|
|
- bar: {select: false, scopes: {input: {select: true}}},
|
|
|
+ it('should resolve a schema name in a nested schema', function () {
|
|
|
+ let invoked = 0;
|
|
|
+ const nameResolver = name => {
|
|
|
+ invoked++;
|
|
|
+ expect(name).to.be.eq('mySchema');
|
|
|
+ return {bar: true, baz: false};
|
|
|
};
|
|
|
- const data = {foo: 10, bar: 20, baz: 30};
|
|
|
- const res = projectData(data, schema, {scope: 'input', keepUnknown: true});
|
|
|
- expect(res).to.be.eql({foo: 10, bar: 20, baz: 30});
|
|
|
+ const res = projectData(
|
|
|
+ {foo: {bar: 10, baz: 20}},
|
|
|
+ {foo: 'mySchema'},
|
|
|
+ {nameResolver},
|
|
|
+ );
|
|
|
+ expect(res).to.be.eql({foo: {bar: 10}});
|
|
|
+ expect(invoked).to.be.eq(1);
|
|
|
});
|
|
|
|
|
|
- it('should project a nested object by a 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(data, schema, {scope: 'input'});
|
|
|
- expect(res).to.be.eql({foo: 10, bar: {baz: 20}});
|
|
|
+ it('should project a nested object in the keep unknown mode', function () {
|
|
|
+ const data = {foo: {bar: 10, baz: 20, qux: 30}};
|
|
|
+ const schema = {foo: {bar: true, baz: false}};
|
|
|
+ const res = projectData(data, schema, {keepUnknown: true});
|
|
|
+ expect(res).to.be.eql({foo: {bar: 10, qux: 30}});
|
|
|
});
|
|
|
});
|