gecko-dev/addon-sdk/source/test/test-traits-core.js

855 строки
17 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const ERR_CONFLICT = 'Remaining conflicting property: ',
ERR_REQUIRED = 'Missing required property: ';
const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
...Object.getOwnPropertySymbols(x)];
function assertSametrait(assert, trait1, trait2) {
let names1 = getOwnIdentifiers(trait1),
names2 = getOwnIdentifiers(trait2);
assert.equal(
names1.length,
names2.length,
'equal traits must have same amount of properties'
);
for (let i = 0; i < names1.length; i++) {
let name = names1[i];
assert.notEqual(
-1,
names2.indexOf(name),
'equal traits must contain same named properties: ' + name
);
assertSameDescriptor(assert, name, trait1[name], trait2[name]);
}
}
function assertSameDescriptor(assert, name, desc1, desc2) {
if (desc1.conflict || desc2.conflict) {
assert.equal(
desc1.conflict,
desc2.conflict,
'if one of same descriptors has `conflict` another must have it: '
+ name
);
}
else if (desc1.required || desc2.required) {
assert.equal(
desc1.required,
desc2.required,
'if one of same descriptors is has `required` another must have it: '
+ name
);
}
else {
assert.equal(
desc1.get,
desc2.get,
'get must be the same on both descriptors: ' + name
);
assert.equal(
desc1.set,
desc2.set,
'set must be the same on both descriptors: ' + name
);
assert.equal(
desc1.value,
desc2.value,
'value must be the same on both descriptors: ' + name
);
assert.equal(
desc1.enumerable,
desc2.enumerable,
'enumerable must be the same on both descriptors: ' + name
);
assert.equal(
desc1.required,
desc2.required,
'value must be the same on both descriptors: ' + name
);
}
}
function Data(value, enumerable, confligurable, writable) {
return {
value: value,
enumerable: false !== enumerable,
confligurable: false !== confligurable,
writable: false !== writable
};
}
function Method(method, enumerable, confligurable, writable) {
return {
value: method,
enumerable: false !== enumerable,
confligurable: false !== confligurable,
writable: false !== writable
};
}
function Accessor(get, set, enumerable, confligurable) {
return {
get: get,
set: set,
enumerable: false !== enumerable,
confligurable: false !== confligurable,
};
}
function Required(name) {
function required() { throw new Error(ERR_REQUIRED + name) }
return {
get: required,
set: required,
required: true
};
}
function Conflict(name) {
function conflict() { throw new Error(ERR_CONFLICT + name) }
return {
get: conflict,
set: conflict,
conflict: true
};
}
function testMethod() {};
const { trait, compose, resolve, required, override, create } =
require('sdk/deprecated/traits/core');
exports['test:empty trait'] = function(assert) {
assertSametrait(
assert,
trait({}),
{}
);
};
exports['test:simple trait'] = function(assert) {
assertSametrait(
assert,
trait({
a: 0,
b: testMethod
}),
{
a: Data(0, true, true, true),
b: Method(testMethod, true, true, true)
}
);
};
exports['test:simple trait with required prop'] = function(assert) {
assertSametrait(
assert,
trait({
a: required,
b: 1
}),
{
a: Required('a'),
b: Data(1)
}
);
};
exports['test:ordering of trait properties is irrelevant'] = function(assert) {
assertSametrait(
assert,
trait({ a: 0, b: 1, c: required }),
trait({ b: 1, c: required, a: 0 })
);
};
exports['test:trait with accessor property'] = function(assert) {
let record = { get a() {}, set a(v) {} };
let get = Object.getOwnPropertyDescriptor(record,'a').get;
let set = Object.getOwnPropertyDescriptor(record,'a').set;
assertSametrait(assert,
trait(record),
{ a: Accessor(get, set ) }
);
};
exports['test:simple composition'] = function(assert) {
assertSametrait(
assert,
compose(
trait({ a: 0, b: 1 }),
trait({ c: 2, d: testMethod })
),
{
a: Data(0),
b: Data(1),
c: Data(2),
d: Method(testMethod)
}
);
};
exports['test:composition with conflict'] = function(assert) {
assertSametrait(
assert,
compose(
trait({ a: 0, b: 1 }),
trait({ a: 2, c: testMethod })
),
{
a: Conflict('a'),
b: Data(1),
c: Method(testMethod)
}
);
};
exports['test:composition of identical props does not cause conflict'] =
function(assert) {
assertSametrait(assert,
compose(
trait({ a: 0, b: 1 }),
trait({ a: 0, c: testMethod })
),
{
a: Data(0),
b: Data(1),
c: Method(testMethod) }
)
};
exports['test:composition with identical required props'] =
function(assert) {
assertSametrait(assert,
compose(
trait({ a: required, b: 1 }),
trait({ a: required, c: testMethod })
),
{
a: Required(),
b: Data(1),
c: Method(testMethod)
}
);
};
exports['test:composition satisfying a required prop'] = function (assert) {
assertSametrait(assert,
compose(
trait({ a: required, b: 1 }),
trait({ a: testMethod })
),
{
a: Method(testMethod),
b: Data(1)
}
);
};
exports['test:compose is neutral wrt conflicts'] = function (assert) {
assertSametrait(assert,
compose(
compose(
trait({ a: 1 }),
trait({ a: 2 })
),
trait({ b: 0 })
),
{
a: Conflict('a'),
b: Data(0)
}
);
};
exports['test:conflicting prop overrides required prop'] = function (assert) {
assertSametrait(assert,
compose(
compose(
trait({ a: 1 }),
trait({ a: 2 })
),
trait({ a: required })
),
{
a: Conflict('a')
}
);
};
exports['test:compose is commutative'] = function (assert) {
assertSametrait(assert,
compose(
trait({ a: 0, b: 1 }),
trait({ c: 2, d: testMethod })
),
compose(
trait({ c: 2, d: testMethod }),
trait({ a: 0, b: 1 })
)
);
};
exports['test:compose is commutative, also for required/conflicting props'] =
function (assert) {
assertSametrait(assert,
compose(
trait({ a: 0, b: 1, c: 3, e: required }),
trait({ c: 2, d: testMethod })
),
compose(
trait({ c: 2, d: testMethod }),
trait({ a: 0, b: 1, c: 3, e: required })
)
);
};
exports['test:compose is associative'] = function (assert) {
assertSametrait(assert,
compose(
trait({ a: 0, b: 1, c: 3, d: required }),
compose(
trait({ c: 3, d: required }),
trait({ c: 2, d: testMethod, e: 'foo' })
)
),
compose(
compose(
trait({ a: 0, b: 1, c: 3, d: required }),
trait({ c: 3, d: required })
),
trait({ c: 2, d: testMethod, e: 'foo' })
)
);
};
exports['test:diamond import of same prop does not generate conflict'] =
function (assert) {
assertSametrait(assert,
compose(
compose(
trait({ b: 2 }),
trait({ a: 1 })
),
compose(
trait({ c: 3 }),
trait({ a: 1 })
),
trait({ d: 4 })
),
{
a: Data(1),
b: Data(2),
c: Data(3),
d: Data(4)
}
);
};
exports['test:resolve with empty resolutions has no effect'] =
function (assert) {
assertSametrait(assert, resolve({}, trait({
a: 1,
b: required,
c: testMethod
})), {
a: Data(1),
b: Required(),
c: Method(testMethod)
});
};
exports['test:resolve: renaming'] = function (assert) {
assertSametrait(assert,
resolve(
{ a: 'A', c: 'C' },
trait({ a: 1, b: required, c: testMethod })
),
{
A: Data(1),
b: Required(),
C: Method(testMethod),
a: Required(),
c: Required()
}
);
};
exports['test:resolve: renaming to conflicting name causes conflict, order 1']
= function (assert) {
assertSametrait(assert,
resolve(
{ a: 'b'},
trait({ a: 1, b: 2 })
),
{
b: Conflict('b'),
a: Required()
}
);
};
exports['test:resolve: renaming to conflicting name causes conflict, order 2']
= function (assert) {
assertSametrait(assert,
resolve(
{ a: 'b' },
trait({ b: 2, a: 1 })
),
{
b: Conflict('b'),
a: Required()
}
);
};
exports['test:resolve: simple exclusion'] = function (assert) {
assertSametrait(assert,
resolve(
{ a: undefined },
trait({ a: 1, b: 2 })
),
{
a: Required(),
b: Data(2)
}
);
};
exports['test:resolve: exclusion to "empty" trait'] = function (assert) {
assertSametrait(assert,
resolve(
{ a: undefined, b: undefined },
trait({ a: 1, b: 2 })
),
{
a: Required(),
b: Required()
}
);
};
exports['test:resolve: exclusion and renaming of disjoint props'] =
function (assert) {
assertSametrait(assert,
resolve(
{ a: undefined, b: 'c' },
trait({ a: 1, b: 2 })
),
{
a: Required(),
c: Data(2),
b: Required()
}
);
};
exports['test:resolve: exclusion and renaming of overlapping props'] =
function (assert) {
assertSametrait(assert,
resolve(
{ a: undefined, b: 'a' },
trait({ a: 1, b: 2 })
),
{
a: Data(2),
b: Required()
}
);
};
exports['test:resolve: renaming to a common alias causes conflict'] =
function (assert) {
assertSametrait(assert,
resolve(
{ a: 'c', b: 'c' },
trait({ a: 1, b: 2 })
),
{
c: Conflict('c'),
a: Required(),
b: Required()
}
);
};
exports['test:resolve: renaming overrides required target'] =
function (assert) {
assertSametrait(assert,
resolve(
{ b: 'a' },
trait({ a: required, b: 2 })
),
{
a: Data(2),
b: Required()
}
);
};
exports['test:resolve: renaming required properties has no effect'] =
function (assert) {
assertSametrait(assert,
resolve(
{ b: 'a' },
trait({ a: 2, b: required })
),
{
a: Data(2),
b: Required()
}
);
};
exports['test:resolve: renaming of non-existent props has no effect'] =
function (assert) {
assertSametrait(assert,
resolve(
{ a: 'c', d: 'c' },
trait({ a: 1, b: 2 })
),
{
c: Data(1),
b: Data(2),
a: Required()
}
);
};
exports['test:resolve: exclusion of non-existent props has no effect'] =
function (assert) {
assertSametrait(assert,
resolve(
{ b: undefined },
trait({ a: 1 })
),
{
a: Data(1)
}
);
};
exports['test:resolve is neutral w.r.t. required properties'] =
function (assert) {
assertSametrait(assert,
resolve(
{ a: 'c', b: undefined },
trait({ a: required, b: required, c: 'foo', d: 1 })
),
{
a: Required(),
b: Required(),
c: Data('foo'),
d: Data(1)
}
);
};
exports['test:resolve supports swapping of property names, ordering 1'] =
function (assert) {
assertSametrait(assert,
resolve(
{ a: 'b', b: 'a' },
trait({ a: 1, b: 2 })
),
{
a: Data(2),
b: Data(1)
}
);
};
exports['test:resolve supports swapping of property names, ordering 2'] =
function (assert) {
assertSametrait(assert,
resolve(
{ b: 'a', a: 'b' },
trait({ a: 1, b: 2 })
),
{
a: Data(2),
b: Data(1)
}
);
};
exports['test:resolve supports swapping of property names, ordering 3'] =
function (assert) {
assertSametrait(assert,
resolve(
{ b: 'a', a: 'b' },
trait({ b: 2, a: 1 })
),
{
a: Data(2),
b: Data(1)
}
);
};
exports['test:resolve supports swapping of property names, ordering 4'] =
function (assert) {
assertSametrait(assert,
resolve(
{ a: 'b', b: 'a' },
trait({ b: 2, a: 1 })
),
{
a: Data(2),
b: Data(1)
}
);
};
exports['test:override of mutually exclusive traits'] = function (assert) {
assertSametrait(assert,
override(
trait({ a: 1, b: 2 }),
trait({ c: 3, d: testMethod })
),
{
a: Data(1),
b: Data(2),
c: Data(3),
d: Method(testMethod)
}
);
};
exports['test:override of mutually exclusive traits is compose'] =
function (assert) {
assertSametrait(assert,
override(
trait({ a: 1, b: 2 }),
trait({ c: 3, d: testMethod })
),
compose(
trait({ d: testMethod, c: 3 }),
trait({ b: 2, a: 1 })
)
);
};
exports['test:override of overlapping traits'] = function (assert) {
assertSametrait(assert,
override(
trait({ a: 1, b: 2 }),
trait({ a: 3, c: testMethod })
),
{
a: Data(1),
b: Data(2),
c: Method(testMethod)
}
);
};
exports['test:three-way override of overlapping traits'] = function (assert) {
assertSametrait(assert,
override(
trait({ a: 1, b: 2 }),
trait({ b: 4, c: 3 }),
trait({ a: 3, c: testMethod, d: 5 })
),
{
a: Data(1),
b: Data(2),
c: Data(3),
d: Data(5)
}
);
};
exports['test:override replaces required properties'] = function (assert) {
assertSametrait(assert,
override(
trait({ a: required, b: 2 }),
trait({ a: 1, c: testMethod })
),
{
a: Data(1),
b: Data(2),
c: Method(testMethod)
}
);
};
exports['test:override is not commutative'] = function (assert) {
assertSametrait(assert,
override(
trait({ a: 1, b: 2 }),
trait({ a: 3, c: 4 })
),
{
a: Data(1),
b: Data(2),
c: Data(4)
}
);
assertSametrait(assert,
override(
trait({ a: 3, c: 4 }),
trait({ a: 1, b: 2 })
),
{
a: Data(3),
b: Data(2),
c: Data(4)
}
);
};
exports['test:override is associative'] = function (assert) {
assertSametrait(assert,
override(
override(
trait({ a: 1, b: 2 }),
trait({ a: 3, c: 4, d: 5 })
),
trait({ a: 6, c: 7, e: 8 })
),
override(
trait({ a: 1, b: 2 }),
override(
trait({ a: 3, c: 4, d: 5 }),
trait({ a: 6, c: 7, e: 8 })
)
)
);
};
exports['test:create simple'] = function(assert) {
let o1 = create(
Object.prototype,
trait({ a: 1, b: function() { return this.a; } })
);
assert.equal(
Object.prototype,
Object.getPrototypeOf(o1),
'o1 prototype'
);
assert.equal(1, o1.a, 'o1.a');
assert.equal(1, o1.b(), 'o1.b()');
assert.equal(
2,
getOwnIdentifiers(o1).length,
'Object.keys(o1).length === 2'
);
};
exports['test:create with Array.prototype'] = function(assert) {
let o2 = create(Array.prototype, trait({}));
assert.equal(
Array.prototype,
Object.getPrototypeOf(o2),
"o2 prototype"
);
};
exports['test:exception for incomplete required properties'] =
function(assert) {
try {
create(Object.prototype, trait({ foo: required }));
assert.fail('expected create to complain about missing required props');
}
catch(e) {
assert.equal(
'Error: Missing required property: foo',
e.toString(),
'required prop error'
);
}
};
exports['test:exception for unresolved conflicts'] = function(assert) {
try {
create({}, compose(trait({ a: 0 }), trait({ a: 1 })));
assert.fail('expected create to complain about unresolved conflicts');
}
catch(e) {
assert.equal(
'Error: Remaining conflicting property: a',
e.toString(),
'conflicting prop error'
);
}
};
exports['test:verify that required properties are present but undefined'] =
function(assert) {
try {
let o4 = Object.create(Object.prototype, trait({ foo: required }));
assert.equal(true, 'foo' in o4, 'required property present');
try {
let foo = o4.foo;
assert.fail('access to required property must throw');
}
catch(e) {
assert.equal(
'Error: Missing required property: foo',
e.toString(),
'required prop error'
)
}
}
catch(e) {
assert.fail('did not expect create to complain about required props');
}
};
exports['test:verify that conflicting properties are present'] =
function(assert) {
try {
let o5 = Object.create(
Object.prototype,
compose(trait({ a: 0 }), trait({ a: 1 }))
);
assert.equal(true, 'a' in o5, 'conflicting property present');
try {
let a = o5.a; // accessors or data prop
assert.fail('expected conflicting prop to cause exception');
}
catch (e) {
assert.equal(
'Error: Remaining conflicting property: a',
e.toString(),
'conflicting prop access error'
);
}
}
catch(e) {
assert.fail('did not expect create to complain about conflicting props');
}
};
exports['test diamond with conflicts'] = function(assert) {
function makeT1(x) trait({ m: function() { return x; } })
function makeT2(x) compose(trait({ t2: 'foo' }), makeT1(x))
function makeT3(x) compose(trait({ t3: 'bar' }), makeT1(x))
let T4 = compose(makeT2(5), makeT3(5));
try {
let o = create(Object.prototype, T4);
assert.fail('expected diamond prop to cause exception');
}
catch(e) {
assert.equal(
'Error: Remaining conflicting property: m',
e.toString(),
'diamond prop conflict'
);
}
};
require('sdk/test').run(exports);