jinaga
Version:
Data management for web and mobile applications.
558 lines (480 loc) • 20.4 kB
text/typescript
import { Jinaga, JinagaTest } from "../../src";
import { Company, Manager, ManagerName, ManagerTerminated, Office, OfficeClosed, OfficeReopened, President, User, UserName, model } from "../companyModel";
describe("specification watch", () => {
let creator: User;
let emptyCompany: Company;
let company: Company;
let office: Office;
let closedOffice: Office;
let closure: OfficeClosed;
let j: Jinaga;
beforeEach(() => {
creator = new User("--- PUBLIC KEY GOES HERE ---");
emptyCompany = new Company(creator, "EmptyCo");
company = new Company(creator, "TestCo");
office = new Office(company, "TestOffice");
closedOffice = new Office(company, "ClosedOffice");
closure = new OfficeClosed(closedOffice, new Date());
j = JinagaTest.create({
initialState: [
creator,
emptyCompany,
company,
office,
closedOffice,
closure
]
});
});
it("should return no results when empty", async () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
);
const offices: string[] = [];
const officeObserver = j.watch(specification, emptyCompany, office => {
offices.push(j.hash(office));
});
await officeObserver.loaded();
officeObserver.stop();
expect(offices).toEqual([]);
});
it("should notify results when they previously existed", async () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
);
const offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
offices.push(j.hash(office));
});
await officeObserver.loaded();
officeObserver.stop();
expect(offices).toEqual([j.hash(office), j.hash(closedOffice)]);
});
it("should notify results when added", async () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
);
const offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
offices.push(j.hash(office));
});
await officeObserver.loaded();
const newOffice = new Office(company, "NewOffice");
await j.fact(newOffice);
officeObserver.stop();
expect(offices).toEqual([j.hash(office), j.hash(closedOffice), j.hash(newOffice)]);
});
it("should stop notifying results when stopped", async () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
);
const offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
offices.push(j.hash(office));
});
await officeObserver.loaded();
officeObserver.stop();
const newOffice = new Office(company, "NewOffice");
await j.fact(newOffice);
expect(offices).toEqual([j.hash(office), j.hash(closedOffice)]);
});
it("should not notify if stopped before load finishes", async () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
);
const offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
offices.push(j.hash(office));
});
officeObserver.stop();
const newOffice = new Office(company, "NewOffice");
await j.fact(newOffice);
await officeObserver.loaded();
expect(offices).toEqual([]);
});
it("should not notify results related to a different starting point", async () => {
const specification = model.given(Company).match((company, facts) =>
facts.ofType(Office)
.join(office => office.company, company)
);
const offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
offices.push(j.hash(office));
});
await officeObserver.loaded();
const newOfficeInOtherCompany = new Office(emptyCompany, "OfficeInOtherCompany");
await j.fact(newOfficeInOtherCompany);
officeObserver.stop();
// The array does not contain the new office.
expect(offices).toEqual([j.hash(office), j.hash(closedOffice)]);
});
it("should notify results when removed", async () => {
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 offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
const hash = j.hash(office);
offices.push(hash);
return () => {
offices.splice(offices.indexOf(hash), 1);
}
});
await officeObserver.loaded();
await j.fact(new OfficeClosed(office, new Date()));
officeObserver.stop();
expect(offices).toEqual([]);
});
it("should execute nested existential conditions", async () => {
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 offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
const hash = j.hash(office);
offices.push(hash);
return () => {
offices.splice(offices.indexOf(hash), 1);
}
});
await officeObserver.loaded();
officeObserver.stop();
expect(offices).toEqual([j.hash(office)]);
});
it("should notify results when re-added", async () => {
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 offices: string[] = [];
const officeObserver = j.watch(specification, company, office => {
const hash = j.hash(office);
offices.push(hash);
return () => {
offices.splice(offices.indexOf(hash), 1);
}
});
await officeObserver.loaded();
await j.fact(new OfficeReopened(closure));
officeObserver.stop();
expect(offices).toEqual([j.hash(office), j.hash(closedOffice)]);
});
it("should notify child results when added", async () => {
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)
)
)
.select(office => ({
identifier: office.identifier,
president: facts.ofType(President)
.join(president => president.office, office)
}))
);
const offices: {
identifier: string,
president?: string
}[] = [];
const officeObserver = j.watch(specification, company, office => {
const model = {
identifier: office.identifier,
president: undefined as string | undefined
};
offices.push(model);
office.president.onAdded(president => {
model.president = j.hash(president);
});
});
await officeObserver.loaded();
expect(offices).toEqual([
{
identifier: office.identifier,
president: undefined
}
]);
const newPresident = new President(office, new User("--- PRESIDENT PUBLIC KEY ---"));
await j.fact(newPresident);
officeObserver.stop();
expect(offices).toEqual([
{
identifier: office.identifier,
president: j.hash(newPresident)
}
]);
});
it("should notify child results when existing", async () => {
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)
)
)
.select(office => ({
identifier: office.identifier,
president: facts.ofType(President)
.join(president => president.office, office)
}))
);
// Add the president before beginning the watch
const newPresident = new President(office, new User("--- PRESIDENT PUBLIC KEY ---"));
await j.fact(newPresident);
const offices: {
identifier: string,
president?: string
}[] = [];
const officeObserver = j.watch(specification, company, office => {
const model = {
identifier: office.identifier,
president: undefined as string | undefined
};
offices.push(model);
office.president.onAdded(president => {
model.president = j.hash(president);
});
});
await officeObserver.loaded();
expect(offices).toEqual([
{
identifier: office.identifier,
president: j.hash(newPresident)
}
]);
officeObserver.stop();
});
it("should notify grandchild results when existing", async () => {
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)
)
)
.select(office => ({
identifier: office.identifier,
president: facts.ofType(President)
.join(president => president.office, office)
.select(president => ({
hash: j.hash(president),
name: facts.ofType(UserName)
.join(userName => userName.user, president.user)
.notExists(userName => facts.ofType(UserName)
.join(next => next.prior, userName)
)
}))
}))
);
// Add the president and their name before beginning the watch
const presidentUser = new User("--- PRESIDENT PUBLIC KEY ---");
const newPresident = new President(office, presidentUser);
await j.fact(newPresident);
const presidentName = new UserName(presidentUser, "Mr. President", []);
await j.fact(presidentName);
const offices: {
identifier: string,
presidentHash?: string
presidentName?: string
}[] = [];
const officeObserver = j.watch(specification, company, office => {
const model = {
identifier: office.identifier,
presidentHash: undefined as string | undefined,
presidentName: undefined as string | undefined
};
offices.push(model);
office.president.onAdded(president => {
model.presidentHash = president.hash;
president.name.onAdded(name => {
model.presidentName = name.value;
});
});
});
await officeObserver.loaded();
expect(offices).toEqual([
{
identifier: office.identifier,
presidentHash: j.hash(newPresident),
presidentName: "Mr. President"
}
]);
officeObserver.stop();
});
it("should notify when manager and name added", async () => {
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)
)
.select(office => ({
office: office,
managers: facts.ofType(Manager)
.join(manager => manager.office, office)
.notExists(manager =>
facts.ofType(ManagerTerminated)
.join(managerTerminated => managerTerminated.manager, manager)
)
.select(manager => ({
manager: manager,
name: facts.ofType(ManagerName)
.join(managerName => managerName.manager, manager)
.notExists(managerName =>
facts.ofType(ManagerName)
.join(next => next.prior, managerName)
)
}))
}))
);
interface ManagerModel {
employeeNumber: number;
name?: string;
}
interface OfficeModel {
identifier: string;
managers: ManagerModel[];
}
const offices: OfficeModel[] = [];
const officeObserver = j.watch(specification, company, office => {
const model: OfficeModel = {
identifier: office.office.identifier,
managers: []
};
offices.push(model);
office.managers.onAdded(manager => {
const managerModel: ManagerModel = {
employeeNumber: manager.manager.employeeNumber,
name: undefined
};
model.managers.push(managerModel);
manager.name.onAdded(name => {
managerModel.name = name.value;
});
});
});
await officeObserver.loaded();
expect(offices).toEqual([
{
identifier: "TestOffice",
managers: []
}
]);
const manager = await j.fact(new Manager(office, 123));
expect(offices).toEqual([
{
identifier: "TestOffice",
managers: [
{
employeeNumber: 123,
name: undefined
}
]
}
]);
await j.fact(new ManagerName(manager, "Test Manager", []));
expect(offices).toEqual([
{
identifier: "TestOffice",
managers: [
{
employeeNumber: 123,
name: "Test Manager"
}
]
}
]);
officeObserver.stop();
});
it("should notify children of identity when added", async () => {
// Given an office, select an object returning both the presidents and the managers
const specification = model.given(Office).select((office, facts) => ({
id: j.hash(office),
presidents: facts.ofType(President)
.join(president => president.office, office)
.select(president => j.hash(president)),
managers: facts.ofType(Manager)
.join(manager => manager.office, office)
.select(manager => j.hash(manager))
}));
interface OfficeViewModel {
id: string;
presidents: string[];
managers: string[];
}
// Watch the office for changes
const offices: OfficeViewModel[] = [];
const officeObserver = j.watch(specification, office, projection => {
const model: OfficeViewModel = {
id: projection.id,
presidents: [],
managers: []
};
offices.push(model);
// When a president is added, add it to the list
projection.presidents.onAdded(president => {
model.presidents.push(president);
});
// When a manager is added, add it to the list
projection.managers.onAdded(manager => {
model.managers.push(manager);
});
});
// Wait for the initial load to complete
await officeObserver.loaded();
// Add a president
const president = await j.fact(new President(office, creator));
// Add a manager
const manager = await j.fact(new Manager(office, 123));
// Stop watching
officeObserver.stop();
// Verify that the office was loaded
expect(offices).toEqual([
{
id: j.hash(office),
presidents: [j.hash(president)],
managers: [j.hash(manager)]
}
]);
});
});