risei 1.0.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.
package/index.js ADDED
@@ -0,0 +1,122 @@
1
+ /**/
2
+
3
+ /* Copyright (c) 2023 Ed Fallin. Published under the terms of the MIT license. */
4
+
5
+ /* This index.js is the entry point for running Risei tests from
6
+ other code. It imports other modules to perform its work.
7
+ It also exports modules that should / can be subclassed. */
8
+
9
+ // region Imports
10
+
11
+ // region Test-running dependencies
12
+
13
+ import { TestRunner } from "./public/javascript/TestRunner.js";
14
+ import { LocalCaller } from "./public/javascript/LocalCaller.js";
15
+ import { TerminalReporter } from "./public/javascript/TerminalReporter.js";
16
+ import { TestFinder } from "./public/javascript/TestFinder.js";
17
+
18
+ // endregion Test-running dependencies
19
+
20
+ // region Display dependencies
21
+
22
+ import chalk from "chalk";
23
+ import { Moment } from "./public/javascript/Moment.js";
24
+
25
+ // endregion Display dependencies
26
+
27
+ // endregion Imports
28
+
29
+ // region Exports
30
+
31
+ // region Classes that users typically subclass
32
+
33
+ export * from "./public/javascript/ATestSource.js";
34
+
35
+ // endregion Classes that users typically subclass
36
+
37
+ // region Classes that users might subclass for uncommon cases
38
+
39
+ export * from "./public/javascript/ATestFinder.js";
40
+ export * from "./public/javascript/ATestReporter.js";
41
+
42
+ // endregion Classes that users might subclass for uncommon cases
43
+
44
+ // endregion Exports
45
+
46
+ // region Named styles
47
+
48
+ /* Display styles for the start title and other general needs. */
49
+ const title = chalk.hex("FFFFFF").bgHex("191970").bold;
50
+
51
+ // endregion Named styles
52
+
53
+ // region Styling for color stripes
54
+
55
+ let wide = (text) => {
56
+ let width = process.stdout.columns;
57
+ text = text || "";
58
+
59
+ return text.padEnd(width, "\u00A0");
60
+ };
61
+
62
+ // endregion Styling for color stripes
63
+
64
+ // region Key callable, its scriptable call, and its export
65
+
66
+ async function runRiseiTests(testFinderPath) {
67
+ // region Converting test-finder from path to class
68
+
69
+ testFinderPath = testFinderPath || "./public/javascript/TestFinder.js";
70
+ let finderModule = await import(testFinderPath);
71
+
72
+ let moduleKeys = Object.keys(finderModule);
73
+ let classKey = moduleKeys[0];
74
+ let finderClass = finderModule[classKey];
75
+
76
+ // endregion Converting test-finder from path to class
77
+
78
+ // region Intro / title
79
+
80
+ console.clear();
81
+ console.log();
82
+ console.log(title(wide()));
83
+
84
+ let now = new Date();
85
+ now = new Moment(now);
86
+
87
+ console.log(title(wide(` Risei tests run on ${ now.asReadable() } local time.`)));
88
+ console.log(title(wide()));
89
+
90
+ console.log();
91
+
92
+ // endregion Intro / title
93
+
94
+ // region Running tests
95
+
96
+ // Test-system objects and their relationships.
97
+ const finder = new finderClass.prototype.constructor();
98
+ const runner = new TestRunner();
99
+ const reporter = new TerminalReporter();
100
+
101
+ const caller = new LocalCaller(finder, runner, reporter);
102
+
103
+ // Actually running the tests using this system.
104
+ await caller.runAllTests();
105
+
106
+ // endregion Running tests
107
+
108
+ // region Trailing formatting
109
+
110
+ console.log();
111
+ console.log();
112
+
113
+ // endregion Trailing formatting
114
+ }
115
+
116
+ /* Runs tests now. */
117
+ await runRiseiTests();
118
+
119
+ export default runRiseiTests;
120
+
121
+ // endregion Key callable, its scriptable call, and its export
122
+
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "risei",
3
+ "version": "1.0.0",
4
+ "description": "RiseiJs is the framework that allows you to write unit tests as collections of values in JavaScript objects, so it's easy and fast, and tests don't serve as a drag on redesigns.",
5
+ "keywords":[ "unit test", "test", "values", "objects", "easy", "fast" ],
6
+ "author": "Ed Fallin <riseijsmaker@gmail.com>",
7
+ "license": "MIT",
8
+ "type": "module",
9
+ "private": false,
10
+ "scripts": {
11
+ "start": "node ./bin/www",
12
+ "risei": "node ./index.js",
13
+ "rtest": "clear; node ./public/javascript/Risei.js",
14
+ "xtest": "clear; mocha **/*.tests.js",
15
+ "test": "clear; mocha **/*.tests.js; node ./public/javascript/Risei.js"
16
+ },
17
+ "risei": {
18
+ "tests": "**.rt.js"
19
+ },
20
+ "exports":{
21
+ ".": "./index.js",
22
+ "./ATestSource": "./public/javascript/ATestSource.js"
23
+ },
24
+ "dependencies": {
25
+ "chalk": "^5.0.0",
26
+ "fs": "^0.0.1-security",
27
+ "minimatch": "^9.0.1"
28
+ },
29
+ "devDependencies": {
30
+ "chai": "^4.3.6",
31
+ "chalk": "^5.0.0",
32
+ "cookie-parser": "~1.4.4",
33
+ "debug": "~2.6.9",
34
+ "express": "~4.16.1",
35
+ "fs": "^0.0.1-security",
36
+ "mocha": "^10.0.0",
37
+ "morgan": "~1.9.1"
38
+ }
39
+ }
@@ -0,0 +1,9 @@
1
+ /**/
2
+
3
+ /* Defines operations that a comparer must implement. */
4
+
5
+ export class AComparer {
6
+ compare(expected, actual) {
7
+ throw new Error("Implement compare() on a subclass of AComparer.");
8
+ }
9
+ }
@@ -0,0 +1,84 @@
1
+ /**/
2
+
3
+ import { ATestFixture } from "./ATestFixture.js";
4
+ import { SpoofTuple } from "./SpoofTuple.js";
5
+
6
+ export class ASpoofingFixture extends ATestFixture {
7
+ constructor(name, kind) {
8
+ super(name, kind);
9
+ }
10
+
11
+ /* A spoof might be supplied as a custom function,
12
+ but it might just be defined by a return value. */
13
+ spoofMethod(spoofOutput) {
14
+ // No spoof supplied, so spoof is an empty function.
15
+ if (spoofOutput === undefined) {
16
+ let empty = () => { };
17
+ return empty;
18
+ }
19
+
20
+ // Spoof is a custom function to use as-is.
21
+ if (spoofOutput instanceof Function) {
22
+ return spoofOutput;
23
+ }
24
+
25
+ // Recursive case: Spoof defines a whole object
26
+ // tree to be returned as method output.
27
+ if (Array.isArray(spoofOutput)) {
28
+ if (spoofOutput.every(x => SpoofTuple.isASpoof(x))) {
29
+ spoofOutput = SpoofTuple.fromNonceTuples(spoofOutput);
30
+ spoofOutput = this.spoofObjectTree(spoofOutput);
31
+ }
32
+ }
33
+
34
+ // Commonest case: Spoof is a value to be returned.
35
+ let spoof = () => { return spoofOutput; }
36
+ return spoof;
37
+ }
38
+
39
+ /* Returns an object with all spoofed methods,
40
+ possibly with intermediate objects. */
41
+ spoofObjectTree(ofAsPairs) {
42
+ let spoof = {};
43
+
44
+ for (let pair of ofAsPairs) {
45
+ let chain = pair.of;
46
+ let output = pair.as;
47
+
48
+ // Individual member names are needed.
49
+ let names = chain.split(".");
50
+
51
+ // Top object and traverser from it.
52
+ let top = spoof;
53
+ let next = top;
54
+ let name;
55
+
56
+ // New objects as members leafward.
57
+ while (names.length > 1) {
58
+ // Object member's name.
59
+ name = names.shift();
60
+
61
+ // Actually spoofing object.
62
+ if (!next[name]) {
63
+ next[name] = {};
64
+ }
65
+
66
+ // Stepping leafward.
67
+ next = next[name];
68
+ }
69
+
70
+ // Final or only name is the leaf method;
71
+ // name may mistakenly end with `()`.
72
+ name = names.shift();
73
+ name = name.replace("()", "");
74
+
75
+ // Actually spoofing method.
76
+ let method = this.spoofMethod(output);
77
+ next[name] = method;
78
+ }
79
+
80
+ // And back to caller.
81
+ return spoof;
82
+ }
83
+
84
+ }
@@ -0,0 +1,61 @@
1
+ /**/
2
+
3
+ /* Defines operations that a test caller (which requests
4
+ the running / reporting of tests) must implement.
5
+ Uses the ATestRunner to run all the tests via a generator,
6
+ and the ATestReporter for out / formatting if needed.
7
+ A = abstract. */
8
+
9
+ import { TestRunner } from "./TestRunner.js";
10
+ import { ATestReporter } from "./ATestReporter.js";
11
+
12
+ export class ATestCaller {
13
+ // region Private fields
14
+
15
+ #finder;
16
+ #runner;
17
+ #reporter;
18
+
19
+ // endregion Private fields
20
+
21
+ // region Properties
22
+
23
+ get finder() {
24
+ return this.#finder;
25
+ }
26
+
27
+ set finder(value) {
28
+ this.#finder = value;
29
+ }
30
+
31
+ get runner() {
32
+ return this.#runner;
33
+ }
34
+
35
+ set runner(value) {
36
+ this.#runner = value;
37
+ }
38
+
39
+ get reporter() {
40
+ return this.#reporter;
41
+ }
42
+
43
+ set reporter(value) {
44
+ this.#reporter = value;
45
+ }
46
+
47
+ // endregion Properties
48
+
49
+ constructor(finder, runner, reporter) {
50
+ this.#finder = finder;
51
+ this.#runner = runner;
52
+ this.#reporter = reporter;
53
+ }
54
+
55
+ async runAllTests() {
56
+ throw new Error(
57
+ "Implement runAllTests() on a subclass, " +
58
+ "using .reporter for out / formatting if needed."
59
+ );
60
+ }
61
+ }
@@ -0,0 +1,25 @@
1
+ /**/
2
+
3
+ /* Defines operations that test-finding classes should implement. A = abstract. */
4
+
5
+ import { ATestSource } from "./ATestSource.js";
6
+
7
+ export class ATestFinder {
8
+ #testSources;
9
+
10
+ get testSources() {
11
+ return this.#testSources;
12
+ }
13
+
14
+ set testSources(value) {
15
+ this.#testSources = value;
16
+ }
17
+
18
+ constructor() {
19
+ this.#testSources = [];
20
+ }
21
+
22
+ async findAllTests() {
23
+ throw new Error("Implement findAllTests() on a subclass of ATestFinder.");
24
+ }
25
+ }
@@ -0,0 +1,44 @@
1
+ /**/
2
+
3
+ export class ATestFixture {
4
+ // region Private fields
5
+
6
+ #name;
7
+ #kind;
8
+ #fixture;
9
+
10
+ // endregion Private fields
11
+
12
+ // region Properties
13
+
14
+ get name() {
15
+ return this.#name;
16
+ }
17
+
18
+ set name(value) {
19
+ this.#name = value;
20
+ }
21
+
22
+ get kind() {
23
+ return this.#kind;
24
+ }
25
+
26
+ set kind(value) {
27
+ this.#kind = value;
28
+ }
29
+
30
+ get fixture() {
31
+ return this.#fixture;
32
+ }
33
+
34
+ set fixture(value) {
35
+ this.#fixture = value;
36
+ }
37
+
38
+ // endregion Properties
39
+
40
+ constructor(name, kind) {
41
+ this.#name = name;
42
+ this.#kind = kind;
43
+ }
44
+ }
@@ -0,0 +1,25 @@
1
+ /**/
2
+
3
+ /* Defines operations that a test reporter must implement. A = abstract. */
4
+
5
+ import chalk from "chalk";
6
+
7
+ export class ATestReporter {
8
+ // Reports whatever was just provided by the TestRunner.
9
+ // Can delegate to the other report-x methods.
10
+ reportNext(result) {
11
+ }
12
+
13
+ reportGroup() {
14
+ }
15
+
16
+ reportPassed(result) {
17
+ }
18
+
19
+ reportFailed(result) {
20
+ }
21
+
22
+ reportSummary(summary) {
23
+ }
24
+
25
+ }
@@ -0,0 +1,8 @@
1
+ /**/
2
+
3
+ /* Defines operations that a source of tests must (trivially) implement, basically listing tests and fixtures. */
4
+
5
+ export class ATestSource {
6
+ /* Must be implemented by subclasses. */
7
+ tests = [];
8
+ }
@@ -0,0 +1,30 @@
1
+ /**/
2
+
3
+ /* ChosenTestFinder is an ATestFinder that finds tests in the places coded in its constructor. */
4
+
5
+ import { ATestFinder } from "./ATestFinder.js";
6
+ import { Tests } from "./topic-tests/Tests.rt.js";
7
+ import { SelfTests } from "./self-tests/SelfTests.rt.js";
8
+
9
+ export class ChosenTestFinder extends ATestFinder {
10
+ constructor() {
11
+ super();
12
+
13
+ let testSource = new Tests();
14
+ let selfTestSource = new SelfTests();
15
+ this.testSources = [ testSource, selfTestSource ];
16
+ }
17
+
18
+ async findAllTests() {
19
+ let tests = [];
20
+
21
+ for (let source of this.testSources) {
22
+ let local = source.tests;
23
+ tests.push(...local);
24
+ }
25
+
26
+ // Returning an awaitable value
27
+ // to match async signature.
28
+ return Promise.resolve(tests);
29
+ }
30
+ }
@@ -0,0 +1,8 @@
1
+ /**/
2
+
3
+ import { TestGroup } from "./TestGroup.js";
4
+
5
+ /* Identifies a grouping of tests by class. No specialized workings. */
6
+
7
+ export class ClassTestGroup extends TestGroup {
8
+ }
@@ -0,0 +1,22 @@
1
+ /**/
2
+
3
+ /* LocalCaller is an ATestCaller that invokes each available test in turn locally
4
+ on the server, without HTTP calls from a browser or other external source. */
5
+
6
+ import { ATestFinder } from "./ATestFinder.js";
7
+ import { ATestCaller } from "./ATestCaller.js";
8
+ import { TerminalReporter } from "./TerminalReporter.js";
9
+ import { TestRunner } from "./TestRunner.js";
10
+
11
+ export class LocalCaller extends ATestCaller {
12
+ async runAllTests() {
13
+ let tests = await this.finder.findAllTests();
14
+ this.runner.useTests(tests);
15
+
16
+ // Getting each run test / summary result
17
+ // and having it reported in the terminal.
18
+ for (let result of this.runner) {
19
+ this.reporter.reportNext(result);
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,8 @@
1
+ /**/
2
+
3
+ import { TestGroup } from "./TestGroup.js";
4
+
5
+ /* Identifies a grouping of tests by method. No specialized workings. */
6
+
7
+ export class MethodTestGroup extends TestGroup {
8
+ }
@@ -0,0 +1,29 @@
1
+ /**/
2
+
3
+ export class Moment {
4
+ #now;
5
+
6
+ constructor(now) {
7
+ this.#now = now;
8
+ }
9
+
10
+ asReadable() /* passed */ {
11
+ let now = this.#now;
12
+ let asTwo = this.#asTwo;
13
+
14
+ let day = `${ now.getMonth() + 1 }/${ asTwo(now.getDate()) }/${ now.getFullYear() }`;
15
+
16
+ let time = `${ now.getHours() % 12 || 12 }:${ asTwo(now.getMinutes()) }:${ asTwo(now.getSeconds()) } `
17
+ + `${ now.getHours() < 12 ? "AM" : "PM" }`;
18
+
19
+ return `${ day } at ${ time }`;
20
+ }
21
+
22
+ #asTwo(number) /* verified */ {
23
+ let text = `${ number }`;
24
+ text = text.padStart(2, "0");
25
+
26
+ return text;
27
+ }
28
+
29
+ }
@@ -0,0 +1,88 @@
1
+ /**/
2
+
3
+ /* Risei.js is the entry point for running Kitten / Koneko / Risei tests.
4
+ It imports other modules to do all of its work. */
5
+
6
+ // region Test-running dependencies
7
+
8
+ import { TestRunner } from "./TestRunner.js";
9
+ import { ChosenTestFinder } from "./ChosenTestFinder.js";
10
+ import { LocalCaller } from "./LocalCaller.js";
11
+ import { TerminalReporter } from "./TerminalReporter.js";
12
+
13
+ // endregion Test-running dependencies
14
+
15
+ // region Display dependencies
16
+
17
+ import chalk from "chalk";
18
+ import { Moment } from "./Moment.js";
19
+
20
+ // endregion Display dependencies
21
+
22
+ // region Named styles
23
+
24
+ /* Display styles for the start title and other general needs. */
25
+ const cal = chalk.hex("0000FF").bgHex("EEEE00").bold;
26
+ const onBlue = chalk.bgBlueBright;
27
+ const onGreen = chalk.bgGreenBright;
28
+
29
+ const future = chalk.hex("FFFFFF").bgHex("191970").bold;
30
+
31
+ /* Display styles for tests. */
32
+ const group = chalk.hex("FFFFFF").bgHex("0000CD");
33
+ const passed = chalk.hex("000000").bgHex("90EE90");
34
+ const failed = chalk.hex("000000").bgHex("F08080");
35
+
36
+ // endregion Named styles
37
+
38
+ // region Styling for color stripes
39
+
40
+ let wide = (text) => {
41
+ let width = process.stdout.columns;
42
+ text = text || "";
43
+
44
+ return text.padEnd(width, "\u00A0");
45
+ };
46
+
47
+ // endregion Styling for color stripes
48
+
49
+ // region Intro / title
50
+
51
+ /* To clear the console first, you can include `clear;` in the script
52
+ ahead of calling this, or else restore console.clear() here,
53
+ although the Risei output may then overwrite some prior output. */
54
+
55
+ console.log();
56
+ console.log(future(wide()));
57
+
58
+ let now = new Date();
59
+ now = new Moment(now);
60
+
61
+ console.log(future(wide(` Risei tests run on ${ now.asReadable() } local time.`)));
62
+ console.log(future(wide()));
63
+
64
+ console.log();
65
+
66
+ // endregion Intro / title
67
+
68
+ // region Running tests
69
+
70
+ // Test-system objects and their relationships.
71
+ const finder = new ChosenTestFinder();
72
+ const runner = new TestRunner(finder.findAllTests());
73
+ const reporter = new TerminalReporter();
74
+
75
+ const caller = new LocalCaller(finder, runner, reporter);
76
+
77
+ // Actually running the tests using this system.
78
+ await caller.runAllTests();
79
+
80
+ // endregion Running tests
81
+
82
+ // region Trailing formatting
83
+
84
+ console.log();
85
+ console.log();
86
+
87
+ // endregion Trailing formatting
88
+