jinaga
Version:
Data management for web and mobile applications.
240 lines (212 loc) • 8.59 kB
text/typescript
import { describeSpecification, invertSpecification, SpecificationInverse, SpecificationOf } from "../../src";
import { Company, model, Office, OfficeClosed, OfficeReopened, President, User } from "../companyModel";
describe("specification inverse", () => {
it("should invert successor", () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
);
const inverses = fromSpecification(specification);
expect(inverses).toEqual([`
(u1: Office) {
p1: Company [
p1 = u1->company: Company
]
} => u1`
]);
});
it("should invert predecessor", () => {
const specification = model.given(Office).match((office, facts) =>
facts.ofType(Company)
.join(company => company, office.company)
);
const inverses = fromSpecification(specification);
// When the predecessor is created, it does not have a successor yet.
expect(inverses).toEqual([]);
});
it("should invert predecessor of successor", () => {
const specification = model.given(Office).match((office, facts) =>
facts.ofType(President)
.join(president => president.office, office)
.selectMany(president =>
facts.ofType(User)
.join(user => user, president.user)
)
);
const inverses = fromSpecification(specification);
// Expect the inverse to filter out the specification starting from the other predecessor.
expect(inverses).toEqual([`
(u1: President) {
p1: Office [
p1 = u1->office: Office
]
u2: User [
u2 = u1->user: User
]
} => u2`
]);
});
it("should invert negative existential condition", () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
.notExists(office =>
facts.ofType(OfficeClosed)
.join(officeClosed => officeClosed.office, office)
)
);
const inverses = invertSpecification(specification.specification);
const formatted = formatInverses(inverses);
expect(formatted).toEqual([`
(u1: Office) {
p1: Company [
p1 = u1->company: Company
]
} => u1`,`
(u2: Office.Closed) {
u1: Office [
u1 = u2->office: Office
]
p1: Company [
p1 = u1->company: Company
]
} => u1`
]);
expect(inverses[0].operation).toEqual("add");
expect(inverses[1].operation).toEqual("remove");
});
it("should invert positive existential condition", () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
.exists(office =>
facts.ofType(OfficeClosed)
.join(officeClosed => officeClosed.office, office)
)
);
const inverses = invertSpecification(specification.specification);
const formatted = formatInverses(inverses);
// The second inverse is not satisfiable because the OfficeClosed
// fact will not yet exist.
expect(formatted).toEqual([`
(u2: Office.Closed) {
u1: Office [
u1 = u2->office: Office
]
p1: Company [
p1 = u1->company: Company
]
} => u1`
]);
expect(inverses[0].operation).toEqual("add");
});
it("should invert restore pattern", () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
.notExists(office =>
facts.ofType(OfficeClosed)
.join(officeClosed => officeClosed.office, office)
.notExists(officeClosed =>
facts.ofType(OfficeReopened)
.join(officeReopened => officeReopened.officeClosed, officeClosed)
)
)
);
const inverses = invertSpecification(specification.specification);
const formatted = formatInverses(inverses);
expect(formatted).toEqual([`
(u1: Office) {
p1: Company [
p1 = u1->company: Company
]
} => u1`,`
(u2: Office.Closed) {
u1: Office [
u1 = u2->office: Office
]
p1: Company [
p1 = u1->company: Company
]
} => u1`,`
(u3: Office.Reopened) {
u2: Office.Closed [
u2 = u3->officeClosed: Office.Closed
]
u1: Office [
u1 = u2->office: Office
!E {
u2: Office.Closed [
u2->office: Office = u1
!E {
u3: Office.Reopened [
u3->officeClosed: Office.Closed = u2
]
}
]
}
]
p1: Company [
p1 = u1->company: Company
]
} => u1`
]);
expect(inverses[0].operation).toEqual("add");
expect(inverses[1].operation).toEqual("remove");
expect(inverses[2].operation).toEqual("add");
expect(inverses[0].parentSubset).toEqual(["p1"]);
expect(inverses[1].parentSubset).toEqual(["p1"]);
expect(inverses[2].parentSubset).toEqual(["p1"]);
expect(inverses[0].path).toEqual("");
expect(inverses[1].path).toEqual("");
expect(inverses[2].path).toEqual("");
expect(inverses[0].resultSubset).toEqual(["p1", "u1"]);
expect(inverses[1].resultSubset).toEqual(["p1", "u1"]);
expect(inverses[2].resultSubset).toEqual(["p1", "u1"]);
});
it("should invert child properties", () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
.select(office => ({
identifier: office.identifier,
president: facts.ofType(President)
.join(president => president.office, office)
}))
);
const inverses = fromSpecification(specification);
expect(inverses).toEqual([`
(u1: Office) {
p1: Company [
p1 = u1->company: Company
]
} => {
identifier = u1.identifier
president = {
u2: President [
u2->office: Office = u1
]
} => u2
}`,`
(u2: President) {
u1: Office [
u1 = u2->office: Office
]
p1: Company [
p1 = u1->company: Company
]
} => u2`
]);
});
});
function fromSpecification<T, U>(specification: SpecificationOf<T, U>) {
const inverses = invertSpecification(specification.specification);
return formatInverses(inverses);
}
function formatInverses(inverses: SpecificationInverse[]) {
return inverses
.map(i => {
const desription = describeSpecification(i.inverseSpecification, 3);
return "\n" + desription.substring(0, desription.length - 1);
});
}