|
|
|
"use strict";
|
|
|
|
|
|
|
|
const expect = require("chai").expect;
|
|
|
|
|
|
|
|
const dm = require("../src");
|
|
|
|
|
|
|
|
/* FIXME: setOf/mapOf? */
|
|
|
|
/* FIXME: Type aliases? Both producing and consuming */
|
|
|
|
|
|
|
|
describe("registry", () => {
|
|
|
|
let registry = dm.createRegistry();
|
|
|
|
|
|
|
|
describe("registry API", () => {
|
|
|
|
it("should not expose registry-specific methods on the primary module API", () => {
|
|
|
|
expect(dm.type).to.equal(undefined);
|
|
|
|
expect(dm.trait).to.equal(undefined);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should expose the createRegistry method on the primary module API", () => {
|
|
|
|
expect(dm.createRegistry).to.be.a("function");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should expose registry-specific methods on the registry API", () => {
|
|
|
|
expect(registry.type).to.be.a("function");
|
|
|
|
expect(registry.trait).to.be.a("function");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should not expose the createRegistry method on the registry API", () => {
|
|
|
|
expect(registry.createRegistry).to.equal(undefined);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("registry usage", () => {
|
|
|
|
it("should work correctly for types", () => {
|
|
|
|
let SimplePerson = registry.createType("SimplePerson", {
|
|
|
|
name: dm.string(),
|
|
|
|
favouriteGift: registry.type("SimpleGift").optional()
|
|
|
|
});
|
|
|
|
|
|
|
|
let SimpleGift = registry.createType("SimpleGift", {
|
|
|
|
description: dm.string(),
|
|
|
|
from: registry.type("SimplePerson"),
|
|
|
|
to: registry.type("SimplePerson")
|
|
|
|
});
|
|
|
|
|
|
|
|
let joe = SimplePerson({
|
|
|
|
name: "Joe"
|
|
|
|
});
|
|
|
|
|
|
|
|
let jane = SimplePerson({
|
|
|
|
name: "Jane"
|
|
|
|
});
|
|
|
|
|
|
|
|
let flowers = SimpleGift({
|
|
|
|
description: "Flowers",
|
|
|
|
from: jane,
|
|
|
|
to: joe
|
|
|
|
});
|
|
|
|
|
|
|
|
joe.favouriteGift = flowers;
|
|
|
|
|
|
|
|
expect(joe.favouriteGift).to.equal(flowers);
|
|
|
|
expect(flowers.to).to.equal(joe);
|
|
|
|
expect(flowers.from).to.equal(jane);
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
jane.favouriteGift = "not a gift";
|
|
|
|
}).to.throw("Expected an instance of SimpleGift, got a string instead");
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
flowers.to = "not a person";
|
|
|
|
}).to.throw("Expected an instance of SimplePerson, got a string instead");
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
flowers.to = flowers;
|
|
|
|
}).to.throw("Expected an instance of SimplePerson, got an instance of SimpleGift instead");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should work correctly for traits", () => {
|
|
|
|
let Givable = registry.createTrait("Givable", {
|
|
|
|
from: registry.type("Person"),
|
|
|
|
to: registry.type("Person")
|
|
|
|
});
|
|
|
|
|
|
|
|
let Person = registry.createType("Person", {
|
|
|
|
name: dm.string(),
|
|
|
|
favouriteGift: registry.trait("Givable").optional()
|
|
|
|
});
|
|
|
|
|
|
|
|
let Gift = registry.createType("Gift", {
|
|
|
|
description: dm.string()
|
|
|
|
}).implements(Givable, {
|
|
|
|
from: dm.slot(),
|
|
|
|
to: dm.slot()
|
|
|
|
});
|
|
|
|
|
|
|
|
let joe = Person({
|
|
|
|
name: "Joe"
|
|
|
|
});
|
|
|
|
|
|
|
|
let jane = Person({
|
|
|
|
name: "Jane"
|
|
|
|
});
|
|
|
|
|
|
|
|
let flowers = Gift({
|
|
|
|
description: "Flowers",
|
|
|
|
from: jane,
|
|
|
|
to: joe
|
|
|
|
});
|
|
|
|
|
|
|
|
joe.favouriteGift = flowers;
|
|
|
|
|
|
|
|
expect(joe.favouriteGift).to.equal(flowers);
|
|
|
|
expect(flowers.to).to.equal(joe);
|
|
|
|
expect(flowers.from).to.equal(jane);
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
jane.favouriteGift = "not a gift";
|
|
|
|
}).to.throw("Expected object of a type with the Givable trait, got a string instead");
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
flowers.to = "not a person";
|
|
|
|
}).to.throw("Expected an instance of Person, got a string instead");
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
flowers.to = flowers;
|
|
|
|
}).to.throw("Expected an instance of Person, got an instance of Gift instead");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should work correctly for type aliases", () => {
|
|
|
|
let CheckedString = registry.createType("CheckedString", dm.string({ matches: /^[a-z]+$/ }));
|
|
|
|
|
|
|
|
let SomeType = registry.createType("SomeType", {
|
|
|
|
value: registry.type("CheckedString")
|
|
|
|
});
|
|
|
|
|
|
|
|
let instance = SomeType({
|
|
|
|
value: "foo"
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(instance.value).to.equal("foo");
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
SomeType({
|
|
|
|
value: "bar42"
|
|
|
|
});
|
|
|
|
}).to.throw("Value for property 'value' failed `matches` condition");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should work correctly for guarded maps and sets", () => {
|
|
|
|
let SomeNewType = registry.createType("SomeNewType", {
|
|
|
|
value: dm.string()
|
|
|
|
});
|
|
|
|
|
|
|
|
let SomeOtherType = registry.createType("SomeOtherType", {
|
|
|
|
things: dm.setOf(registry.type("SomeNewType")),
|
|
|
|
thingsMap: dm.mapOf(dm.string(), registry.type("SomeNewType"))
|
|
|
|
});
|
|
|
|
|
|
|
|
let thingOne = SomeNewType({
|
|
|
|
value: "one"
|
|
|
|
});
|
|
|
|
|
|
|
|
let thingTwo = SomeNewType({
|
|
|
|
value: "two"
|
|
|
|
});
|
|
|
|
|
|
|
|
let collection = SomeOtherType({
|
|
|
|
things: new Set([
|
|
|
|
thingOne,
|
|
|
|
thingTwo
|
|
|
|
]),
|
|
|
|
thingsMap: new Map([
|
|
|
|
["one", thingOne],
|
|
|
|
["two", thingTwo]
|
|
|
|
])
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
SomeOtherType({
|
|
|
|
things: new Set([
|
|
|
|
"not",
|
|
|
|
"things"
|
|
|
|
]),
|
|
|
|
thingsMap: new Map([
|
|
|
|
["one", thingOne],
|
|
|
|
["two", thingTwo]
|
|
|
|
])
|
|
|
|
});
|
|
|
|
}).to.throw("Expected an instance of SomeNewType, got a string instead");
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
SomeOtherType({
|
|
|
|
things: new Set([
|
|
|
|
thingOne,
|
|
|
|
thingTwo
|
|
|
|
]),
|
|
|
|
thingsMap: new Map([
|
|
|
|
["one", "not"],
|
|
|
|
["two", "things"]
|
|
|
|
])
|
|
|
|
});
|
|
|
|
}).to.throw("Expected an instance of SomeNewType, got a string instead");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should reject duplicate type registrations", () => {
|
|
|
|
registry.createType("DuplicateType", {});
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
registry.createType("DuplicateType", {});
|
|
|
|
}).to.throw("A type named DuplicateType already exists in this registry");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should reject duplicate trait registrations", () => {
|
|
|
|
registry.createTrait("DuplicateTrait", {});
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
registry.createTrait("DuplicateTrait", {});
|
|
|
|
}).to.throw("A trait named DuplicateTrait already exists in this registry");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should allow using types from different registries (as well as registry-less types) with one another", () => {
|
|
|
|
let registryOne = dm.createRegistry();
|
|
|
|
let registryTwo = dm.createRegistry();
|
|
|
|
|
|
|
|
let Item = dm.createType("Item", {
|
|
|
|
value: dm.string()
|
|
|
|
});
|
|
|
|
|
|
|
|
let ItemOne = registryOne.createType("Item", {
|
|
|
|
two: registryTwo.type("Item")
|
|
|
|
});
|
|
|
|
|
|
|
|
let ItemTwo = registryTwo.createType("Item", {
|
|
|
|
item: Item
|
|
|
|
});
|
|
|
|
|
|
|
|
let zero = Item({
|
|
|
|
value: "foo"
|
|
|
|
});
|
|
|
|
|
|
|
|
let two = ItemTwo({
|
|
|
|
item: zero
|
|
|
|
});
|
|
|
|
|
|
|
|
let one = ItemOne({
|
|
|
|
two: two
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(one.two.item.value).to.equal("foo");
|
|
|
|
|
|
|
|
expect(() => {
|
|
|
|
ItemOne({
|
|
|
|
two: ItemOne({
|
|
|
|
two: ItemTwo({
|
|
|
|
item: Item({
|
|
|
|
value: "bar"
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}).to.throw("Expected an instance of Item, got an instance of Item instead");
|
|
|
|
/* TODO: Clarify the error message when the display name of two types is the same. */
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|