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/util/misc.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 seedrandom from "seedrandom";
7
import * as misc from "./misc";
8
9
describe("academic domain", () => {
10
const ia = misc.isAcademic;
11
12
test("denies non academics", () => {
13
expect(ia("[email protected]")).toBe(false);
14
expect(ia("[email protected]")).toBe(false);
15
expect(ia("[email protected]")).toBe(false);
16
expect(ia("[email protected]")).toBe(false);
17
expect(ia("[email protected]")).toBe(false);
18
});
19
20
test("detects academics", () => {
21
expect(ia("[email protected]")).toBe(true);
22
expect(ia("[email protected]")).toBe(true);
23
expect(ia("[email protected]")).toBe(true);
24
expect(ia("[email protected]")).toBe(true);
25
expect(ia("[email protected]")).toBe(true);
26
});
27
});
28
29
describe("rpad_html", () => {
30
const rp = misc.rpad_html;
31
const round1 = misc.round1;
32
test("0", () => expect(rp(0, 3)).toEqual("  0"));
33
test("99", () => expect(rp(99, 3)).toEqual(" 99"));
34
test("4444-5", () => expect(rp(4444, 5)).toEqual(" 4444"));
35
test("6666-4", () => expect(rp(6666, 4)).toEqual("6666"));
36
test("1000-4", () => expect(rp(1000, 4)).toEqual("1000"));
37
test("1000-3", () => expect(rp(1000, 3)).toEqual("1000"));
38
test("pi-1", () => expect(rp(3.1415, 4, round1)).toEqual(" 3.1"));
39
});
40
41
describe("path_split", () => {
42
const ps = misc.path_split;
43
44
test("full path", () =>
45
expect(ps("foo/bar")).toEqual({ head: "foo", tail: "bar" }));
46
47
test("filename", () =>
48
expect(ps("foo.bar.baz")).toEqual({ head: "", tail: "foo.bar.baz" }));
49
50
test("dirname", () => expect(ps("foo/")).toEqual({ head: "foo", tail: "" }));
51
52
test("abspath", () =>
53
expect(ps("/HOME/USER/DIR")).toEqual({
54
head: "/HOME/USER",
55
tail: "DIR",
56
}));
57
58
test("ROOT", () => expect(ps("/")).toEqual({ head: "", tail: "" }));
59
});
60
61
describe("contains_url", () => {
62
const cu = misc.contains_url;
63
64
test("normal html is fine", () =>
65
expect(cu("<h2>foo</h2><div>bar</div>")).toBe(false));
66
67
test("detects URLs", () => {
68
expect(cu("<p><a href='http://foo.com'>click me</a></p>")).toBe(true);
69
expect(cu("abc bar.com xyz")).toBe(true);
70
expect(cu("abc www.buy.me xyz")).toBe(true);
71
});
72
});
73
74
describe("date object some time ago", () => {
75
test("roughly 10 mins ago", () => {
76
const res = misc.minutes_ago(10);
77
const diff = new Date().getTime() - res.getTime();
78
expect(diff).toBeLessThan(10 * 60 * 1000 + 100);
79
expect(diff).toBeGreaterThan(10 * 60 * 1000 - 100);
80
});
81
test("2 months ago", () => {
82
const res = misc.months_ago(2);
83
const diff = new Date().getTime() - res.getTime();
84
expect(diff).toBeLessThan(2 * 31 * 24 * 60 * 60 * 1000);
85
expect(diff).toBeGreaterThan(2 * 30 * 24 * 60 * 60 * 1000);
86
});
87
});
88
89
describe("how_long_ago_m", () => {
90
test("10 min ago by Date", () => {
91
const past: Date = misc.minutes_ago(10);
92
const diff = misc.how_long_ago_m(past);
93
expect(diff).toBeLessThan(10.1);
94
expect(diff).toBeGreaterThan(9.9);
95
});
96
97
test("10 min ago by timestamp", () => {
98
const past: number = misc.minutes_ago(10).getTime();
99
const diff = misc.how_long_ago_m(past);
100
expect(diff).toBeLessThan(10.1);
101
expect(diff).toBeGreaterThan(9.9);
102
});
103
});
104
105
describe("json patch test", () => {
106
const j = misc.test_valid_jsonpatch;
107
test("empty array is fine", () => expect(j([])).toBe(true));
108
test("a complete example is fine", () => {
109
// taken from https://jsonpatch.com/
110
const patch = [
111
{ op: "add", path: "/biscuits/1", value: { name: "Ginger Nut" } },
112
{ op: "remove", path: "/biscuits" },
113
{ op: "remove", path: "/biscuits/0" },
114
{ op: "replace", path: "/biscuits/0/name", value: "Chocolate Digestive" },
115
{ op: "copy", from: "/biscuits/0", path: "/best_biscuit" },
116
{ op: "move", from: "/biscuits", path: "/cookies" },
117
{ op: "test", path: "/best_biscuit/name", value: "Choco Leibniz" },
118
];
119
120
expect(j(patch)).toBe(true);
121
});
122
test("fails with broken examples", () => {
123
expect(
124
j({ op: "add", path: "/biscuits/1", value: { name: "Ginger Nut" } }),
125
).toBe(false);
126
expect(j([{ opp: "remove", path: "/biscuits" }])).toBe(false);
127
expect(j([{ path: "/biscuits/0" }])).toBe(false);
128
expect(j([{ op: "replacce", path: "/biscuits/0/name" }])).toBe(false);
129
});
130
});
131
132
test("firstLetterUppercase", () => {
133
const s = misc.firstLetterUppercase;
134
expect(s(undefined)).toBe("");
135
expect(s("")).toBe("");
136
expect(s("a")).toBe("A");
137
expect(s("abc")).toBe("Abc");
138
expect(s("ABC")).toBe("ABC");
139
expect(s("aBC")).toBe("ABC");
140
});
141
142
test("hexColorToRGBA", () => {
143
const c1 = misc.hexColorToRGBA("#000000");
144
expect(c1).toEqual("rgb(0,0,0)");
145
const c2 = misc.hexColorToRGBA("#ffffff", 0.5);
146
expect(c2).toEqual("rgba(255,255,255,0.5)");
147
});
148
149
test("strictMod", () => {
150
const mod = misc.strictMod;
151
expect(mod(0, 3)).toBe(0);
152
expect(mod(1, 3)).toBe(1);
153
expect(mod(-2, 3)).toBe(1);
154
expect(mod(-3, 3)).toBe(0);
155
expect(mod(-1, 10)).toBe(9);
156
});
157
158
test("EDITOR_PREFIX", () => {
159
// don't change it, because codebase is not using the global variable everywhere
160
expect(misc.EDITOR_PREFIX).toBe("editor-");
161
});
162
163
describe("test code for displaying numbers as currency with 2 or sometimes 3 decimals of precision", () => {
164
const { currency } = misc;
165
it("displays 1.23", () => {
166
expect(currency(1.23)).toBe("$1.23");
167
});
168
169
it("displays 0.0094 with 3 digits (not 2), but only because n is less than 0.01", () => {
170
expect(currency(0.0094)).toBe("$0.009");
171
});
172
173
it("displays 0.1941 with 2, because n is not less than 0.01", () => {
174
expect(currency(0.1941)).toBe("$0.19");
175
});
176
it("displays 0.01941 with 2, because n is not less than 0.01", () => {
177
expect(currency(0.01941)).toBe("$0.02");
178
});
179
180
it("displays 0.0941 with 2 digits if second argument specifies that", () => {
181
expect(currency(0.0941, 2)).toBe("$0.09");
182
});
183
184
it("displays 0.086 with 2 digits if second argument specifies that, and it is rounded to nearest", () => {
185
expect(currency(0.086, 2)).toBe("$0.09");
186
});
187
188
it("displays 0.083 with 2 digits if second argument specifies that, and it is rounded to nearest (NOT up)", () => {
189
expect(currency(0.083, 2)).toBe("$0.08");
190
});
191
192
it("always includes at least 2 decimals", () => {
193
expect(currency(10)).toBe("$10.00");
194
});
195
});
196
197
describe("smallIntegerToEnglishWord", () => {
198
it("handles floats", () => {
199
expect(misc.smallIntegerToEnglishWord(1.2)).toBe(1.2);
200
});
201
202
it("handles 0", () => {
203
expect(misc.smallIntegerToEnglishWord(0)).toBe("zero");
204
});
205
206
it("handles 1", () => {
207
expect(misc.smallIntegerToEnglishWord(1)).toBe("one");
208
});
209
210
it("handles 17", () => {
211
expect(misc.smallIntegerToEnglishWord(17)).toBe("seventeen");
212
});
213
214
it("handles negative numbers", () => {
215
expect(misc.smallIntegerToEnglishWord(-1)).toBe(-1);
216
});
217
});
218
219
describe("test round2up and round2down for various inputs", () => {
220
const { round2up, round2down } = misc;
221
it("round2up tests -- uses the decimal representation (not internal binary))", () => {
222
// see https://github.com/sagemathinc/cocalc/issues/7220
223
expect(round2up(20.01)).toBe(20.01);
224
expect(round2up(20.011)).toBe(20.02);
225
expect(round2up(20.01999)).toBe(20.02);
226
expect(round2up(4.73)).toBe(4.73);
227
expect(round2up(4.731)).toBe(4.74);
228
expect(round2up(4.736)).toBe(4.74);
229
});
230
231
it("round2down tests -- uses the decimal representation (not internal binary))", () => {
232
// see https://github.com/sagemathinc/cocalc/issues/7220
233
expect(round2down(20.01)).toBe(20.01);
234
expect(round2down(20.011)).toBe(20.01);
235
expect(round2down(20.019)).toBe(20.01);
236
expect(round2down(4.73)).toBe(4.73);
237
expect(round2down(4.731)).toBe(4.73);
238
expect(round2down(4.736)).toBe(4.73);
239
});
240
241
it("a random test of 1000 cases", () => {
242
let seed = "my-seed";
243
let rng = seedrandom(seed);
244
245
for (let i = 0; i < 1000; i++) {
246
let randomNum = rng(); // Returns a number between 0 and 1
247
// Perform your tests with randomNum
248
// For example:
249
expect(round2up(randomNum)).toBeGreaterThanOrEqual(randomNum);
250
expect(round2up(randomNum)).toBeLessThan(randomNum + 0.01);
251
expect(round2down(randomNum)).toBeLessThanOrEqual(randomNum);
252
expect(round2down(randomNum)).toBeGreaterThan(randomNum - 0.01);
253
}
254
});
255
});
256
257
describe("numToOrdinal", () => {
258
const { numToOrdinal } = misc;
259
it("appends proper suffixes", () => {
260
expect(numToOrdinal(1)).toBe("1st");
261
expect(numToOrdinal(2)).toBe("2nd");
262
expect(numToOrdinal(3)).toBe("3rd");
263
expect(numToOrdinal(4)).toBe("4th");
264
expect(numToOrdinal(5)).toBe("5th");
265
expect(numToOrdinal(6)).toBe("6th");
266
expect(numToOrdinal(7)).toBe("7th");
267
expect(numToOrdinal(8)).toBe("8th");
268
expect(numToOrdinal(9)).toBe("9th");
269
expect(numToOrdinal(10)).toBe("10th");
270
expect(numToOrdinal(11)).toBe("11th");
271
expect(numToOrdinal(12)).toBe("12th");
272
expect(numToOrdinal(13)).toBe("13th");
273
expect(numToOrdinal(21)).toBe("21st");
274
expect(numToOrdinal(22)).toBe("22nd");
275
expect(numToOrdinal(23)).toBe("23rd");
276
expect(numToOrdinal(24)).toBe("24th");
277
expect(numToOrdinal(42)).toBe("42nd");
278
expect(numToOrdinal(101)).toBe("101st");
279
expect(numToOrdinal(202)).toBe("202nd");
280
expect(numToOrdinal(303)).toBe("303rd");
281
expect(numToOrdinal(1000)).toBe("1000th");
282
});
283
it("Falls back in other cases", () => {
284
expect(numToOrdinal(-1)).toBe("-1th");
285
});
286
});
287
288
describe("hoursToTimeIntervalHuman", () => {
289
const { hoursToTimeIntervalHuman } = misc;
290
it("converts nicely", () => {
291
expect(hoursToTimeIntervalHuman(1)).toBe("1 hour");
292
expect(hoursToTimeIntervalHuman(13.333)).toBe("13.3 hours");
293
expect(hoursToTimeIntervalHuman(13.888)).toBe("13.9 hours");
294
expect(hoursToTimeIntervalHuman(24)).toBe("1 day");
295
expect(hoursToTimeIntervalHuman(24 * 7)).toBe("1 week");
296
expect(hoursToTimeIntervalHuman(2)).toBe("2 hours");
297
expect(hoursToTimeIntervalHuman(2 * 24)).toBe("2 days");
298
expect(hoursToTimeIntervalHuman(5 * 7 * 24)).toBe("5 weeks");
299
expect(hoursToTimeIntervalHuman(2.5111 * 24)).toBe("2.5 days");
300
expect(hoursToTimeIntervalHuman(2.5111 * 24 * 7)).toBe("2.5 weeks");
301
});
302
});
303
304
describe("tail", () => {
305
const s = `
306
foo
307
bar
308
baz
309
abc
310
xyz
311
test 123`;
312
const { tail } = misc;
313
it("return the last 3 lines", () => {
314
const t = tail(s, 3);
315
expect(t.split("\n").length).toEqual(3);
316
expect(t.startsWith("abc")).toBe(true);
317
});
318
it("return the last line", () => {
319
const t = tail("foo", 3);
320
expect(t.split("\n").length).toEqual(1);
321
expect(t).toEqual("foo");
322
});
323
});
324
325
describe("suggest_duplicate_filename", () => {
326
const dup = misc.suggest_duplicate_filename;
327
it("works with numbers", () => {
328
expect(dup("filename-1.test")).toBe("filename-2.test");
329
expect(dup("filename-99.test")).toBe("filename-100.test");
330
expect(dup("filename_99.test")).toBe("filename_100.test");
331
});
332
it("handles leading zeros", () => {
333
// handles leading 0's properly: https://github.com/sagemathinc/cocalc/issues/2973
334
expect(dup("filename_001.test")).toBe("filename_002.test");
335
});
336
it("works also without", () => {
337
expect(dup("filename-test")).toBe("filename-test-1");
338
expect(dup("filename-xxx.test")).toBe("filename-xxx-1.test");
339
expect(dup("bla")).toBe("bla-1");
340
expect(dup("foo.bar")).toBe("foo-1.bar");
341
});
342
it("also works with weird corner cases", () => {
343
expect(dup("asdf-")).toBe("asdf--1");
344
});
345
});
346
347