@w-lfpup/jackrabbit 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.github/workflows/browsers.json +55 -0
  2. package/.github/workflows/browsers.macos.json +51 -0
  3. package/.github/workflows/browsers.windows.json +19 -0
  4. package/.github/workflows/tests.yml +13 -0
  5. package/README.md +41 -1
  6. package/browser/dist/logger.js +43 -0
  7. package/browser/dist/mod.js +25 -0
  8. package/browser/dist/queue.js +27 -0
  9. package/browser/dist/runner.js +20 -0
  10. package/{cli → browser}/package.json +1 -1
  11. package/browser/src/logger.ts +57 -0
  12. package/browser/src/mod.ts +30 -0
  13. package/browser/src/runner.ts +22 -0
  14. package/browser/tsconfig.json +11 -0
  15. package/browser/tsconfig.tsbuildinfo +1 -0
  16. package/browsers.json +38 -0
  17. package/core/dist/jackrabbit_types.d.ts +61 -27
  18. package/core/dist/mod.d.ts +2 -2
  19. package/core/dist/mod.js +1 -1
  20. package/core/dist/run_steps.d.ts +2 -2
  21. package/core/dist/run_steps.js +83 -67
  22. package/core/src/jackrabbit_types.ts +72 -28
  23. package/core/src/mod.ts +2 -8
  24. package/core/src/run_steps.ts +111 -80
  25. package/examples/hello_world/goodbye_world.ts +1 -1
  26. package/examples/hello_world/hello_world.ts +1 -1
  27. package/nodejs/dist/logger.js +164 -0
  28. package/nodejs/dist/mod.js +33 -0
  29. package/nodejs/dist/results.js +145 -0
  30. package/nodejs/dist/results_str.js +147 -0
  31. package/nodejs/dist/runner.js +17 -0
  32. package/nodejs/src/logger.ts +200 -0
  33. package/nodejs/src/mod.ts +39 -0
  34. package/nodejs/src/results.ts +239 -0
  35. package/{nodejs_cli → nodejs}/tsconfig.json +2 -1
  36. package/nodejs/tsconfig.tsbuildinfo +1 -0
  37. package/package.json +6 -4
  38. package/tests/dist/mod.d.ts +14 -3
  39. package/tests/dist/mod.js +33 -15
  40. package/tests/dist/test_error.test.d.ts +9 -0
  41. package/tests/dist/test_error.test.js +25 -0
  42. package/tests/dist/test_errors.test.d.ts +9 -0
  43. package/tests/dist/test_errors.test.js +27 -0
  44. package/tests/dist/test_fail.test.js +0 -2
  45. package/tests/dist/test_logger.d.ts +3 -2
  46. package/tests/dist/test_logger.js +5 -1
  47. package/tests/dist/test_pass.test.js +0 -2
  48. package/tests/src/mod.ts +31 -15
  49. package/tests/src/test_error.test.ts +32 -0
  50. package/tests/src/test_logger.ts +6 -1
  51. package/tests/tsconfig.tsbuildinfo +1 -1
  52. package/tsconfig.json +3 -2
  53. package/webdriver/dist/config.js +56 -0
  54. package/webdriver/dist/eventbus.js +18 -0
  55. package/webdriver/dist/listeners.js +21 -0
  56. package/webdriver/dist/logger.js +200 -0
  57. package/webdriver/dist/mod.js +35 -0
  58. package/webdriver/dist/results.js +190 -0
  59. package/webdriver/dist/results_str.js +167 -0
  60. package/webdriver/dist/routes.js +162 -0
  61. package/webdriver/dist/routes2.js +163 -0
  62. package/webdriver/dist/test_hangar.js +20 -0
  63. package/webdriver/dist/webdriver.js +272 -0
  64. package/webdriver/package.json +8 -0
  65. package/webdriver/src/config.ts +89 -0
  66. package/webdriver/src/eventbus.ts +104 -0
  67. package/webdriver/src/logger.ts +247 -0
  68. package/webdriver/src/mod.ts +45 -0
  69. package/webdriver/src/results.ts +311 -0
  70. package/webdriver/src/routes.ts +198 -0
  71. package/webdriver/src/test_hangar.ts +25 -0
  72. package/webdriver/src/webdriver.ts +373 -0
  73. package/{cli → webdriver}/tsconfig.json +1 -0
  74. package/webdriver/tsconfig.tsbuildinfo +1 -0
  75. package/cli/dist/cli.d.ts +0 -3
  76. package/cli/dist/cli.js +0 -8
  77. package/cli/dist/cli_types.d.ts +0 -7
  78. package/cli/dist/cli_types.js +0 -1
  79. package/cli/dist/config.d.ts +0 -5
  80. package/cli/dist/config.js +0 -6
  81. package/cli/dist/importer.d.ts +0 -7
  82. package/cli/dist/importer.js +0 -16
  83. package/cli/dist/logger.d.ts +0 -7
  84. package/cli/dist/logger.js +0 -88
  85. package/cli/dist/mod.d.ts +0 -6
  86. package/cli/dist/mod.js +0 -4
  87. package/cli/src/cli.ts +0 -17
  88. package/cli/src/cli_types.ts +0 -9
  89. package/cli/src/config.ts +0 -9
  90. package/cli/src/importer.ts +0 -25
  91. package/cli/src/logger.ts +0 -126
  92. package/cli/src/mod.ts +0 -7
  93. package/cli/tsconfig.tsbuildinfo +0 -1
  94. package/nodejs_cli/dist/mod.d.ts +0 -2
  95. package/nodejs_cli/dist/mod.js +0 -20
  96. package/nodejs_cli/src/mod.ts +0 -25
  97. package/nodejs_cli/tsconfig.tsbuildinfo +0 -1
  98. /package/{nodejs_cli → nodejs}/package.json +0 -0
