CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/sync/table/test/synctable.0.test.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { SyncTable } from "../synctable";
7
import { once } from "@cocalc/util/async-utils";
8
import { ClientTest } from "./client-test";
9
10
describe("tests public API of a system_notifications SyncTable", () => {
11
let synctable: SyncTable;
12
const notifications = [
13
{
14
id: "123e4567-e89b-12d3-a456-426655440000",
15
time: new Date(),
16
text: "This is a message.",
17
priority: "low",
18
},
19
{
20
id: "123e4567-e89b-12d3-a456-426655440001",
21
time: new Date(),
22
text: "This is a second message.",
23
priority: "high",
24
},
25
];
26
const client = new ClientTest(notifications);
27
const query = {
28
system_notifications: [
29
{ id: null, time: null, text: null, priority: null },
30
],
31
};
32
33
test("create the synctable", async () => {
34
// last 0 is to disable change throttling, which messes up jest.
35
synctable = new SyncTable(query, [], client, 0);
36
expect(synctable.get_state()).toBe("disconnected");
37
await once(synctable, "connected");
38
});
39
40
test("get query the synctable", () => {
41
const x = synctable.get();
42
if (x == null) {
43
throw Error("must be defined since synctable is connected");
44
}
45
expect(x.toJS()).toEqual({
46
[notifications[0].id]: notifications[0],
47
[notifications[1].id]: notifications[1],
48
});
49
});
50
51
test("get_one query the synctable", () => {
52
const x = synctable.get_one();
53
if (x == null) {
54
throw Error("must be defined since synctable is connected");
55
}
56
expect(x.toJS()).toEqual(notifications[0]);
57
});
58
59
test("get_one query for one primary key", () => {
60
const x = synctable.get_one(notifications[0].id);
61
if (x == null) {
62
throw Error("must be defined since synctable is connected");
63
}
64
expect(x.toJS()).toEqual(notifications[0]);
65
expect(x).toBe(synctable.get(notifications[0].id));
66
});
67
68
test("get_one query for other primary key", () => {
69
const x = synctable.get_one(notifications[1].id);
70
if (x == null) {
71
throw Error("must be defined since synctable is connected");
72
}
73
expect(x.toJS()).toEqual(notifications[1]);
74
// also the get is the same when there is an arg.
75
expect(x).toBe(synctable.get(notifications[1].id));
76
});
77
78
test("get_one query for other primary key", () => {
79
const x = synctable.get_one("foo");
80
expect(x).toBe(undefined);
81
// also the get is the same when there is an arg.
82
expect(x).toBe(synctable.get("foo"));
83
});
84
85
test("does not have uncommitted changes", () => {
86
expect(synctable.has_uncommitted_changes()).toBe(false);
87
});
88
89
test("making change to invalid field raises error", () => {
90
expect(() => synctable.set({ foobar: "medium" })).toThrow(
91
"Cannot coerce: no field 'foobar' in table 'system_notifications'"
92
);
93
});
94
95
test("making change to field not in query (even though it is valid) raises error", () => {
96
expect(() => synctable.set({ done: true })).toThrow("coerce");
97
});
98
99
test("make change; then has uncommitted changes", () => {
100
expect(client.set_queries.length).toBe(0);
101
synctable.set({ id: notifications[1].id, priority: "medium" });
102
// Set does not cause a database write (via save).
103
expect(client.set_queries.length).toBe(0);
104
expect(synctable.has_uncommitted_changes()).toBe(true);
105
});
106
107
test("save change; then does not have uncommitted changes", async () => {
108
await synctable.save();
109
// Set causes a database write:
110
expect(client.set_queries.length).toBe(1);
111
expect(synctable.has_uncommitted_changes()).toBe(false);
112
});
113
114
test("waiting for a condition to be satisfied", async () => {
115
function satisfy_condition() {
116
synctable.set({ id: notifications[1].id, priority: "high" });
117
synctable.save();
118
}
119
120
function until(s) {
121
const priority = s.get(notifications[1].id).get("priority");
122
return priority === "high";
123
}
124
125
const p = synctable.wait(until);
126
satisfy_condition();
127
await p;
128
});
129
130
// @ts-ignore
131
test("a change event", (done) => {
132
synctable.once("change", (keys) => {
133
expect(keys).toEqual(["123e4567-e89b-12d3-a456-426655440001"]);
134
done();
135
});
136
synctable.set({ id: notifications[1].id, priority: "medium" });
137
synctable.save();
138
});
139
140
test("closing the synctable", async () => {
141
const n = client.set_queries.length;
142
expect(synctable.get_state()).toBe("connected");
143
synctable.set({ id: notifications[1].id, priority: "low" });
144
expect(synctable.has_uncommitted_changes()).toBe(true);
145
synctable.close();
146
await once(synctable, "closed");
147
expect(client.set_queries.length).toBe(n + 1); // final save happened
148
});
149
150
test("closed synctable -- has the right state", () => {
151
expect(synctable.get_state()).toBe("closed");
152
});
153
154
test("closed synctable -- most public API functions throw an error", async () => {
155
expect(() => synctable.set({ priority: "medium" })).toThrow(
156
"can't set until table is initialized"
157
);
158
expect(() => synctable.get()).toThrow("closed");
159
expect(() => synctable.get_one()).toThrow("table not yet initialized");
160
expect(synctable.has_uncommitted_changes()).toBe(false); // does not throw
161
await synctable.close();
162
try {
163
await synctable.wait(() => true);
164
} catch (err) {
165
expect(err.toString()).toContain("closed");
166
}
167
try {
168
await synctable.save();
169
} catch (err) {
170
expect(err.toString()).toContain("closed");
171
}
172
});
173
174
// some errors...
175
176
test("try create synctable with an invalid query and get exception", () => {
177
const invalid_query = {
178
// invalid since missing id primary key.
179
system_notifications: [{ time: null, text: null, priority: null }],
180
};
181
expect(() => new SyncTable(invalid_query, [], client, 0)).toThrow(
182
"primary key"
183
);
184
});
185
186
test("try create synctable with another invalid query and get exception", () => {
187
const invalid_query = {
188
// invalid since extra foo key.
189
system_notifications: [
190
{ id: null, foo: null, time: null, text: null, priority: null },
191
],
192
};
193
expect(() => new SyncTable(invalid_query, [], client, 0)).toThrow(
194
"field in the schema"
195
);
196
});
197
});
198
199