"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. */ }); }); });