@@ -0,0 +1,311 @@
1
+ import type { LoggerAction } from "../../core/dist/jackrabbit_types.js";
2
+ import type { WebdriverParams } from "./config.js";
3
+ import type { WebdriverActions } from "./eventbus.js";
4
+
5
+ export interface TestResults {
6
+ loggerStartAction: LoggerAction;
7
+ loggerEndAction: LoggerAction | undefined;
8
+ }
9
+
10
+ export interface ModuleResults {
11
+ loggerAction: LoggerAction;
12
+ fails: number;
13
+ errors: number;
14
+ expectedTests: number;
15
+ completedTests: number;
16
+ errorLogs: LoggerAction[];
17
+ testResults: (TestResults | undefined)[];
18
+ }
19
+
20
+ export interface CollectionResults {
21
+ loggerAction: LoggerAction;
22
+ fails: number;
23
+ errors: number;
24
+ expectedTests: number;
25
+ completedTests: number;
26
+ expectedModules: number;
27
+ completedModules: number;
28
+ errorLogs: LoggerAction[];
29
+ modules: (ModuleResults | undefined)[];
30
+ }
31
+
32
+ export interface RunResults {
33
+ fails: number;
34
+ errors: number;
35
+ startTime: number;
36
+ endTime: number;
37
+ testTime: number;
38
+ expectedTests: number;
39
+ completedTests: number;
40
+ expectedModules: number;
41
+ completedModules: number;
42
+ expectedCollections: number;
43
+ completedCollections: number;
44
+ errorLogs: WebdriverActions[];
45
+ webdriverParams: WebdriverParams;
46
+ collections: (CollectionResults | undefined)[];
47
+ }
48
+
49
+ export interface SessionResults {
50
+ fails: number;
51
+ errors: number;
52
+ runs: Map<string, RunResults>;
53
+ }
54
+
55
+ const SPACE = " ";
56
+
57
+ /*
58
+ Lots of nested loops because results a nested structure.
59
+ I'd rather see composition nested in one function
60
+ than have for loops spread across each function.
61
+ */
62
+
63
+ export function getResultsAsString(sessionResults: SessionResults): string {
64
+ const output: string[] = [];
65
+
66
+ logSessionErrors(output, sessionResults);
67
+
68
+ for (let [, result] of sessionResults.runs) {
69
+ if (logRunResults(output, result)) continue;
70
+
71
+ for (const collection of result.collections) {
72
+ if (logCollectionResult(output, collection)) continue;
73
+
74
+ if (collection)
75
+ for (const moduleResult of collection.modules) {
76
+ if (logModuleResult(output, moduleResult)) continue;
77
+
78
+ if (moduleResult)
79
+ for (const testResult of moduleResult.testResults) {
80
+ logTest(output, testResult);
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ logSummary(output, sessionResults);
87
+
88
+ return output.join("\n");
89
+ }
90
+
91
+ function logSessionErrors(output: string[], sessionResults: SessionResults) {
92
+ if (!sessionResults.runs.size) output.push("\nNo webdrivers were run.");
93
+
94
+ for (let [, result] of sessionResults.runs) {
95
+ if (result.errorLogs.length)
96
+ output.push(`\n${result.webdriverParams.title}`);
97
+ for (let errorAction of result.errorLogs) {
98
+ if ("session_error" === errorAction.type) {
99
+ output.push(`${SPACE}[session_error] ${errorAction.error}`);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ function logRunResults(output: string[], result: RunResults): boolean {
106
+ output.push(`\n${result.webdriverParams.title}`);
107
+
108
+ for (let errorAction of result.errorLogs) {
109
+ if ("log" !== errorAction.type) continue;
110
+ if ("run_error" !== errorAction.loggerAction.type) continue;
111
+
112
+ output.push(
113
+ `${SPACE.repeat(2)}[run_error] ${errorAction.loggerAction.error}`,
114
+ );
115
+ }
116
+ if (result.errorLogs.length) output.push("");
117
+
118
+ if (!result.collections && !result.expectedTests) {
119
+ output.push(`${SPACE}No tests were run.`);
120
+ return true;
121
+ }
122
+
123
+ // When everything goes right :3
124
+ if (
125
+ !result.fails &&
126
+ !result.errors &&
127
+ result.expectedTests === result.completedTests &&
128
+ result.expectedModules === result.completedModules &&
129
+ result.expectedCollections === result.completedCollections
130
+ ) {
131
+ output.push(`${SPACE}${result.completedTests} tests
132
+ ${SPACE}${result.completedModules} modules
133
+ ${SPACE}${result.completedCollections} collections`);
134
+ return true;
135
+ }
136
+
137
+ return false;
138
+ }
139
+
140
+ function logCollectionResult(
141
+ output: string[],
142
+ collection: CollectionResults | undefined,
143
+ ): boolean {
144
+ if (!collection) return true;
145
+
146
+ let { loggerAction } = collection;
147
+ if ("start_collection" !== loggerAction.type) return true;
148
+
149
+ output.push(`${SPACE}${loggerAction.collection_url}`);
150
+
151
+ // when everything in the collection goes right
152
+ if (
153
+ !collection.fails &&
154
+ !collection.errors &&
155
+ collection.expectedTests === collection.completedTests &&
156
+ collection.expectedModules === collection.completedModules
157
+ ) {
158
+ output.push(
159
+ `${SPACE.repeat(2)}${collection.expectedTests} tests
160
+ ${SPACE.repeat(2)}${loggerAction.expected_module_count} modules`,
161
+ );
162
+
163
+ return true;
164
+ }
165
+
166
+ for (let errorAction of collection.errorLogs) {
167
+ if ("collection_error" !== errorAction.type) continue;
168
+ output.push(`${SPACE.repeat(2)}[collection_error] ${errorAction.error}`);
169
+ }
170
+
171
+ if (collection.errorLogs.length) output.push("");
172
+
173
+ return false;
174
+ }
175
+
176
+ function logModuleResult(
177
+ output: string[],
178
+ module: ModuleResults | undefined,
179
+ ): boolean {
180
+ if (!module) return true;
181
+
182
+ let { loggerAction } = module;
183
+ if ("start_module" !== loggerAction.type) return true;
184
+
185
+ output.push(`${SPACE.repeat(2)}${loggerAction.module_name}`);
186
+
187
+ // when everything in the module goes right
188
+ if (
189
+ !module.fails &&
190
+ !module.errors &&
191
+ module.expectedTests === module.completedTests
192
+ ) {
193
+ output.push(`${SPACE.repeat(3)}${module.expectedTests} tests`);
194
+ return true;
195
+ }
196
+
197
+ output.push(
198
+ `${SPACE.repeat(3)}${module.completedTests - module.fails}/${module.expectedTests} tests`,
199
+ );
200
+
201
+ output.push("");
202
+ for (let errorAction of module.errorLogs) {
203
+ if ("module_error" !== errorAction.type) continue;
204
+ output.push(`${SPACE.repeat(3)}[module_error] ${errorAction.error}`);
205
+ }
206
+
207
+ if (module.errorLogs.length) output.push("");
208
+
209
+ return false;
210
+ }
211
+
212
+ function logTest(output: string[], test: TestResults | undefined) {
213
+ if (!test) return;
214
+
215
+ let { loggerStartAction, loggerEndAction } = test;
216
+ if ("start_test" !== loggerStartAction.type) return;
217
+
218
+ if ("test_error" === loggerEndAction?.type) {
219
+ let { test_name } = loggerStartAction;
220
+ output.push(
221
+ `${SPACE.repeat(3)}${test_name}
222
+ ${SPACE.repeat(4)}[error] ${loggerEndAction.error}`,
223
+ );
224
+ }
225
+
226
+ if ("end_test" === loggerEndAction?.type) {
227
+ let { assertions } = loggerEndAction;
228
+ const isAssertionArray = Array.isArray(assertions) && assertions.length;
229
+ const isAssertion =
230
+ !Array.isArray(assertions) &&
231
+ undefined !== assertions &&
232
+ null !== assertions;
233
+
234
+ if (isAssertion || isAssertionArray) {
235
+ let { test_name } = loggerStartAction;
236
+ output.push(`${SPACE.repeat(3)}${test_name}`);
237
+ }
238
+
239
+ if (isAssertion) {
240
+ output.push(`${SPACE.repeat(4)}- ${assertions}`);
241
+ }
242
+
243
+ if (isAssertionArray) {
244
+ for (const assertion of assertions) {
245
+ output.push(`${SPACE.repeat(4)}- ${assertion}`);
246
+ }
247
+ }
248
+ }
249
+
250
+ if (undefined === loggerEndAction) {
251
+ let { test_name } = loggerStartAction;
252
+
253
+ output.push(`${SPACE.repeat(3)}${test_name}
254
+ ${SPACE.repeat(4)}[incomplete]`);
255
+ }
256
+ }
257
+
258
+ function logSummary(output: string[], sessionResults: SessionResults) {
259
+ let status_with_color = blue("\u{2714} passed");
260
+
261
+ if (!isComplete(sessionResults))
262
+ status_with_color = gray("\u{2717} incomplete");
263
+ if (sessionResults.fails) status_with_color = yellow("\u{2717} failed");
264
+ if (sessionResults.errors) status_with_color = gray("\u{2717} errored");
265
+
266
+ let totalTime = 0;
267
+ let testTime = 0;
268
+ for (let [, run] of sessionResults.runs) {
269
+ totalTime += run.endTime - run.startTime;
270
+ testTime += run.testTime;
271
+ }
272
+
273
+ output.push(`
274
+ ${status_with_color}
275
+ duration: ${testTime.toFixed(4)} mS
276
+ total: ${totalTime.toFixed(4)} mS
277
+ `);
278
+ }
279
+
280
+ export function isComplete(sessionResults: SessionResults): boolean {
281
+ for (const [, result] of sessionResults.runs) {
282
+ if (
283
+ !result.expectedTests ||
284
+ !result.expectedModules ||
285
+ !result.expectedCollections
286
+ )
287
+ return false;
288
+ if (
289
+ result.expectedTests !== result.completedTests ||
290
+ result.expectedModules !== result.completedModules ||
291
+ result.expectedCollections !== result.completedCollections
292
+ )
293
+ return false;
294
+ }
295
+
296
+ return true;
297
+ }
298
+
299
+ // 39 - default foreground color
300
+ // 49 - default background color
301
+ function blue(text: string) {
302
+ return `\x1b[44m\x1b[97m${text}\x1b[0m`;
303
+ }
304
+
305
+ function yellow(text: string) {
306
+ return `\x1b[43m\x1b[97m${text}\x1b[0m`;
307
+ }
308
+
309
+ function gray(text: string) {
310
+ return `\x1b[100m\x1b[97m${text}\x1b[0m`;
311
+ }
@@ -0,0 +1,198 @@
1
+ import type { IncomingMessage, ServerResponse } from "http";
2
+ import type { EventBus } from "./eventbus.js";
3
+ import type { LoggerAction } from "../../core/dist/jackrabbit_types.js";
4
+
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { testHanger } from "./test_hangar.js";
8
+ import { ConfigInterface } from "./config.js";
9
+
10
+ let cwd = process.cwd();
11
+ const parentPath = path.join(import.meta.url.substring(5), "../../../");
12
+
13
+ const MIME_TYPES: Record<string, string> = {
14
+ octet: "application/octet-stream",
15
+ html: "text/html; charset=UTF-8",
16
+ js: "text/javascript",
17
+ json: "application/json",
18
+ css: "text/css",
19
+ png: "image/png",
20
+ jpg: "image/jpeg",
21
+ ico: "image/x-icon",
22
+ svg: "image/svg+xml",
23
+ };
24
+
25
+ export class Router {
26
+ #config: ConfigInterface;
27
+ #eventbus: EventBus;
28
+
29
+ constructor(config: ConfigInterface, eventbus: EventBus) {
30
+ this.#config = config;
31
+ this.#eventbus = eventbus;
32
+ }
33
+
34
+ get route() {
35
+ return this.#boundRoute;
36
+ }
37
+
38
+ #boundRoute = this.#route.bind(this);
39
+ async #route(req: IncomingMessage, res: ServerResponse) {
40
+ if (serveBadRequest(req, res)) return;
41
+ if (servePing(req, res)) return;
42
+ if (serveTestPage(req, res, this.#config)) return;
43
+ if (logAction(req, res, this.#eventbus)) return;
44
+
45
+ await serveFile(req, res);
46
+ }
47
+ }
48
+
49
+ function serveBadRequest(req: IncomingMessage, res: ServerResponse): boolean {
50
+ let { url } = req;
51
+ if (url) return false;
52
+
53
+ res.setHeader("Content-Type", "text/html");
54
+ res.writeHead(400);
55
+ res.end();
56
+
57
+ return true;
58
+ }
59
+
60
+ function servePing(req: IncomingMessage, res: ServerResponse): boolean {
61
+ let { url, method } = req;
62
+ if (url !== "/ping" || "GET" !== method) return false;
63
+
64
+ res.setHeader("Content-Type", "text/html");
65
+ res.writeHead(200);
66
+ res.end("The cookie train has arrived!");
67
+
68
+ return true;
69
+ }
70
+
71
+ function serveTestPage(
72
+ req: IncomingMessage,
73
+ res: ServerResponse,
74
+ config: ConfigInterface,
75
+ ): boolean {
76
+ let { url, method } = req;
77
+ if (url !== "/" || "GET" !== method) return false;
78
+
79
+ let hangar = testHanger({
80
+ jackrabbit_url: config.hostAndPort,
81
+ test_collections: process.argv.slice(3),
82
+ });
83
+
84
+ res.setHeader("Content-Type", "text/html");
85
+ res.writeHead(200);
86
+ res.end(hangar);
87
+
88
+ return true;
89
+ }
90
+
91
+ function logAction(
92
+ req: IncomingMessage,
93
+ res: ServerResponse,
94
+ eventbus: EventBus,
95
+ ): boolean {
96
+ let { url, method } = req;
97
+ if (!url?.startsWith("/log/") || "POST" !== method) return false;
98
+
99
+ let id: string | undefined;
100
+ let cookies = req.headers.cookie?.split(";") ?? [];
101
+ for (const cookieLine of cookies) {
102
+ if (cookieLine.startsWith("jackrabbit=")) {
103
+ let [_name, value] = cookieLine.split("=");
104
+ id = value;
105
+ }
106
+ }
107
+
108
+ if (id) {
109
+ getLoggerActionFromRequestBody(req)
110
+ .then(function (loggerAction: LoggerAction) {
111
+ eventbus.dispatchAction({
112
+ type: "log",
113
+ loggerAction,
114
+ id,
115
+ });
116
+ res.writeHead(201);
117
+ })
118
+ .catch(function () {
119
+ res.writeHead(401);
120
+ })
121
+ .finally(function () {
122
+ res.end();
123
+ });
124
+ } else {
125
+ res.writeHead(401);
126
+ res.end();
127
+ }
128
+
129
+ return true;
130
+ }
131
+
132
+ async function serveFile(req: IncomingMessage, res: ServerResponse) {
133
+ let { url, method } = req;
134
+
135
+ if (!url || "GET" !== method) {
136
+ res.setHeader("Content-Type", MIME_TYPES["html"]);
137
+ res.writeHead(400);
138
+ res.end();
139
+ return;
140
+ }
141
+
142
+ // assume http 1.1
143
+ let urlFilePath = path.join(url);
144
+
145
+ let extStr = "";
146
+ if (url.endsWith("/")) extStr = "index.html";
147
+ let urlNoPrefix = url;
148
+
149
+ if (urlFilePath.startsWith("/jackrabbit")) {
150
+ let strippedUrl = urlFilePath.substring("/jackrabbit".length);
151
+ urlFilePath = path.join(parentPath, strippedUrl, extStr);
152
+ } else {
153
+ urlFilePath = path.join(cwd, urlNoPrefix, extStr);
154
+ }
155
+
156
+ let stream = await getFile(urlFilePath);
157
+
158
+ if (stream) {
159
+ // throws errors if not a string
160
+ // filepath is always a string
161
+ const ext = path.extname(urlFilePath).substring(1).toLowerCase();
162
+ let mimeType = MIME_TYPES[ext] ?? MIME_TYPES["octet"];
163
+ res.setHeader("Content-Type", mimeType);
164
+ res.writeHead(200);
165
+ stream.pipe(res);
166
+ } else {
167
+ res.setHeader("Content-Type", MIME_TYPES["html"]);
168
+ res.writeHead(404);
169
+ res.end();
170
+ }
171
+ }
172
+
173
+ function getLoggerActionFromRequestBody(
174
+ req: IncomingMessage,
175
+ ): Promise<LoggerAction> {
176
+ return new Promise(function (resolve, reject) {
177
+ let data: Uint8Array[] = [];
178
+ req.addListener("data", function (chunk) {
179
+ data.push(chunk);
180
+ });
181
+ req.addListener("end", function () {
182
+ let actionStr = Buffer.concat(data).toString();
183
+ let action = JSON.parse(actionStr) as LoggerAction;
184
+
185
+ resolve(action);
186
+ });
187
+ req.addListener("error", function (err: Error) {
188
+ reject(err);
189
+ });
190
+ });
191
+ }
192
+
193
+ async function getFile(filePath: string): Promise<fs.ReadStream | undefined> {
194
+ try {
195
+ await fs.promises.access(filePath);
196
+ return fs.createReadStream(filePath);
197
+ } catch {}
198
+ }
@@ -0,0 +1,25 @@
1
+ interface TestHangerParams {
2
+ jackrabbit_url: URL;
3
+ test_collections: string[];
4
+ }
5
+
6
+ export function testHanger(params: TestHangerParams) {
7
+ return `<!DOCTYPE html>
8
+ <html>
9
+ <head>
10
+ <script type="jackrabbit_config">
11
+ ${JSON.stringify(params)}
12
+ </script>
13
+ <script type="importmap">
14
+ {
15
+ "imports": {
16
+ "jackrabbit/core/": "/jackrabbit/core/"
17
+ }
18
+ }
19
+ </script>
20
+ <script type="module" src="/jackrabbit/browser/dist/mod.js"></script>
21
+ </head>
22
+ <body></body>
23
+ </html>
24
+ `;
25
+ }