@w-lfpup/jackrabbit 0.1.0

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 (66) hide show
  1. package/.github/workflows/build_and_test.yml +18 -0
  2. package/.prettierignore +3 -0
  3. package/.prettierrc +5 -0
  4. package/LICENSE +29 -0
  5. package/README.md +33 -0
  6. package/cli/dist/cli.d.ts +3 -0
  7. package/cli/dist/cli.js +8 -0
  8. package/cli/dist/cli_types.d.ts +7 -0
  9. package/cli/dist/cli_types.js +1 -0
  10. package/cli/dist/config.d.ts +5 -0
  11. package/cli/dist/config.js +27 -0
  12. package/cli/dist/importer.d.ts +7 -0
  13. package/cli/dist/importer.js +16 -0
  14. package/cli/dist/logger.d.ts +7 -0
  15. package/cli/dist/logger.js +88 -0
  16. package/cli/dist/mod.d.ts +6 -0
  17. package/cli/dist/mod.js +4 -0
  18. package/cli/package.json +8 -0
  19. package/cli/src/cli.ts +17 -0
  20. package/cli/src/cli_types.ts +9 -0
  21. package/cli/src/config.ts +36 -0
  22. package/cli/src/importer.ts +25 -0
  23. package/cli/src/logger.ts +126 -0
  24. package/cli/src/mod.ts +7 -0
  25. package/cli/tsconfig.json +7 -0
  26. package/cli/tsconfig.tsbuildinfo +1 -0
  27. package/core/dist/jackrabbit_types.d.ts +56 -0
  28. package/core/dist/jackrabbit_types.js +1 -0
  29. package/core/dist/mod.d.ts +2 -0
  30. package/core/dist/mod.js +1 -0
  31. package/core/dist/run_steps.d.ts +3 -0
  32. package/core/dist/run_steps.js +95 -0
  33. package/core/package.json +8 -0
  34. package/core/src/jackrabbit_types.ts +75 -0
  35. package/core/src/mod.ts +9 -0
  36. package/core/src/run_steps.ts +138 -0
  37. package/core/tsconfig.json +7 -0
  38. package/core/tsconfig.tsbuildinfo +1 -0
  39. package/examples/hello_world/goodbye_world.ts +15 -0
  40. package/examples/hello_world/hello_world.ts +9 -0
  41. package/examples/hello_world/mod.ts +4 -0
  42. package/examples/package.json +3 -0
  43. package/nodejs_cli/dist/mod.d.ts +2 -0
  44. package/nodejs_cli/dist/mod.js +20 -0
  45. package/nodejs_cli/package.json +8 -0
  46. package/nodejs_cli/src/mod.ts +25 -0
  47. package/nodejs_cli/tsconfig.json +7 -0
  48. package/nodejs_cli/tsconfig.tsbuildinfo +1 -0
  49. package/package.json +29 -0
  50. package/test_guide.md +114 -0
  51. package/tests/dist/mod.d.ts +9 -0
  52. package/tests/dist/mod.js +30 -0
  53. package/tests/dist/test_fail.test.d.ts +9 -0
  54. package/tests/dist/test_fail.test.js +23 -0
  55. package/tests/dist/test_logger.d.ts +7 -0
  56. package/tests/dist/test_logger.js +19 -0
  57. package/tests/dist/test_pass.test.d.ts +9 -0
  58. package/tests/dist/test_pass.test.js +23 -0
  59. package/tests/package.json +8 -0
  60. package/tests/src/mod.ts +39 -0
  61. package/tests/src/test_fail.test.ts +28 -0
  62. package/tests/src/test_logger.ts +28 -0
  63. package/tests/src/test_pass.test.ts +28 -0
  64. package/tests/tsconfig.json +7 -0
  65. package/tests/tsconfig.tsbuildinfo +1 -0
  66. package/tsconfig.json +9 -0
