terriajs
Version:
Geospatial data visualization platform.
440 lines (370 loc) • 13.5 kB
text/typescript
import { configure } from "mobx";
import CreateModel from "../../lib/Models/Definition/CreateModel";
import createStratumInstance from "../../lib/Models/Definition/createStratumInstance";
import Terria from "../../lib/Models/Terria";
import objectArrayTrait, {
MergeStrategy
} from "../../lib/Traits/Decorators/objectArrayTrait";
import primitiveTrait from "../../lib/Traits/Decorators/primitiveTrait";
import ModelTraits from "../../lib/Traits/ModelTraits";
import updateModelFromJson from "../../lib/Models/Definition/updateModelFromJson";
import CommonStrata from "../../lib/Models/Definition/CommonStrata";
configure({
enforceActions: "always",
computedRequiresReaction: true
});
class InnerTraits extends ModelTraits {
({
type: "string",
name: "Foo",
description: "Foo"
})
foo?: string;
({
type: "number",
name: "Bar",
description: "Bar"
})
bar?: number;
({
type: "boolean",
name: "Baz",
description: "Baz"
})
baz?: boolean;
static isRemoval(traits: InnerTraits) {
return traits.bar === 42;
}
}
class OuterTraits extends ModelTraits {
({
type: InnerTraits,
name: "Inner",
description: "Inner",
idProperty: "foo"
})
inner?: InnerTraits[];
({
type: InnerTraits,
name: "Inner by index",
description: "Inner by index",
idProperty: "index"
})
innerByIndex?: InnerTraits[];
({
type: "string",
name: "Other",
description: "Other"
})
other?: string;
}
class OuterTraitsNoMerge extends ModelTraits {
({
type: InnerTraits,
name: "Inner",
description: "Inner",
idProperty: "foo",
merge: MergeStrategy.None
})
inner?: InnerTraits[];
({
type: "string",
name: "Other",
description: "Other"
})
other?: string;
}
class OuterTraitsMergeTop extends ModelTraits {
({
type: InnerTraits,
name: "Inner",
description: "Inner",
idProperty: "foo",
merge: MergeStrategy.TopStratum
})
inner?: InnerTraits[];
({
type: "string",
name: "Other",
description: "Other"
})
other?: string;
}
class TestModel extends CreateModel(OuterTraits) {}
class TestModelNoMerge extends CreateModel(OuterTraitsNoMerge) {}
class TestModelMergeTop extends CreateModel(OuterTraitsMergeTop) {}
describe("objectArrayTrait", function () {
it("returns an empty model if all strata are undefined", function () {
const terria = new Terria();
const model = new TestModel("test", terria);
model.strata.set("definition", createStratumInstance(OuterTraits));
model.strata.set("user", createStratumInstance(OuterTraits));
expect(model.inner).toBeDefined();
});
it("combines values from different strata", function () {
const terria = new Terria();
const model = new TestModel("test", terria);
const definition = createStratumInstance(OuterTraits);
const user = createStratumInstance(OuterTraits);
model.strata.set("definition", definition);
model.strata.set("user", user);
definition.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
definition.inner[0].foo = "a";
definition.inner[0].bar = 1;
definition.inner[1].foo = "b";
definition.inner[1].bar = 2;
definition.inner[1].baz = true;
user.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
user.inner[0].foo = "b";
user.inner[0].baz = false;
user.inner[1].foo = "c";
user.inner[1].bar = 3;
expect(model.inner).toBeDefined();
if (model.inner !== undefined) {
expect(model.inner.length).toEqual(3);
const a = model.inner.filter((x) => x.foo === "a")[0];
const b = model.inner.filter((x) => x.foo === "b")[0];
const c = model.inner.filter((x) => x.foo === "c")[0];
expect(a).toBeDefined();
expect(b).toBeDefined();
expect(c).toBeDefined();
expect(a.bar).toEqual(1);
expect(a.baz).toBeUndefined();
expect(b.bar).toEqual(2);
expect(b.baz).toEqual(false);
expect(c.bar).toEqual(3);
expect(c.baz).toBeUndefined();
}
});
it("updates to reflect array elements added after evaluation", function () {
const terria = new Terria();
const model = new TestModel("test", terria);
const definition = createStratumInstance(OuterTraits);
const user = createStratumInstance(OuterTraits);
model.strata.set("definition", definition);
model.strata.set("user", user);
definition.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
definition.inner[0].foo = "a";
definition.inner[0].bar = 1;
definition.inner[1].foo = "b";
definition.inner[1].bar = 2;
definition.inner[1].baz = true;
user.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
user.inner[0].foo = "b";
user.inner[0].baz = false;
user.inner[1].foo = "c";
user.inner[1].bar = 3;
expect(model.inner).toBeDefined();
if (model.inner !== undefined) {
expect(model.inner.length).toEqual(3);
const newOne = createStratumInstance(InnerTraits);
definition.inner.push(newOne);
newOne.foo = "c";
newOne.bar = 4;
newOne.baz = true;
expect(model.inner.length).toEqual(3);
const c = model.inner.filter((x) => x.foo === "c")[0];
expect(c.bar).toEqual(3);
expect(c.baz).toEqual(true);
}
});
it("allows strata to remove elements from top level", function () {
const terria = new Terria();
const model = new TestModel("test", terria);
const definition = createStratumInstance(OuterTraits);
const user = createStratumInstance(OuterTraits);
model.strata.set("definition", definition);
model.strata.set("user", user);
definition.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
definition.inner[0].foo = "a";
definition.inner[0].bar = 1;
definition.inner[1].foo = "b";
definition.inner[1].bar = 2;
definition.inner[1].baz = true;
user.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
user.inner[0].foo = "b";
user.inner[0].bar = 42; // indicates removed, according to InnerTraits.isRemoval.
user.inner[1].foo = "c";
user.inner[1].bar = 3;
user.inner[2].foo = "a";
user.inner[2].bar = 11;
// Here we expect the order to be "a" and then "c" as "a" is defined in a lower strata to "c"
expect(model.inner.length).toEqual(2);
expect(model.inner[0].foo).toEqual("a");
expect(model.inner[0].bar).toEqual(11);
expect(model.inner[1].foo).toEqual("c");
});
it("allows strata to remove elements from bottom level", function () {
const terria = new Terria();
const model = new TestModel("test", terria);
const definition = createStratumInstance(OuterTraits);
const user = createStratumInstance(OuterTraits);
model.strata.set("definition", definition);
model.strata.set("user", user);
definition.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
// Order is important here:
// - b (with removal)
// - a
definition.inner[0].foo = "b";
definition.inner[0].bar = 42; // indicates removed, according to InnerTraits.isRemoval.
definition.inner[0].baz = true;
definition.inner[1].foo = "a";
definition.inner[1].bar = 1;
user.inner = [
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits),
createStratumInstance(InnerTraits)
];
// Order:
// - b (removed in "definition")
// - c
// - a (exists in "definition")
user.inner[0].foo = "b";
user.inner[0].bar = 100;
user.inner[1].foo = "c";
user.inner[1].bar = 3;
user.inner[2].foo = "a";
user.inner[2].bar = 11;
// So we expect this order:
// - a (because it is in "definition")
// - b (removed in "definition" so treated as from "user")
// - c (from "user")
expect(model.inner.length).toEqual(3);
expect(model.inner[0].foo).toEqual("a");
expect(model.inner[0].bar).toEqual(11);
expect(model.inner[1].foo).toEqual("b");
expect(model.inner[1].bar).toEqual(100);
expect(model.inner[2].foo).toEqual("c");
expect(model.inner[2].bar).toEqual(3);
expect(model.inner[2].baz).toBeUndefined();
});
it("updates to reflect new strata added after evaluation", function () {
const terria = new Terria();
const model = new TestModel("test", terria);
const newObj = model.addObject("user", "inner", "test");
expect(newObj).toBeDefined();
if (newObj) {
expect(newObj.foo).toBe("test");
newObj.setTrait("user", "bar", 4);
expect(newObj.bar).toBe(4);
newObj.setTrait("definition", "baz", true);
expect(newObj.baz).toBe(true);
}
expect(model.inner.length).toBe(1);
expect(model.inner[0].foo).toBe("test");
expect(model.inner[0].bar).toBe(4);
expect(model.inner[0].baz).toBe(true);
});
it("handles idProperty = index", function () {
const terria = new Terria();
const model = new TestModel("test", terria);
// Create new object and set removal
const firstObject = model.addObject("definition", "innerByIndex");
firstObject?.setTrait("definition", "foo", "definition");
firstObject?.setTrait("user", "bar", 10);
expect(model.innerByIndex.length).toBe(1);
expect(model.innerByIndex[0].foo).toBe("definition");
// Remove first object by setting bar = 42
firstObject?.setTrait("user", "bar", 42);
expect(model.innerByIndex.length).toBe(0);
// Add new object to user
const secondObject = model.addObject("user", "innerByIndex");
secondObject?.setTrait("user", "foo", "user");
secondObject?.setTrait("user", "bar", 10);
expect(model.innerByIndex.length).toBe(1);
expect(model.innerByIndex[0].foo).toBe("user");
// Add new object to definition
const thirdObject = model.addObject("definition", "innerByIndex");
thirdObject?.setTrait("user", "foo", "definition");
thirdObject?.setTrait("user", "bar", 10);
expect(model.innerByIndex.length).toBe(2);
expect(model.innerByIndex[0].foo).toBe("user");
expect(model.innerByIndex[1].foo).toBe("definition");
// Add new object to user
const fourthObject = model.addObject("user", "innerByIndex");
fourthObject?.setTrait("user", "foo", "user2");
fourthObject?.setTrait("user", "bar", 20);
expect(model.innerByIndex.length).toBe(3);
expect(model.innerByIndex[0].foo).toBe("user");
expect(model.innerByIndex[1].foo).toBe("definition");
expect(model.innerByIndex[2].foo).toBe("user2");
});
it("updates to reflect new strata added after evaluation (with no merge)", function () {
const terria = new Terria();
const model = new TestModelNoMerge("test", terria);
const newObj = model.addObject("user", "inner", "test");
expect(newObj).toBeDefined();
if (newObj) {
expect(newObj.foo).toBe("test");
newObj.setTrait("user", "bar", 4);
expect(newObj.bar).toBe(4);
newObj.setTrait("definition", "baz", true);
expect(newObj.baz).toBeUndefined();
newObj.setTrait("user", "baz", true);
expect(newObj.baz).toBeTruthy();
}
expect(model.inner.length).toBe(1);
expect(model.inner[0].foo).toBe("test");
expect(model.inner[0].bar).toBe(4);
expect(model.inner[0].baz).toBe(true);
});
it("no error when a model has 0 strata and merge = top", function () {
const terria = new Terria();
const model = new TestModelMergeTop("test", terria);
expect(model.inner.length).toBe(0);
});
it("updates to reflect new strata added after evaluation (with merge = top)", function () {
const terria = new Terria();
const model = new TestModelMergeTop("test", terria);
updateModelFromJson(model, CommonStrata.definition, {
inner: [
{ foo: "this-exists-in-definition-and-user", bar: 1, baz: false },
{ foo: "this-exists-in-definition", bar: 2, baz: true },
{ foo: "this-also-exists-in-definition-and-user" }
]
});
expect(model.inner.length).toBe(3);
expect(model.inner[0].foo).toBe("this-exists-in-definition-and-user");
expect(model.inner[0].bar).toBe(1);
expect(model.inner[0].baz).toBe(false);
expect(model.inner[1].foo).toBe("this-exists-in-definition");
expect(model.inner[1].bar).toBe(2);
expect(model.inner[1].baz).toBe(true);
expect(model.inner[2].foo).toBe("this-also-exists-in-definition-and-user");
updateModelFromJson(model, CommonStrata.user, {
inner: [
{ foo: "this-exists-in-definition-and-user", baz: true },
{ foo: "this-also-exists-in-definition-and-user", bar: 3, baz: true }
]
});
expect(model.inner.length).toBe(2);
expect(model.inner[0].foo).toBe("this-exists-in-definition-and-user");
expect(model.inner[0].bar).toBe(1);
expect(model.inner[0].baz).toBe(true);
expect(model.inner[1].foo).toBe("this-also-exists-in-definition-and-user");
expect(model.inner[1].bar).toBe(3);
expect(model.inner[1].baz).toBe(true);
});
});