UNPKG

jinaga

Version:

Data management for web and mobile applications.

558 lines (480 loc) 20.4 kB
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)] } ]); }); });