forked from rosa/hakurei
Compare commits
7 Commits
f6e1fbc1b5
...
f2acdc6cfc
| Author | SHA1 | Date | |
|---|---|---|---|
|
f2acdc6cfc
|
|||
|
6ef93246b7
|
|||
|
262f1ec663
|
|||
|
4c8a824e0b
|
|||
|
61c24a5bfd
|
|||
|
45205619ca
|
|||
|
d749d46bd1
|
@@ -5,12 +5,7 @@ type TestTree = TestGroup | Test;
|
|||||||
type TestGroup = { name: string; children: TestTree[] };
|
type TestGroup = { name: string; children: TestTree[] };
|
||||||
type Test = { name: string; test: (t: TestController) => void };
|
type Test = { name: string; test: (t: TestController) => void };
|
||||||
|
|
||||||
// A registrar provides a central location to register test suites.
|
|
||||||
export class TestRegistrar {
|
export class TestRegistrar {
|
||||||
// Note that, while this is equivalent to a new tree node sans a name, the
|
|
||||||
// lack of a name provides the illusion of multiple “top-level” suites,
|
|
||||||
// while still allowing reporters to pick their favorite name—say, “JS
|
|
||||||
// tests”—were they to need to label all suites together.
|
|
||||||
#suites: TestGroup[];
|
#suites: TestGroup[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -49,10 +44,6 @@ export function test(name: string, test: (t: TestController) => void): TestTree
|
|||||||
return { name, test };
|
return { name, test };
|
||||||
}
|
}
|
||||||
|
|
||||||
// While this function could certainly refine the type to a map instead of
|
|
||||||
// simply checking for duplicates and discarding that knowledge, these test
|
|
||||||
// trees are primarily for flooding—that is, iteration—for which an array is
|
|
||||||
// better suited.
|
|
||||||
function checkDuplicates(parent: string, names: { name: string }[]) {
|
function checkDuplicates(parent: string, names: { name: string }[]) {
|
||||||
let seen = new Set<string>();
|
let seen = new Set<string>();
|
||||||
for (const { name } of names) {
|
for (const { name } of names) {
|
||||||
@@ -158,37 +149,11 @@ function extractExceptionString(e: any): string {
|
|||||||
// Reporting
|
// Reporting
|
||||||
|
|
||||||
export interface Reporter {
|
export interface Reporter {
|
||||||
// A notable feature—or flaw, to some—of the DSL is that the tree of tests
|
|
||||||
// is statically known, which might greatly aid in implementing a reporter.
|
|
||||||
register(suites: TestGroup[]): void;
|
register(suites: TestGroup[]): void;
|
||||||
// While we could simply call a function with a tree representing all
|
|
||||||
// results, which would indeed greatly simplify implementation of reporters,
|
|
||||||
// simply registering a path and allowing the reporter to—either implicitly
|
|
||||||
// or explicitly—construct a tree themselves allows for results to be
|
|
||||||
// *incrementally reported*, instead of a great deal of silence until all
|
|
||||||
// tests finish.
|
|
||||||
update(path: string[], result: TestResult): void;
|
update(path: string[], result: TestResult): void;
|
||||||
// With just update(), the reporter never knows when all tests have
|
|
||||||
// completed. The simplest possible use for this is to notify the user, but
|
|
||||||
// its intent is actually more tailored to the StreamReporter scenario:
|
|
||||||
// while destructively updated report rendering (as with a tree of DOM nodes
|
|
||||||
// which are mutated) always displays the results in a structured manner
|
|
||||||
// matching that of the tests, “rerendering” or otherwise destructively
|
|
||||||
// updating the rendered output might be infeasible in some paradigms, such
|
|
||||||
// as command-line applications—all existing implementations of such
|
|
||||||
// rendering both mess up the scrollback position and necessarily crop out
|
|
||||||
// some of the data at the bottom (since the top of the tree is forced to be
|
|
||||||
// at the top of the screen, as one cannot unscroll portably). Explicitly
|
|
||||||
// signaling to the reporter that no more results will be received permits
|
|
||||||
// it to simply display live test progress linearly, while building up
|
|
||||||
// a tree and displaying it once it's known the tree is complete.
|
|
||||||
finalize(): void;
|
finalize(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A reporter that diligently reports absolutely nothing. This is essentially
|
|
||||||
// a way to “undo” the incremental reporting update() provides, getting back the
|
|
||||||
// underlying result tree; this makes it extremely convenient in some cases like
|
|
||||||
// testing ourself.
|
|
||||||
export class NoOpReporter implements Reporter {
|
export class NoOpReporter implements Reporter {
|
||||||
suites: TestGroup[];
|
suites: TestGroup[];
|
||||||
results: ({ path: string[] } & TestResult)[];
|
results: ({ path: string[] } & TestResult)[];
|
||||||
@@ -219,7 +184,6 @@ export interface Stream {
|
|||||||
|
|
||||||
const SEP = " ❯ ";
|
const SEP = " ❯ ";
|
||||||
|
|
||||||
// A simple reporter that outputs to some stream; suitable for CLIs.
|
|
||||||
export class StreamReporter implements Reporter {
|
export class StreamReporter implements Reporter {
|
||||||
stream: Stream;
|
stream: Stream;
|
||||||
verbose: boolean;
|
verbose: boolean;
|
||||||
@@ -239,7 +203,6 @@ export class StreamReporter implements Reporter {
|
|||||||
return this.#successes.length > 0 && this.#failures.length === 0;
|
return this.#successes.length > 0 && this.#failures.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't need the structure for reporting.
|
|
||||||
register(suites: TestGroup[]) {}
|
register(suites: TestGroup[]) {}
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
update(path: string[], result: TestResult) {
|
||||||
@@ -248,17 +211,6 @@ export class StreamReporter implements Reporter {
|
|||||||
switch (result.state) {
|
switch (result.state) {
|
||||||
case "success":
|
case "success":
|
||||||
this.#successes.push({ path, ...result });
|
this.#successes.push({ path, ...result });
|
||||||
// NOTE: emojis are used instead of colored Unicode symbols as
|
|
||||||
// coloring isn't possible through all streams, which would make
|
|
||||||
// this terminal-specific, and even in terminals and emulators
|
|
||||||
// thereof, it's very tedious to correctly detect whether one should
|
|
||||||
// use colors (https://no-color.org, https://bixense.com/clicolors,
|
|
||||||
// https://force-color.org), ensure reasonable contrast is retained
|
|
||||||
// on every possible theme (using reverse video is often the only
|
|
||||||
// way), and be immediately noticeable. Emojis have an upper hand in
|
|
||||||
// that they're more common than obscure Unicode characters—which
|
|
||||||
// also means you're more likely to have an emoji font but not
|
|
||||||
// a font with those symbols—and that they're double-width.
|
|
||||||
if (this.verbose) this.stream.writeln(`✅️ ${pathStr}`);
|
if (this.verbose) this.stream.writeln(`✅️ ${pathStr}`);
|
||||||
break;
|
break;
|
||||||
case "failure":
|
case "failure":
|
||||||
@@ -286,10 +238,8 @@ export class StreamReporter implements Reporter {
|
|||||||
#displaySection(name: string, data: ({ path: string[] } & TestResult)[], ignoreEmpty: boolean = false) {
|
#displaySection(name: string, data: ({ path: string[] } & TestResult)[], ignoreEmpty: boolean = false) {
|
||||||
if (!data.length) return;
|
if (!data.length) return;
|
||||||
|
|
||||||
// Transform [{ path: ["a", "b", "c"] }, { path: ["a", "b", "d"] }] into
|
// Transform [{ path: ["a", "b", "c"] }, { path: ["a", "b", "d"] }]
|
||||||
// { "a ❯ b": ["c", "d"] }. NOTE: intermediate nodes are collapsed as
|
// into { "a ❯ b": ["c", "d"] }.
|
||||||
// excessive nesting is difficult to convey clearly in a text-only
|
|
||||||
// environment.
|
|
||||||
let pathMap = new Map<string, ({ name: string } & TestResult)[]>();
|
let pathMap = new Map<string, ({ name: string } & TestResult)[]>();
|
||||||
for (const t of data) {
|
for (const t of data) {
|
||||||
if (t.path.length === 0) throw new RangeError("path is empty");
|
if (t.path.length === 0) throw new RangeError("path is empty");
|
||||||
@@ -338,14 +288,11 @@ function assertGetElementById(id: string): HTMLElement {
|
|||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A reporter that directly translates a tree of results into a tree of
|
|
||||||
// collapsible elements in the DOM.
|
|
||||||
export class DOMReporter implements Reporter {
|
export class DOMReporter implements Reporter {
|
||||||
// It is very difficult to implement this using the statically known tree,
|
// It is very difficult to implement this using the statically known tree,
|
||||||
// because Map doesn't handle array keys properly (to store the path), and
|
// because Map doesn't handle array keys properly (to store the path), and
|
||||||
// it's unknown of there's any way to implement it without writing one's own
|
// it's unknown of there's any way to implement it without writing one's own
|
||||||
// data types. Oh well; using the DOM as a data structure might seem hacky
|
// data types.
|
||||||
// but it does have its benefits, apart from encouraging a tagless final.
|
|
||||||
register(suites: TestGroup[]) {}
|
register(suites: TestGroup[]) {}
|
||||||
|
|
||||||
update(path: string[], result: TestResult) {
|
update(path: string[], result: TestResult) {
|
||||||
@@ -385,7 +332,6 @@ export class DOMReporter implements Reporter {
|
|||||||
|
|
||||||
switch (result.state) {
|
switch (result.state) {
|
||||||
case "failure":
|
case "failure":
|
||||||
// Only expand failures, to minimize successes and skips.
|
|
||||||
child.open = true;
|
child.open = true;
|
||||||
child.classList.add("failure");
|
child.classList.add("failure");
|
||||||
child.classList.remove("skip");
|
child.classList.remove("skip");
|
||||||
@@ -456,8 +402,6 @@ export class GoTestReporter implements Reporter {
|
|||||||
console.log(JSON.stringify({ path, state, logs: result.logs }));
|
console.log(JSON.stringify({ path, state, logs: result.logs }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unnecessary but convenient on the Go side, so that it doesn't have to
|
|
||||||
// infer this via process exit.
|
|
||||||
finalize() {
|
finalize() {
|
||||||
console.log("null");
|
console.log("null");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user