@davidsouther/jiffies 1.0.0-beta.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.
- package/README.md +60 -0
- package/build/assert.d.ts +23 -0
- package/build/assert.js +33 -0
- package/build/case.d.ts +1 -0
- package/build/case.js +5 -0
- package/build/components/button_bar.d.ts +8 -0
- package/build/components/button_bar.js +16 -0
- package/build/components/index.d.ts +1 -0
- package/build/components/index.js +1 -0
- package/build/components/inline_edit.d.ts +12 -0
- package/build/components/inline_edit.js +48 -0
- package/build/components/logger.d.ts +7 -0
- package/build/components/logger.js +22 -0
- package/build/components/select.d.ts +13 -0
- package/build/components/select.js +3 -0
- package/build/components/test.d.ts +1 -0
- package/build/components/test.js +2 -0
- package/build/components/virtual_scroll.d.ts +41 -0
- package/build/components/virtual_scroll.js +94 -0
- package/build/components/virtual_scroll.test.d.ts +1 -0
- package/build/components/virtual_scroll.test.js +21 -0
- package/build/context.d.ts +15 -0
- package/build/context.js +43 -0
- package/build/context.test.d.ts +1 -0
- package/build/context.test.js +46 -0
- package/build/debounce.d.ts +1 -0
- package/build/debounce.js +7 -0
- package/build/display.d.ts +5 -0
- package/build/display.js +3 -0
- package/build/dom/css/border.d.ts +11 -0
- package/build/dom/css/border.js +27 -0
- package/build/dom/css/constants.d.ts +31 -0
- package/build/dom/css/constants.js +28 -0
- package/build/dom/css/core.d.ts +5 -0
- package/build/dom/css/core.js +24 -0
- package/build/dom/css/fstyle.d.ts +5 -0
- package/build/dom/css/fstyle.js +32 -0
- package/build/dom/css/sizing.d.ts +5 -0
- package/build/dom/css/sizing.js +10 -0
- package/build/dom/dom.d.ts +27 -0
- package/build/dom/dom.js +94 -0
- package/build/dom/fc.d.ts +14 -0
- package/build/dom/fc.js +35 -0
- package/build/dom/fc.test.d.ts +1 -0
- package/build/dom/fc.test.js +21 -0
- package/build/dom/form/form.app.d.ts +1 -0
- package/build/dom/form/form.app.js +23 -0
- package/build/dom/form/form.d.ts +25 -0
- package/build/dom/form/form.js +25 -0
- package/build/dom/form/form.test.d.ts +0 -0
- package/build/dom/form/form.test.js +1 -0
- package/build/dom/html.d.ts +117 -0
- package/build/dom/html.js +114 -0
- package/build/dom/html.test.d.ts +1 -0
- package/build/dom/html.test.js +58 -0
- package/build/dom/router/link.d.ts +6 -0
- package/build/dom/router/link.js +3 -0
- package/build/dom/router/router.d.ts +12 -0
- package/build/dom/router/router.js +49 -0
- package/build/dom/svg.d.ts +64 -0
- package/build/dom/svg.js +65 -0
- package/build/dom/test.d.ts +1 -0
- package/build/dom/test.js +2 -0
- package/build/dom/types/css.d.ts +6612 -0
- package/build/dom/types/css.js +23 -0
- package/build/dom/types/dom.d.ts +0 -0
- package/build/dom/types/dom.js +1 -0
- package/build/dom/types/html.d.ts +616 -0
- package/build/dom/types/html.js +1 -0
- package/build/dom/xml.d.ts +1 -0
- package/build/dom/xml.js +5 -0
- package/build/equal.d.ts +4 -0
- package/build/equal.js +22 -0
- package/build/equal.test.d.ts +1 -0
- package/build/equal.test.js +20 -0
- package/build/flags.d.ts +7 -0
- package/build/flags.js +48 -0
- package/build/flags.test.d.ts +1 -0
- package/build/flags.test.js +35 -0
- package/build/generator.d.ts +1 -0
- package/build/generator.js +10 -0
- package/build/generator.test.d.ts +1 -0
- package/build/generator.test.js +24 -0
- package/build/index.d.ts +13 -0
- package/build/index.js +13 -0
- package/build/is_browser.d.ts +1 -0
- package/build/is_browser.js +1 -0
- package/build/loader.d.mts +22 -0
- package/build/loader.mjs +35 -0
- package/build/lock.d.ts +1 -0
- package/build/lock.js +23 -0
- package/build/lock.test.d.ts +1 -0
- package/build/lock.test.js +16 -0
- package/build/log.d.ts +26 -0
- package/build/log.js +34 -0
- package/build/parcel_resolver.d.ts +3 -0
- package/build/parcel_resolver.js +19 -0
- package/build/range.d.ts +1 -0
- package/build/range.js +7 -0
- package/build/result.d.ts +31 -0
- package/build/result.js +65 -0
- package/build/result.test.d.ts +1 -0
- package/build/result.test.js +71 -0
- package/build/safe.d.ts +1 -0
- package/build/safe.js +10 -0
- package/build/scope/describe.d.ts +14 -0
- package/build/scope/describe.js +52 -0
- package/build/scope/display/console.d.ts +2 -0
- package/build/scope/display/console.js +21 -0
- package/build/scope/display/dom.d.ts +3 -0
- package/build/scope/display/dom.js +26 -0
- package/build/scope/display/junit.d.ts +2 -0
- package/build/scope/display/junit.js +17 -0
- package/build/scope/execute.d.ts +12 -0
- package/build/scope/execute.js +85 -0
- package/build/scope/expect.d.ts +23 -0
- package/build/scope/expect.js +107 -0
- package/build/scope/fix.d.ts +4 -0
- package/build/scope/fix.js +22 -0
- package/build/scope/index.d.ts +3 -0
- package/build/scope/index.js +3 -0
- package/build/scope/scope.d.ts +17 -0
- package/build/scope/scope.js +1 -0
- package/build/server/http/apps.d.ts +5 -0
- package/build/server/http/apps.js +23 -0
- package/build/server/http/index.d.ts +21 -0
- package/build/server/http/index.js +71 -0
- package/build/server/http/response.d.ts +4 -0
- package/build/server/http/response.js +37 -0
- package/build/server/http/sitemap.d.ts +2 -0
- package/build/server/http/sitemap.js +42 -0
- package/build/server/http/static.d.ts +2 -0
- package/build/server/http/static.js +21 -0
- package/build/server/http/typescript.d.ts +5 -0
- package/build/server/http/typescript.js +40 -0
- package/build/server/main.d.ts +2 -0
- package/build/server/main.js +9 -0
- package/build/test.d.mts +2 -0
- package/build/test.mjs +23 -0
- package/build/test_all.d.ts +1 -0
- package/build/test_all.js +19 -0
- package/build/transpile.d.mts +3 -0
- package/build/transpile.mjs +18 -0
- package/package.json +36 -0
- package/src/404.html +14 -0
- package/src/assert.ts +50 -0
- package/src/case.ts +5 -0
- package/src/components/_notes +33 -0
- package/src/components/button_bar.ts +38 -0
- package/src/components/inline_edit.ts +77 -0
- package/src/components/logger.ts +36 -0
- package/src/components/select.ts +22 -0
- package/src/components/test.js +2 -0
- package/src/components/virtual_scroll.test.ts +27 -0
- package/src/components/virtual_scroll.ts +194 -0
- package/src/context.test.ts +58 -0
- package/src/context.ts +62 -0
- package/src/debounce.ts +7 -0
- package/src/display.ts +12 -0
- package/src/dom/README.md +102 -0
- package/src/dom/css/border.ts +47 -0
- package/src/dom/css/constants.ts +34 -0
- package/src/dom/css/core.ts +28 -0
- package/src/dom/css/fstyle.ts +42 -0
- package/src/dom/css/sizing.ts +11 -0
- package/src/dom/dom.ts +153 -0
- package/src/dom/fc.test.ts +43 -0
- package/src/dom/fc.ts +79 -0
- package/src/dom/form/form.app.ts +50 -0
- package/src/dom/form/form.test.ts +0 -0
- package/src/dom/form/form.ts +53 -0
- package/src/dom/form/index.html +14 -0
- package/src/dom/html.test.ts +72 -0
- package/src/dom/html.ts +129 -0
- package/src/dom/router/link.ts +14 -0
- package/src/dom/router/router.ts +69 -0
- package/src/dom/svg.ts +77 -0
- package/src/dom/test.ts +2 -0
- package/src/dom/types/css.ts +10106 -0
- package/src/dom/types/dom.ts +0 -0
- package/src/dom/types/html.ts +631 -0
- package/src/dom/xml.ts +12 -0
- package/src/equal.test.ts +23 -0
- package/src/equal.ts +32 -0
- package/src/favicon.ico +0 -0
- package/src/flags.test.ts +43 -0
- package/src/flags.ts +53 -0
- package/src/generator.test.ts +26 -0
- package/src/generator.ts +12 -0
- package/src/hooks/_notes +3 -0
- package/src/index.html +79 -0
- package/src/is_browser.js +1 -0
- package/src/loader.mjs +45 -0
- package/src/lock.test.ts +17 -0
- package/src/lock.ts +22 -0
- package/src/log.ts +61 -0
- package/src/observable/_notes +13 -0
- package/src/observable/observable._js +175 -0
- package/src/range.ts +7 -0
- package/src/result.test.ts +98 -0
- package/src/result.ts +107 -0
- package/src/safe.ts +12 -0
- package/src/scope/describe.ts +70 -0
- package/src/scope/display/console.ts +26 -0
- package/src/scope/display/dom.ts +36 -0
- package/src/scope/display/junit.ts +67 -0
- package/src/scope/execute.ts +108 -0
- package/src/scope/expect.ts +170 -0
- package/src/scope/fix.ts +29 -0
- package/src/scope/index.ts +11 -0
- package/src/scope/scope.ts +21 -0
- package/src/server/http/apps.ts +26 -0
- package/src/server/http/index.ts +119 -0
- package/src/server/http/response.ts +47 -0
- package/src/server/http/sitemap.ts +48 -0
- package/src/server/http/static.ts +27 -0
- package/src/server/http/typescript.ts +46 -0
- package/src/server/main.ts +13 -0
- package/src/test.mjs +29 -0
- package/src/test_all.ts +22 -0
- package/src/transpile.mjs +29 -0
package/src/result.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export type None = null;
|
|
2
|
+
export type Some<T> = T;
|
|
3
|
+
export type Option<T> = Some<T> | None;
|
|
4
|
+
export type Err<E extends Error> = {
|
|
5
|
+
err: E;
|
|
6
|
+
map: <U>(fn: (t: unknown) => Result<U>) => Result<U>;
|
|
7
|
+
};
|
|
8
|
+
export type Ok<T> = { ok: T; map: <U>(fn: (t: T) => Result<U>) => Result<U> };
|
|
9
|
+
export type Result<T, E extends Error = Error> = Ok<T> | Err<E>;
|
|
10
|
+
|
|
11
|
+
export const isNone = <T>(s: Option<T>): s is None => s == null;
|
|
12
|
+
export const isSome = <T>(s: Option<T>): s is Some<T> => s != null;
|
|
13
|
+
|
|
14
|
+
export function None<T = unknown>(_?: T): Option<T> {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Some<T>(t: Option<T>): Option<T>;
|
|
19
|
+
export function Some<T>(t: T): Option<T>;
|
|
20
|
+
export function Some(t: any): any {
|
|
21
|
+
return t;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const isOk = <T, E extends Error>(t: Result<T, E>): t is Ok<T> =>
|
|
25
|
+
(t as Ok<T>).ok !== undefined;
|
|
26
|
+
export const isErr = <T, E extends Error>(e: Result<T, E>) =>
|
|
27
|
+
(e as Err<E>).err !== undefined;
|
|
28
|
+
export const isResult = <T, E extends Error>(
|
|
29
|
+
t: Result<T, E>
|
|
30
|
+
): t is Result<T, E> => isOk(t) || isErr(t);
|
|
31
|
+
|
|
32
|
+
// Beware: Order matters for correct inference.
|
|
33
|
+
export function Ok<T>(ok: Ok<T>): T;
|
|
34
|
+
export function Ok<T>(t: T): Ok<T>;
|
|
35
|
+
export function Ok<T, E extends Error>(t: any): any {
|
|
36
|
+
return t.ok
|
|
37
|
+
? t.ok
|
|
38
|
+
: {
|
|
39
|
+
ok: t,
|
|
40
|
+
map<U>(fn: (_: typeof t) => Result<U, E>): Result<U, E> {
|
|
41
|
+
return fn(Ok(this));
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Beware: Order matters for correct inference.
|
|
47
|
+
export function Err<E extends Error>(e: Err<E>): E;
|
|
48
|
+
export function Err<E extends Error>(e: E): Err<E>;
|
|
49
|
+
export function Err<E extends Error>(e: string): Err<E>;
|
|
50
|
+
export function Err<T, E extends Error>(e: any): any {
|
|
51
|
+
return (
|
|
52
|
+
e.err ?? {
|
|
53
|
+
err: typeof e === "string" ? new Error(e) : e,
|
|
54
|
+
map<U>(this: Result<T, E>, fn: (t: unknown) => Result<U>): Result<U, E> {
|
|
55
|
+
return this as Result<U, E>;
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function unwrap<T, E extends Error>(result: Result<T, E>): T | never;
|
|
62
|
+
export function unwrap<O>(some: Option<O>): O | never;
|
|
63
|
+
export function unwrap(t: any): any {
|
|
64
|
+
if (isNone(t)) {
|
|
65
|
+
throw new Error(`Attempted to unwrap None`);
|
|
66
|
+
}
|
|
67
|
+
if (isErr(t)) {
|
|
68
|
+
throw Err(t);
|
|
69
|
+
}
|
|
70
|
+
if (isOk(t)) {
|
|
71
|
+
return Ok(t);
|
|
72
|
+
}
|
|
73
|
+
return t;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function unwrapOr<T, E extends Error>(result: Result<T, E>, def: T): T;
|
|
77
|
+
export function unwrapOr<T>(some: Some<T>, def: T): T;
|
|
78
|
+
export function unwrapOr(t: any, def: any): any {
|
|
79
|
+
if (isNone(t)) {
|
|
80
|
+
return def;
|
|
81
|
+
}
|
|
82
|
+
if (isErr(t)) {
|
|
83
|
+
return def;
|
|
84
|
+
}
|
|
85
|
+
if (isOk(t)) {
|
|
86
|
+
return Ok(t);
|
|
87
|
+
}
|
|
88
|
+
return t;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function unwrapOrElse<T, E extends Error>(
|
|
92
|
+
result: Result<T, Error>,
|
|
93
|
+
def: () => T
|
|
94
|
+
): T;
|
|
95
|
+
export function unwrapOrElse<T>(some: Some<T>, def: () => T): T;
|
|
96
|
+
export function unwrapOrElse(t: any, def: any): any {
|
|
97
|
+
if (isNone(t)) {
|
|
98
|
+
return def();
|
|
99
|
+
}
|
|
100
|
+
if (isErr(t)) {
|
|
101
|
+
return def();
|
|
102
|
+
}
|
|
103
|
+
if (isOk(t)) {
|
|
104
|
+
return Ok(t);
|
|
105
|
+
}
|
|
106
|
+
return t;
|
|
107
|
+
}
|
package/src/safe.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getLogger } from "../log.js";
|
|
2
|
+
import { TestCase } from "./scope.js";
|
|
3
|
+
|
|
4
|
+
export const beforeall = Symbol("beforeAll");
|
|
5
|
+
export const beforeeach = Symbol("beforeEach");
|
|
6
|
+
export const afterall = Symbol("afterAll");
|
|
7
|
+
export const aftereach = Symbol("afterEach");
|
|
8
|
+
|
|
9
|
+
const logger = getLogger("scope");
|
|
10
|
+
|
|
11
|
+
const CASES: TestCase = {};
|
|
12
|
+
let cases = [CASES];
|
|
13
|
+
let totalCases = 0;
|
|
14
|
+
|
|
15
|
+
function push(title: string) {
|
|
16
|
+
const next = (cases[0][title] = {});
|
|
17
|
+
cases.unshift(next);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pop() {
|
|
21
|
+
cases.shift();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function rootCases(): TestCase {
|
|
25
|
+
return CASES;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getTotalCases() {
|
|
29
|
+
return totalCases;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function describe(title: string, block: Function) {
|
|
33
|
+
logger.debug(`describe(${title})`);
|
|
34
|
+
push(title);
|
|
35
|
+
block();
|
|
36
|
+
pop();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function it(title: string, block: Function) {
|
|
40
|
+
logger.debug(`it(${title})`);
|
|
41
|
+
totalCases += 1;
|
|
42
|
+
cases[0][title] = block;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function beforeEach(fn: () => void) {
|
|
46
|
+
cases[0][beforeeach] = fn;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function beforeAll(fn: () => void) {
|
|
50
|
+
cases[0][beforeall] = fn;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function afterEach(fn: () => void) {
|
|
54
|
+
cases[0][aftereach] = fn;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function afterAll(fn: () => void) {
|
|
58
|
+
cases[0][afterall] = fn;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function cleanState<State extends {}>(
|
|
62
|
+
init: () => State,
|
|
63
|
+
runner: (action: () => void) => void = beforeEach
|
|
64
|
+
): State {
|
|
65
|
+
const state = {};
|
|
66
|
+
runner(() => {
|
|
67
|
+
Object.assign(state, init());
|
|
68
|
+
});
|
|
69
|
+
return state as State;
|
|
70
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getLogger } from "../../log.js";
|
|
2
|
+
import { getTotalCases } from "../describe.js";
|
|
3
|
+
import { flattenResults } from "../execute.js";
|
|
4
|
+
import { TestResult } from "../scope.js";
|
|
5
|
+
|
|
6
|
+
const logger = getLogger("Scope Test Runner");
|
|
7
|
+
|
|
8
|
+
export function onConsole(results: TestResult) {
|
|
9
|
+
const { executed, failed } = results;
|
|
10
|
+
logger.info("Executed test suite.", {
|
|
11
|
+
executed,
|
|
12
|
+
total: getTotalCases(),
|
|
13
|
+
failed,
|
|
14
|
+
});
|
|
15
|
+
const flat = flattenResults(results);
|
|
16
|
+
|
|
17
|
+
for (const { test } of flat) {
|
|
18
|
+
logger.debug(test);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const { test, stack } of flat) {
|
|
22
|
+
if (stack) {
|
|
23
|
+
logger.error(test, { stack });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isHTMLLogger, makeHTMLLogger } from "../../components/logger.js";
|
|
2
|
+
import { DOMElement, Updatable } from "../../dom/dom.js";
|
|
3
|
+
import { DEFAULT_LOGGER, LEVEL } from "../../log.js";
|
|
4
|
+
import { getTotalCases } from "../describe.js";
|
|
5
|
+
import { flattenResults } from "../execute.js";
|
|
6
|
+
import { TestResult } from "../scope.js";
|
|
7
|
+
|
|
8
|
+
export function displayStatistics(
|
|
9
|
+
results: TestResult,
|
|
10
|
+
root: Updatable<DOMElement> = document.body as unknown as Updatable<DOMElement>
|
|
11
|
+
) {
|
|
12
|
+
const { executed, failed } = results;
|
|
13
|
+
const logger = (() => {
|
|
14
|
+
try {
|
|
15
|
+
return makeHTMLLogger(
|
|
16
|
+
`Executed ${executed} of ${getTotalCases()}; ${failed} failed.`
|
|
17
|
+
);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return DEFAULT_LOGGER;
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
|
|
23
|
+
logger.level = LEVEL.DEBUG;
|
|
24
|
+
|
|
25
|
+
const flat = flattenResults(results);
|
|
26
|
+
for (const { test, stack } of flat) {
|
|
27
|
+
if (stack) {
|
|
28
|
+
logger.info(test);
|
|
29
|
+
logger.debug(`${stack}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (isHTMLLogger(logger)) {
|
|
34
|
+
root.appendChild(logger.root);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { xml } from "../../dom/xml.js";
|
|
2
|
+
import { FlatResult, flattenResults } from "../execute.js";
|
|
3
|
+
import { TestResult } from "../scope.js";
|
|
4
|
+
|
|
5
|
+
const cases = (results: TestResult) =>
|
|
6
|
+
Object.entries(results).filter(
|
|
7
|
+
([key]) => !["executed", "passed", "failed"].includes(key)
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export function asXML(results: TestResult) {
|
|
11
|
+
return (
|
|
12
|
+
`<?xml version="1.0" encoding="UTF-8" ?>` +
|
|
13
|
+
xml(
|
|
14
|
+
"testsuites",
|
|
15
|
+
{ tests: results.executed, failures: results.failed },
|
|
16
|
+
cases(results).map(([title, children]) =>
|
|
17
|
+
testsuite(
|
|
18
|
+
title,
|
|
19
|
+
(children as TestResult).executed,
|
|
20
|
+
(children as TestResult).failed,
|
|
21
|
+
flattenResults(children as TestResult)
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function testsuite(
|
|
29
|
+
name: string,
|
|
30
|
+
tests: number,
|
|
31
|
+
failures: number,
|
|
32
|
+
cases: FlatResult[]
|
|
33
|
+
) {
|
|
34
|
+
const id = name.replace("s+", "_");
|
|
35
|
+
return xml(
|
|
36
|
+
"testsuite",
|
|
37
|
+
{ id, name, tests, failures },
|
|
38
|
+
cases.map(({ test, stack }) =>
|
|
39
|
+
testcase({ name: `${name} ${test}` }, stack ? [stack as string] : [])
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function testcase(
|
|
45
|
+
{
|
|
46
|
+
name,
|
|
47
|
+
id = name.replace(/\s+/g, "_"),
|
|
48
|
+
time = "0.00",
|
|
49
|
+
}: { name: string; id?: string; time?: string },
|
|
50
|
+
failures: string[]
|
|
51
|
+
) {
|
|
52
|
+
return xml(
|
|
53
|
+
"testcase",
|
|
54
|
+
{ id, name, time },
|
|
55
|
+
failures.map((stack) => failure({ text: stack }))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function failure({
|
|
60
|
+
text,
|
|
61
|
+
message = text.split("\n")[0],
|
|
62
|
+
}: {
|
|
63
|
+
text: string;
|
|
64
|
+
message?: string;
|
|
65
|
+
}) {
|
|
66
|
+
return xml("failure", { message }, [`<![CDATA[${text}]]>`]);
|
|
67
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterall,
|
|
3
|
+
aftereach,
|
|
4
|
+
beforeall,
|
|
5
|
+
beforeeach,
|
|
6
|
+
rootCases,
|
|
7
|
+
} from "./describe.js";
|
|
8
|
+
import { TestFailed, TestPassed, TestResult, TestSummary } from "./scope.js";
|
|
9
|
+
|
|
10
|
+
export async function execute(
|
|
11
|
+
prefix = "",
|
|
12
|
+
cases = rootCases()
|
|
13
|
+
): Promise<TestResult> {
|
|
14
|
+
const beforeallfn = cases[beforeall] ?? (() => {});
|
|
15
|
+
const beforeeachfn = cases[beforeeach] ?? (() => {});
|
|
16
|
+
const afterallfn = cases[afterall] ?? (() => {});
|
|
17
|
+
const aftereachfn = cases[aftereach] ?? (() => {});
|
|
18
|
+
|
|
19
|
+
const result: TestResult = { executed: 0, passed: 0, failed: 0, total: 0 };
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await beforeallfn();
|
|
23
|
+
} catch (e) {
|
|
24
|
+
result["_beforeAll"] = { error: e };
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const [title, block] of Object.entries(cases)) {
|
|
29
|
+
if (typeof title === "symbol") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (block instanceof Function) {
|
|
33
|
+
try {
|
|
34
|
+
result.executed += 1;
|
|
35
|
+
await beforeeachfn();
|
|
36
|
+
await block();
|
|
37
|
+
await aftereachfn();
|
|
38
|
+
result.passed += 1;
|
|
39
|
+
result[title] = { passed: true };
|
|
40
|
+
} catch (e) {
|
|
41
|
+
result.failed += 1;
|
|
42
|
+
result[title] = { error: /** @type Error */ e };
|
|
43
|
+
}
|
|
44
|
+
} else if (block) {
|
|
45
|
+
const run = await execute(title, block);
|
|
46
|
+
result.executed += run.executed;
|
|
47
|
+
result.passed += run.passed;
|
|
48
|
+
result.failed += run.failed;
|
|
49
|
+
result[title] = run;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await afterallfn();
|
|
55
|
+
} catch (e) {
|
|
56
|
+
result["_afterAll"] = { error: e };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getError({ error }: TestResult) {
|
|
63
|
+
if (typeof error == "string") {
|
|
64
|
+
return error;
|
|
65
|
+
} else if ((error as TestResult).message) {
|
|
66
|
+
return (error as TestResult).stack;
|
|
67
|
+
} else {
|
|
68
|
+
return "unknown error";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FlatResult {
|
|
73
|
+
test: string;
|
|
74
|
+
stack?: string | number | TestResult | TestSummary;
|
|
75
|
+
stats: { executed: number; failed: number };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function makeResult(
|
|
79
|
+
test: string,
|
|
80
|
+
result: TestResult | TestSummary
|
|
81
|
+
): FlatResult[] {
|
|
82
|
+
if ((result as TestFailed).error)
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
test,
|
|
86
|
+
stack: getError(result as TestResult),
|
|
87
|
+
stats: { executed: 1, failed: 1 },
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
if ((result as TestPassed).passed) {
|
|
91
|
+
return [{ test, stats: { executed: 1, failed: 0 } }];
|
|
92
|
+
}
|
|
93
|
+
return flattenResults(result as TestResult, test);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function flattenResults(results: TestResult, prefix = ""): FlatResult[] {
|
|
97
|
+
const arrow = prefix == "" ? "" : " -> ";
|
|
98
|
+
let errorList: FlatResult[] = [];
|
|
99
|
+
for (const [title, result] of Object.entries(results).filter(
|
|
100
|
+
([key]) => !["executed", "passed", "failed"].includes(key)
|
|
101
|
+
)) {
|
|
102
|
+
const test = `${prefix}${arrow}${title}`;
|
|
103
|
+
if (typeof result == "number") continue;
|
|
104
|
+
const flatResult = makeResult(test, result);
|
|
105
|
+
errorList = errorList.concat(flatResult);
|
|
106
|
+
}
|
|
107
|
+
return errorList;
|
|
108
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { assert } from "../assert.js";
|
|
2
|
+
import { equals } from "../equal.js";
|
|
3
|
+
|
|
4
|
+
export class Matcher<T> {
|
|
5
|
+
actual: T;
|
|
6
|
+
constructor(actual: T) {
|
|
7
|
+
this.actual = actual;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get not(): Matcher<T> {
|
|
11
|
+
return new NotMatcher(this.actual);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
toBe(expected: T) {
|
|
15
|
+
assert(this.actual === expected, () => `${this.actual} !== ${expected}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toEqual(expected: T) {
|
|
19
|
+
assert(
|
|
20
|
+
equals(this.actual, expected),
|
|
21
|
+
() =>
|
|
22
|
+
`Objects are not equivalent: ${JSON.stringify(
|
|
23
|
+
this.actual
|
|
24
|
+
)}, ${JSON.stringify(expected)}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toMatch(expected: RegExp | string) {
|
|
29
|
+
assert(
|
|
30
|
+
typeof this.actual === "string",
|
|
31
|
+
() => "Must have string for regexp match"
|
|
32
|
+
);
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
const actual: string = this.actual;
|
|
35
|
+
if (typeof expected === "string") {
|
|
36
|
+
assert(
|
|
37
|
+
actual.includes(expected),
|
|
38
|
+
() => `${actual} does not include ${expected}`
|
|
39
|
+
);
|
|
40
|
+
} else {
|
|
41
|
+
assert(
|
|
42
|
+
expected.test(actual),
|
|
43
|
+
() => `${actual} does not match ${expected}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
toMatchObject(expected: Partial<T>) {
|
|
49
|
+
for (const [k, v] of Object.entries(expected)) {
|
|
50
|
+
// @ts-expect-error
|
|
51
|
+
const actual: Partial<T> = this.actual[k];
|
|
52
|
+
assert(
|
|
53
|
+
equals(actual, v),
|
|
54
|
+
() =>
|
|
55
|
+
`Comparing ${k}, properties not equal: ${JSON.stringify(
|
|
56
|
+
actual
|
|
57
|
+
)}, ${JSON.stringify(v)}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
toBeNull() {
|
|
63
|
+
assert(
|
|
64
|
+
this.actual === null,
|
|
65
|
+
() => `Expected null, got ${JSON.stringify(this.actual)}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
toThrow(message = "") {
|
|
70
|
+
let didThrow = false;
|
|
71
|
+
|
|
72
|
+
let result: unknown = undefined;
|
|
73
|
+
try {
|
|
74
|
+
// @ts-expect-error
|
|
75
|
+
result = this.actual();
|
|
76
|
+
} catch ({ message: e }) {
|
|
77
|
+
assert(
|
|
78
|
+
// @ts-expect-error
|
|
79
|
+
(e ?? "").match(message),
|
|
80
|
+
() => `Expected thrown message to match ${message}, got ${e}`
|
|
81
|
+
);
|
|
82
|
+
didThrow = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
assert(didThrow, () => `Expected throw but got ${JSON.stringify(result)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class NotMatcher<T> {
|
|
90
|
+
actual: T;
|
|
91
|
+
constructor(actual: T) {
|
|
92
|
+
this.actual = actual;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get not(): Matcher<T> {
|
|
96
|
+
return new Matcher(this.actual);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
toBe(expected: T) {
|
|
100
|
+
assert(this.actual !== expected, () => `${this.actual} === ${expected}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
toEqual(expected: T) {
|
|
104
|
+
assert(
|
|
105
|
+
!equals(this.actual, expected),
|
|
106
|
+
() =>
|
|
107
|
+
`Objects are equivalent: ${JSON.stringify(
|
|
108
|
+
this.actual
|
|
109
|
+
)}, ${JSON.stringify(expected)}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
toMatch(expected: RegExp | string) {
|
|
114
|
+
assert(
|
|
115
|
+
typeof this.actual === "string",
|
|
116
|
+
() => "Must have string for regexp match"
|
|
117
|
+
);
|
|
118
|
+
// @ts-expect-error
|
|
119
|
+
const actual: string = this.actual;
|
|
120
|
+
if (typeof expected === "string") {
|
|
121
|
+
assert(
|
|
122
|
+
!actual.includes(expected),
|
|
123
|
+
() => `${actual} includes ${expected}`
|
|
124
|
+
);
|
|
125
|
+
} else {
|
|
126
|
+
assert(!expected.test(actual), () => `${actual} matches ${expected}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
toMatchObject(expected: Partial<T>) {
|
|
131
|
+
for (const [k, v] of Object.entries(expected)) {
|
|
132
|
+
// @ts-expect-error
|
|
133
|
+
const actual = this.actual[k];
|
|
134
|
+
assert(
|
|
135
|
+
!equals(actual, v),
|
|
136
|
+
() =>
|
|
137
|
+
`Comparing ${k}, properties equal: ${JSON.stringify(
|
|
138
|
+
actual
|
|
139
|
+
)}, ${JSON.stringify(v)}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
toBeNull() {
|
|
145
|
+
assert(this.actual !== null, () => `Expected not null`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
toThrow(message = "") {
|
|
149
|
+
let didThrow = false;
|
|
150
|
+
|
|
151
|
+
let result: unknown = undefined;
|
|
152
|
+
try {
|
|
153
|
+
// @ts-expect-error
|
|
154
|
+
result = this.actual();
|
|
155
|
+
} catch ({ message: e }) {
|
|
156
|
+
assert(
|
|
157
|
+
// @ts-expect-error
|
|
158
|
+
(e ?? "").match(message),
|
|
159
|
+
() => `Expected thrown message to match ${message}, got ${e}`
|
|
160
|
+
);
|
|
161
|
+
didThrow = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
assert(!didThrow, () => `Expected throw but got ${JSON.stringify(result)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function expect<T>(t: T): Matcher<T> {
|
|
169
|
+
return new Matcher(t);
|
|
170
|
+
}
|
package/src/scope/fix.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Given a value with numbers, attempt to fix all numbers to 1 decimal point.
|
|
3
|
+
*/
|
|
4
|
+
export function fix<T>(n: T): T {
|
|
5
|
+
if (typeof n === "number") {
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
return +n.toFixed(1) as T;
|
|
8
|
+
}
|
|
9
|
+
if (n !== Object(n)) {
|
|
10
|
+
// A primitive
|
|
11
|
+
return n;
|
|
12
|
+
}
|
|
13
|
+
if (n instanceof Array) {
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
return n.map(fix) as T;
|
|
16
|
+
}
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
return mapreduce<T>(fix, n as Record<string, T>);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function mapreduce<T, U>(
|
|
22
|
+
fn: (t: T) => U,
|
|
23
|
+
iter: Record<string, T>
|
|
24
|
+
): Record<string, U> {
|
|
25
|
+
return Object.entries(iter).reduce(
|
|
26
|
+
(acc, [k, v]) => ((acc[k] = fn(v)), acc),
|
|
27
|
+
{} as Record<string, U>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface TestCase {
|
|
2
|
+
[k: string]: Function | TestCase;
|
|
3
|
+
[k: symbol]: Function;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface TestResult {
|
|
7
|
+
executed: number;
|
|
8
|
+
passed: number;
|
|
9
|
+
failed: number;
|
|
10
|
+
[k: string]: TestResult | TestSummary | number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type TestSummary = TestFailed | TestPassed;
|
|
14
|
+
|
|
15
|
+
export interface TestFailed {
|
|
16
|
+
error: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TestPassed {
|
|
20
|
+
passed: true;
|
|
21
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { MiddlewareFactory } from "./index.js";
|
|
4
|
+
import { fileResponse } from "./response.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Searches up the request path until the first index is found.
|
|
8
|
+
*/
|
|
9
|
+
export const findIndex: MiddlewareFactory =
|
|
10
|
+
async ({ root }) =>
|
|
11
|
+
async (req) => {
|
|
12
|
+
let filename = path.join(root, req.url ?? "");
|
|
13
|
+
if (path.basename(filename).match(/\.[a-z]{1,3}$/)) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
while (filename.startsWith(root)) {
|
|
17
|
+
const index = path.join(filename, "index.html");
|
|
18
|
+
try {
|
|
19
|
+
const stat = await fs.stat(index);
|
|
20
|
+
return fileResponse(index, stat);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
filename = path.dirname(filename);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
};
|