@@ -0,0 +1,95 @@
1
+ const TIMEOUT_INTERVAL_MS = 10000;
2
+ function sleep(time) {
3
+ return new Promise((resolve) => {
4
+ setTimeout(() => {
5
+ resolve();
6
+ }, time);
7
+ });
8
+ }
9
+ async function createTimeout(timeoutMs = TIMEOUT_INTERVAL_MS) {
10
+ await sleep(timeoutMs);
11
+ return `timed out at ${performance.now()} after ${timeoutMs} ms.`;
12
+ }
13
+ async function execTest(testModules, logger, moduleId, testId) {
14
+ if (logger.cancelled)
15
+ return;
16
+ logger.log(testModules, {
17
+ type: "start_test",
18
+ moduleId,
19
+ testId,
20
+ });
21
+ const { tests, options } = testModules[moduleId];
22
+ const testFunc = tests[testId];
23
+ const startTime = performance.now();
24
+ const assertions = await Promise.race([
25
+ createTimeout(options.timeoutMs),
26
+ testFunc(),
27
+ ]);
28
+ if (logger.cancelled)
29
+ return;
30
+ const endTime = performance.now();
31
+ logger.log(testModules, {
32
+ type: "end_test",
33
+ assertions,
34
+ endTime,
35
+ moduleId,
36
+ startTime,
37
+ testId,
38
+ });
39
+ }
40
+ async function execCollection(testModules, logger, moduleId) {
41
+ if (logger.cancelled)
42
+ return;
43
+ const { tests } = testModules[moduleId];
44
+ const wrappedTests = [];
45
+ for (let [testID] of tests.entries()) {
46
+ wrappedTests.push(execTest(testModules, logger, moduleId, testID));
47
+ }
48
+ if (logger.cancelled)
49
+ return;
50
+ await Promise.all(wrappedTests);
51
+ }
52
+ async function execCollectionOrdered(testModules, logger, moduleId) {
53
+ const { tests } = testModules[moduleId];
54
+ for (let [testID] of tests.entries()) {
55
+ if (logger.cancelled)
56
+ return;
57
+ await execTest(testModules, logger, moduleId, testID);
58
+ }
59
+ }
60
+ export async function startRun(logger, testModules) {
61
+ logger.log(testModules, {
62
+ type: "start_run",
63
+ time: performance.now(),
64
+ });
65
+ for (let [moduleId, testModule] of testModules.entries()) {
66
+ if (logger.cancelled)
67
+ return;
68
+ logger.log(testModules, {
69
+ type: "start_module",
70
+ moduleId,
71
+ });
72
+ const { options } = testModule;
73
+ options?.runAsynchronously
74
+ ? await execCollection(testModules, logger, moduleId)
75
+ : await execCollectionOrdered(testModules, logger, moduleId);
76
+ if (logger.cancelled)
77
+ return;
78
+ logger.log(testModules, {
79
+ type: "end_module",
80
+ moduleId,
81
+ });
82
+ }
83
+ logger.log(testModules, {
84
+ type: "end_run",
85
+ time: performance.now(),
86
+ });
87
+ }
88
+ export function cancelRun(logger, testModules) {
89
+ if (logger.cancelled)
90
+ return;
91
+ logger.log(testModules, {
92
+ type: "cancel_run",
93
+ time: performance.now(),
94
+ });
95
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "core",
3
+ "type": "module",
4
+ "main": "dist/mod.js",
5
+ "scripts": {
6
+ "build": "npx tsc --build"
7
+ }
8
+ }
@@ -0,0 +1,75 @@
1
+ interface Stringable {
2
+ toString: Object["toString"];
3
+ }
4
+
5
+ export type Assertions = Stringable | Stringable[] | undefined;
6
+
7
+ type SyncTest = () => Assertions;
8
+ type AsyncTest = () => Promise<Assertions>;
9
+ export type Test = SyncTest | AsyncTest;
10
+
11
+ export interface Options {
12
+ title?: string;
13
+ runAsynchronously?: boolean;
14
+ timeoutMs?: number;
15
+ }
16
+
17
+ export interface TestModule {
18
+ tests: Test[];
19
+ options: Options;
20
+ }
21
+
22
+ interface StartRun {
23
+ type: "start_run";
24
+ time: number;
25
+ }
26
+
27
+ interface EndRun {
28
+ type: "end_run";
29
+ time: number;
30
+ }
31
+
32
+ interface CancelRun {
33
+ type: "cancel_run";
34
+ time: number;
35
+ }
36
+
37
+ interface StartModule {
38
+ type: "start_module";
39
+ moduleId: number;
40
+ }
41
+
42
+ interface EndModule {
43
+ type: "end_module";
44
+ moduleId: number;
45
+ }
46
+
47
+ interface StartTest {
48
+ type: "start_test";
49
+ testId: number;
50
+ moduleId: number;
51
+ }
52
+
53
+ interface EndTest {
54
+ type: "end_test";
55
+ testId: number;
56
+ moduleId: number;
57
+ startTime: number;
58
+ endTime: number;
59
+ assertions: Assertions;
60
+ }
61
+
62
+ export type LoggerAction =
63
+ | StartRun
64
+ | EndRun
65
+ | CancelRun
66
+ | StartModule
67
+ | EndModule
68
+ | StartTest
69
+ | EndTest;
70
+
71
+ export interface LoggerInterface {
72
+ readonly failed: boolean;
73
+ readonly cancelled: boolean;
74
+ log(testModules: TestModule[], action: LoggerAction): void;
75
+ }
@@ -0,0 +1,9 @@
1
+ export type {
2
+ LoggerAction,
3
+ LoggerInterface,
4
+ Test,
5
+ Options,
6
+ TestModule,
7
+ } from "./jackrabbit_types.ts";
8
+
9
+ export { startRun, cancelRun } from "./run_steps.js";
@@ -0,0 +1,138 @@
1
+ import type {
2
+ Assertions,
3
+ LoggerInterface,
4
+ TestModule,
5
+ } from "./jackrabbit_types.ts";
6
+
7
+ const TIMEOUT_INTERVAL_MS = 10000;
8
+
9
+ function sleep(time: number): Promise<void> {
10
+ return new Promise((resolve) => {
11
+ setTimeout(() => {
12
+ resolve();
13
+ }, time);
14
+ });
15
+ }
16
+
17
+ async function createTimeout(
18
+ timeoutMs: number = TIMEOUT_INTERVAL_MS,
19
+ ): Promise<Assertions> {
20
+ await sleep(timeoutMs);
21
+
22
+ return `timed out at ${performance.now()} after ${timeoutMs} ms.`;
23
+ }
24
+
25
+ async function execTest(
26
+ testModules: TestModule[],
27
+ logger: LoggerInterface,
28
+ moduleId: number,
29
+ testId: number,
30
+ ) {
31
+ if (logger.cancelled) return;
32
+
33
+ logger.log(testModules, {
34
+ type: "start_test",
35
+ moduleId,
36
+ testId,
37
+ });
38
+
39
+ const { tests, options } = testModules[moduleId];
40
+
41
+ const testFunc = tests[testId];
42
+ const startTime = performance.now();
43
+ const assertions = await Promise.race([
44
+ createTimeout(options.timeoutMs),
45
+ testFunc(),
46
+ ]);
47
+
48
+ if (logger.cancelled) return;
49
+
50
+ const endTime = performance.now();
51
+
52
+ logger.log(testModules, {
53
+ type: "end_test",
54
+ assertions,
55
+ endTime,
56
+ moduleId,
57
+ startTime,
58
+ testId,
59
+ });
60
+ }
61
+
62
+ async function execCollection(
63
+ testModules: TestModule[],
64
+ logger: LoggerInterface,
65
+ moduleId: number,
66
+ ) {
67
+ if (logger.cancelled) return;
68
+
69
+ const { tests } = testModules[moduleId];
70
+
71
+ const wrappedTests = [];
72
+ for (let [testID] of tests.entries()) {
73
+ wrappedTests.push(execTest(testModules, logger, moduleId, testID));
74
+ }
75
+
76
+ if (logger.cancelled) return;
77
+
78
+ await Promise.all(wrappedTests);
79
+ }
80
+
81
+ async function execCollectionOrdered(
82
+ testModules: TestModule[],
83
+ logger: LoggerInterface,
84
+ moduleId: number,
85
+ ) {
86
+ const { tests } = testModules[moduleId];
87
+
88
+ for (let [testID] of tests.entries()) {
89
+ if (logger.cancelled) return;
90
+
91
+ await execTest(testModules, logger, moduleId, testID);
92
+ }
93
+ }
94
+
95
+ export async function startRun(
96
+ logger: LoggerInterface,
97
+ testModules: TestModule[],
98
+ ) {
99
+ logger.log(testModules, {
100
+ type: "start_run",
101
+ time: performance.now(),
102
+ });
103
+
104
+ for (let [moduleId, testModule] of testModules.entries()) {
105
+ if (logger.cancelled) return;
106
+
107
+ logger.log(testModules, {
108
+ type: "start_module",
109
+ moduleId,
110
+ });
111
+
112
+ const { options } = testModule;
113
+ options?.runAsynchronously
114
+ ? await execCollection(testModules, logger, moduleId)
115
+ : await execCollectionOrdered(testModules, logger, moduleId);
116
+
117
+ if (logger.cancelled) return;
118
+
119
+ logger.log(testModules, {
120
+ type: "end_module",
121
+ moduleId,
122
+ });
123
+ }
124
+
125
+ logger.log(testModules, {
126
+ type: "end_run",
127
+ time: performance.now(),
128
+ });
129
+ }
130
+
131
+ export function cancelRun(logger: LoggerInterface, testModules: TestModule[]) {
132
+ if (logger.cancelled) return;
133
+
134
+ logger.log(testModules, {
135
+ type: "cancel_run",
136
+ time: performance.now(),
137
+ });
138
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/jackrabbit_types.ts","./src/mod.ts","./src/run_steps.ts"],"version":"5.9.3"}
@@ -0,0 +1,15 @@
1
+ async function test_that_passes() {
2
+ return [];
3
+ }
4
+
5
+ async function test_that_fails() {
6
+ return ["this test will fail"];
7
+ }
8
+
9
+ export const tests = [test_that_passes, test_that_fails];
10
+
11
+ export const options = {
12
+ title: import.meta.url,
13
+ runAsynchronously: true,
14
+ timeoutMs: 2000,
15
+ };
@@ -0,0 +1,9 @@
1
+ function test_will_pass() {
2
+ return;
3
+ }
4
+
5
+ function test_will_fail() {
6
+ return "this test will fail";
7
+ }
8
+
9
+ export const tests = [test_will_pass, test_will_fail];
@@ -0,0 +1,4 @@
1
+ import * as hello_world from "./hello_world.js";
2
+ import * as goodbye_world from "./goodbye_world.js";
3
+
4
+ export const testModules = [hello_world, goodbye_world];
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { Config, Importer, Logger, run } from "../../cli/dist/mod.js";
3
+ const config = new Config(process.argv.slice(2));
4
+ const importer = new Importer(process.cwd());
5
+ const logger = new Logger();
6
+ let errored = false;
7
+ try {
8
+ await run(config, importer, logger);
9
+ }
10
+ catch (e) {
11
+ errored = true;
12
+ console.log("Error:");
13
+ e instanceof Error
14
+ ? console.log(`
15
+ ${e.name}
16
+ ${e.message}
17
+ ${e.stack}`)
18
+ : console.log(e);
19
+ }
20
+ logger.failed || errored ? process.exit(1) : process.exit(0);
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "nodejs_cli",
3
+ "type": "module",
4
+ "main": "dist/mod.js",
5
+ "scripts": {
6
+ "build": "npx tsc --build"
7
+ }
8
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Config, Importer, Logger, run } from "../../cli/dist/mod.js";
4
+
5
+ const config = new Config(process.argv.slice(2));
6
+ const importer = new Importer(process.cwd());
7
+ const logger = new Logger();
8
+
9
+ let errored = false;
10
+
11
+ try {
12
+ await run(config, importer, logger);
13
+ } catch (e: unknown) {
14
+ errored = true;
15
+ console.log("Error:");
16
+
17
+ e instanceof Error
18
+ ? console.log(`
19
+ ${e.name}
20
+ ${e.message}
21
+ ${e.stack}`)
22
+ : console.log(e);
23
+ }
24
+
25
+ logger.failed || errored ? process.exit(1) : process.exit(0);
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/mod.ts"],"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@w-lfpup/jackrabbit",
3
+ "description": "A test runner without dependencies",
4
+ "workspaces": [
5
+ "core",
6
+ "tests",
7
+ "cli",
8
+ "nodejs_cli"
9
+ ],
10
+ "bin": {
11
+ "jackrabbit": "nodejs_cli/dist/mod.js"
12
+ },
13
+ "scripts": {
14
+ "prepare": "npm run build",
15
+ "build": "npm run --workspaces build",
16
+ "format": "npx prettier --write ./",
17
+ "test": "npx jackrabbit --file ./tests/dist/mod.js"
18
+ },
19
+ "version": "0.1.0",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/w-lfpup/jackarabbit-js.git"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.5.5",
26
+ "prettier": "^3.3.1",
27
+ "typescript": "^5.4.5"
28
+ }
29
+ }
package/test_guide.md ADDED
@@ -0,0 +1,114 @@
1
+ # Jackrabbit Tests
2
+
3
+ For a quick visual reference, please refer to the [examples](./examples/).
4
+
5
+ `Jackrabbit` leverages esmodules for a flat, concise testing experience. There are no assertion libraries, there are no wild BDD functions. Developers with javascript experience can immediately start testing with basically zero overhead.
6
+
7
+ ## Tests
8
+
9
+ Tests are functions that return assertions.
10
+
11
+ Tests `pass` when they return the `undefined` primitive or an empty array `[]`.
12
+
13
+ ```TS
14
+ // my_library.tests.ts
15
+
16
+ function testStuffAndPass() {
17
+ return;
18
+ }
19
+
20
+ async function testMoreStuffAndPass() {
21
+ return [];
22
+ }
23
+ ```
24
+
25
+ Any other value will cause a test to `fail`.
26
+
27
+ So tests that `fail` look like:
28
+
29
+ ```TS
30
+ // my_library.tests.ts
31
+
32
+ function testStuffAndFail() {
33
+ return "this test failed!";
34
+ }
35
+
36
+ function testMoreStuffAndFail() {
37
+ return ["this test also failed!"];
38
+ }
39
+ ```
40
+
41
+ ## Test Modules
42
+
43
+ Test Modules are javascript `modules` that contain tests.
44
+
45
+ ### Export Tests
46
+
47
+ Test Modules export their tests in an array called `tests`.
48
+
49
+ ```TS
50
+ // my_library.tests.ts
51
+
52
+ export const tests = [
53
+ testStuffAndPass,
54
+ testMoreStuffAndPass,
55
+ testStuffAndFail,
56
+ testMoreStuffAndFail,
57
+ ];
58
+ ```
59
+
60
+ ### Export Options
61
+
62
+ Exporting an `options` pojo is not required.
63
+
64
+ But exporting an `options` pojo with the following properties will affect test behavior:
65
+
66
+ ```TS
67
+ export const options = {
68
+ title: import.meta.url,
69
+ runAsyncronously: true,
70
+ timeoutMs: 3000,
71
+ }
72
+ ```
73
+
74
+ All properteis are optional.
75
+
76
+ Tests run sequentially unless the `runAsyncronously` property is set to `true`.
77
+
78
+ ```TS
79
+ // my_library.tests.ts
80
+
81
+ interface Options {
82
+ title?: string;
83
+ runAsynchronously?: boolean;
84
+ timeoutMs?: number;
85
+ }
86
+ ```
87
+
88
+ ## Test Collections
89
+
90
+ A `test collection` is a javascript module that exports a list test modules called `testModules`.
91
+
92
+ ```TS
93
+ // mod.test.ts
94
+
95
+ import * as MyTests from "./my_library.tests.ts";
96
+
97
+ export const testModules = [
98
+ MyTests
99
+ ];
100
+ ```
101
+
102
+ This gathers all tests into a single explicit location.
103
+
104
+ ## Run Test Collections
105
+
106
+ Run the following command and Jackrabbit will log the results of `test collections`.
107
+
108
+ ```sh
109
+ npx jackrabbit --file ./mod.test.ts
110
+ ```
111
+
112
+ ## License
113
+
114
+ BSD 3-Clause License
@@ -0,0 +1,9 @@
1
+ declare function testsFail(): Promise<"fail tests failed to fail" | undefined>;
2
+ declare function testsPass(): Promise<"passing tests failed to pass" | undefined>;
3
+ export declare const testModules: {
4
+ tests: (typeof testsFail | typeof testsPass)[];
5
+ options: {
6
+ title: string;
7
+ };
8
+ }[];
9
+ export {};
@@ -0,0 +1,30 @@
1
+ import * as FailTests from "./test_fail.test.js";
2
+ import * as PassTests from "./test_pass.test.js";
3
+ import { startRun } from "../../core/dist/mod.js";
4
+ import { TestLogger } from "./test_logger.js";
5
+ // Test pass and fail behavior
6
+ const failTestModules = [FailTests];
7
+ const passTestModules = [PassTests];
8
+ // jackrabbit test run won't pass failing tests
9
+ async function testsFail() {
10
+ let logger = new TestLogger();
11
+ await startRun(logger, failTestModules);
12
+ if (!logger.failed)
13
+ return "fail tests failed to fail";
14
+ }
15
+ // jackrabbit test run won't fail passing tests
16
+ async function testsPass() {
17
+ let logger = new TestLogger();
18
+ await startRun(logger, passTestModules);
19
+ if (logger.failed)
20
+ return "passing tests failed to pass";
21
+ }
22
+ const tests = [testsFail, testsPass];
23
+ const options = {
24
+ title: "Failures and Successes",
25
+ };
26
+ const testModule = {
27
+ tests,
28
+ options,
29
+ };
30
+ export const testModules = [testModule];
@@ -0,0 +1,9 @@
1
+ declare function testStuffAndFail(): string;
2
+ declare function testMoreStuffAndFail(): string[];
3
+ declare function testStuffAndFailAsync(): Promise<string>;
4
+ declare function testMoreStuffAndFailAsync(): Promise<string[]>;
5
+ export declare const tests: (typeof testStuffAndFail | typeof testMoreStuffAndFail | typeof testStuffAndFailAsync | typeof testMoreStuffAndFailAsync)[];
6
+ export declare const options: {
7
+ title: string;
8
+ };
9
+ export {};
@@ -0,0 +1,23 @@
1
+ function testStuffAndFail() {
2
+ return "this test failed!";
3
+ }
4
+ function testMoreStuffAndFail() {
5
+ return ["this test also failed!"];
6
+ }
7
+ async function testStuffAndFailAsync() {
8
+ return "this test failed!";
9
+ }
10
+ async function testMoreStuffAndFailAsync() {
11
+ return ["this test also failed!"];
12
+ }
13
+ // export tests
14
+ export const tests = [
15
+ testStuffAndFail,
16
+ testMoreStuffAndFail,
17
+ testStuffAndFailAsync,
18
+ testMoreStuffAndFailAsync,
19
+ ];
20
+ // export optional test details
21
+ export const options = {
22
+ title: import.meta.url,
23
+ };
@@ -0,0 +1,7 @@
1
+ import type { LoggerAction, LoggerInterface, TestModule } from "../../core/dist/mod.ts";
2
+ declare class TestLogger implements LoggerInterface {
3
+ cancelled: boolean;
4
+ failed: boolean;
5
+ log(_testModule: TestModule[], action: LoggerAction): void;
6
+ }
7
+ export { TestLogger };