testeranto 0.9.20

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/src/Report.tsx ADDED
@@ -0,0 +1,347 @@
1
+ import ReactDom from "react-dom/client";
2
+ import React, { useEffect, useState } from "react";
3
+
4
+ import Tab from 'react-bootstrap/Tab';
5
+ import Tabs from 'react-bootstrap/Tabs';
6
+ import Col from 'react-bootstrap/Col';
7
+ import Nav from 'react-bootstrap/Nav';
8
+ import Row from 'react-bootstrap/Row';
9
+
10
+ import 'bootstrap/dist/css/bootstrap.min.css';
11
+
12
+ export function Report() {
13
+
14
+ const [data, setData] = useState<{
15
+ configs: object,
16
+ features: {
17
+ features: any[]
18
+ },
19
+ tests: any[],
20
+ featureTests: object,
21
+ summaries: { dags: [], directed: [], undirected: [] }
22
+ }>({
23
+ configs: {},
24
+ features: {
25
+ features: []
26
+ },
27
+ tests: [],
28
+ featureTests: {},
29
+ summaries: { dags: [], directed: [], undirected: [] }
30
+ });
31
+
32
+ const getData = async () => {
33
+
34
+ const configs = await (await fetch('testeranto.config.json')).json();
35
+ const features = await (await fetch('TesterantoFeatures.json')).json();
36
+ const featureTests = await (await fetch('results/featureTestJoin.json')).json();
37
+ const summaries = await (await fetch('report.json')).json();
38
+ const testPromises = await configs.tests.map(async (test) => {
39
+ return await (await (await fetch(`results/${test[0]}.json`)).json())
40
+ })
41
+ const tests = await Promise.all(testPromises);
42
+
43
+ setData({ configs, features, tests, featureTests, summaries });
44
+ }
45
+
46
+ useEffect(() => { getData() }, []);
47
+
48
+ return (<div>
49
+ {
50
+ data && data.configs && <>
51
+ <Tabs defaultActiveKey="home">
52
+ <Tab eventKey="home" title="config">
53
+ <pre><code>{JSON.stringify(data.configs, null, 2)}</code></pre>
54
+ </Tab>
55
+ <Tab eventKey="features" title="features">
56
+ <Tab.Container id="left-tabs-example5" defaultActiveKey="feature-0">
57
+ <Row>
58
+ <Col sm={3}>
59
+ <Nav variant="pills" className="flex-column">
60
+ {data.features.features.map((feature, ndx) => <Nav.Item key={ndx}>
61
+ <Nav.Link eventKey={`feature-${ndx}`}>
62
+ {feature.name}
63
+ </Nav.Link>
64
+ </Nav.Item>)}
65
+ </Nav>
66
+ </Col>
67
+ <Col sm={9}>
68
+ <Tab.Content>
69
+ {data.features.features.map((feature, ndx) => <Tab.Pane eventKey={`feature-${ndx}`} key={ndx}>
70
+ <p>{feature.name}</p>
71
+
72
+
73
+ <Tab.Container id="left-tabs-example5" defaultActiveKey="relations-0">
74
+ <Row>
75
+ <Col sm={3}>
76
+ <Nav variant="pills" className="flex-column">
77
+ {feature.inNetworks.map((summary, ndx2) => <Nav.Item key={ndx2}>
78
+ <Nav.Link eventKey={`relations-${ndx2}`}>
79
+ {summary.network}
80
+ </Nav.Link>
81
+ </Nav.Item>)}
82
+ </Nav>
83
+ </Col>
84
+ <Col sm={9}>
85
+ <Tab.Content>
86
+ {feature.inNetworks.map((summary, ndx2) => <Tab.Pane eventKey={`relations-${ndx2}`} key={ndx2}>
87
+ <pre>{
88
+ JSON.stringify(summary.neighbors, null, 2)
89
+ }</pre>
90
+ </Tab.Pane>)}
91
+ </Tab.Content>
92
+ </Col>
93
+ </Row>
94
+ </Tab.Container>
95
+
96
+
97
+
98
+ </Tab.Pane>)}
99
+ </Tab.Content>
100
+ </Col>
101
+ </Row>
102
+ </Tab.Container>
103
+ </Tab>
104
+ <Tab eventKey="results" title="tests">
105
+ <Tab.Container id="left-tabs-example" defaultActiveKey="first">
106
+ <Row>
107
+ <Col sm={3}>
108
+ <Nav variant="pills" className="flex-column">
109
+ {data.tests.map((suite, ndx) => <Nav.Item key={ndx}>
110
+ <Nav.Link eventKey={`suite-${ndx}`}>
111
+ {(suite.fails.length > 0 ? `❌ * ${suite.fails.length.toString()}` : `✅ * ${suite.givens.length.toString()}`)} - {suite.name}
112
+ </Nav.Link>
113
+ </Nav.Item>)}
114
+ </Nav>
115
+ </Col>
116
+ <Col sm={9}>
117
+ <Tab.Content>
118
+ {data.tests.map((suite, ndx) => <Tab.Pane eventKey={`suite-${ndx}`} key={ndx}>
119
+ <Tab.Container id="left-tabs-example2" defaultActiveKey={`given-0`}>
120
+ <Row>
121
+ <Col sm={3}>
122
+ <Nav variant="pills" className="flex-column">
123
+ {suite.givens.map((g, ndx2) => <Nav.Item key={ndx2}>
124
+ <Nav.Link eventKey={`given-${ndx2}`}>
125
+ {(g.errors ? `❌` : `✅`)} - {g.name}
126
+ </Nav.Link>
127
+ </Nav.Item>)}
128
+ </Nav>
129
+ </Col>
130
+ <Col sm={9}>
131
+ <Tab.Content>
132
+ {suite.givens.map((g, ndx2) => <Tab.Pane key={ndx2} eventKey={`given-${ndx2}`} >
133
+ <p>when</p>
134
+ <ul>
135
+ {g.whens.map((w, ndx3) => <li key={ndx3}>
136
+ {/* <p>{w.name}</p> */}
137
+ {(w.error === true ? `❌` : `✅`)} - {w.name}
138
+ </li>)}
139
+ </ul>
140
+ <p>then</p>
141
+ <ul>
142
+ {g.thens.map((t, ndx3) => <li key={ndx3}>
143
+ <p>
144
+ {(t.error === true ? `❌` : `✅`)} - {t.name}
145
+ </p>
146
+ </li>)}
147
+ </ul>
148
+ <pre><code>{JSON.stringify(g.errors, null, 2)}</code></pre>
149
+ </Tab.Pane>)}
150
+ </Tab.Content>
151
+ </Col>
152
+ </Row>
153
+ </Tab.Container>
154
+ </Tab.Pane>)}
155
+ </Tab.Content>
156
+ </Col>
157
+ </Row>
158
+ </Tab.Container>
159
+ </Tab>
160
+ <Tab eventKey="featureTests" title="feature-tests">
161
+ <Tab.Container id="left-tabs-example7" defaultActiveKey="first">
162
+ <Row>
163
+ <Col sm={3}>
164
+ <Nav variant="pills" className="flex-column">
165
+ {Object.keys(data.featureTests).map((ftKey, ndx) => <Nav.Item key={ndx}>
166
+ <Nav.Link eventKey={`featureTests-${ndx}`}>
167
+ {Object.values(data.featureTests[ftKey]).reduce((testMemo, test: any) => test.errors) ? `❌` : `✅`} {ftKey}
168
+ </Nav.Link>
169
+ </Nav.Item>)}
170
+ </Nav>
171
+ </Col>
172
+ <Col sm={9}>
173
+ <Tab.Content>
174
+ {Object.keys(data.featureTests).map((ftKey, ndx) => <Tab.Pane eventKey={`featureTests-${ndx}`} key={ndx}>
175
+ <Tab.Container id="left-tabs-example8" defaultActiveKey={`ft-suite-0`}>
176
+ <Row>
177
+ <Col sm={6}>
178
+ <Nav variant="pills" className="flex-column">
179
+ {data.featureTests[ftKey].map((s, ndx2) => <Nav.Item key={ndx2}>
180
+ <Nav.Link eventKey={`ft-suite-${ndx2}`}>
181
+ {s.errors ? `❌` : `✅`} <strong>{s.testKey}</strong> <em>{s.name}</em>
182
+ </Nav.Link>
183
+ </Nav.Item>)}
184
+ </Nav>
185
+ </Col>
186
+ <Col sm={6}>
187
+ <Tab.Content>
188
+ {data.featureTests[ftKey].map((g, ndx2) => <Tab.Pane key={ndx2} eventKey={`ft-suite-${ndx2}`} >
189
+
190
+ {/* <pre> <code>{JSON.stringify(x)} </code></pre> */}
191
+
192
+ <p>when</p>
193
+ <ul>
194
+ {g.whens.map((w, ndx3) => <li key={ndx3}>
195
+ <p>{w}</p>
196
+ </li>)}
197
+ </ul>
198
+ <p>then</p>
199
+ <ul>
200
+ {g.thens.map((t, ndx3) => <li key={ndx3}>
201
+ <p>{t}</p>
202
+ </li>)}
203
+ </ul>
204
+
205
+ <pre><code>{JSON.stringify(g.errors, null, 2)}</code></pre>
206
+
207
+ </Tab.Pane>)}
208
+
209
+ </Tab.Content>
210
+ </Col>
211
+ </Row>
212
+ </Tab.Container>
213
+
214
+ </Tab.Pane>)}
215
+
216
+ {/* ////////////////////////// */}
217
+
218
+ </Tab.Content>
219
+ </Col>
220
+ </Row>
221
+ </Tab.Container>
222
+ </Tab>
223
+
224
+ <Tab eventKey="networks" title="networks">
225
+ <Tab.Container id="left-tabs-example88" defaultActiveKey={`networks-dags`}>
226
+ <Row>
227
+ <Col sm={3}>
228
+ <Nav variant="pills" className="flex-column">
229
+ <Nav.Link eventKey={`networks-dags`}>
230
+ DAG
231
+ </Nav.Link>
232
+ <Nav.Link eventKey={`networks-directed`}>
233
+ Directed (not acyclic)
234
+ </Nav.Link>
235
+ <Nav.Link eventKey={`networks-undirected`}>
236
+ Undirected
237
+ </Nav.Link>
238
+ </Nav>
239
+ </Col>
240
+ <Col sm={9}>
241
+ <Tab.Content>
242
+ <Tab.Pane eventKey={`networks-dags`} >
243
+ <Tab.Container defaultActiveKey={`networks-dags-0`}>
244
+ <Row>
245
+ <Col sm={3}>
246
+ <Nav variant="pills" className="flex-column">
247
+ {data.summaries.dags.map((g: any, ndx2) => <Nav.Item key={ndx2}>
248
+ <Nav.Link eventKey={`networks-dags-${ndx2}`}>
249
+ {g.name}
250
+ </Nav.Link>
251
+ </Nav.Item>)}
252
+ </Nav>
253
+ </Col>
254
+ <Col sm={9}>
255
+ <Tab.Content>
256
+ <p>idk dags</p>
257
+ </Tab.Content>
258
+ </Col>
259
+ </Row>
260
+ </Tab.Container>
261
+ </Tab.Pane>
262
+ <Tab.Pane eventKey={`networks-directed`} >
263
+ <Tab.Container defaultActiveKey={`networks-directed-0`}>
264
+ <Row>
265
+ <Col sm={3}>
266
+ <Nav variant="pills" className="flex-column">
267
+ {data.summaries.directed.map((g: any, ndx2) => <Nav.Item key={ndx2}>
268
+ <Nav.Link eventKey={`networks-directed-${ndx2}`}>
269
+ {g.name}
270
+ </Nav.Link>
271
+ </Nav.Item>)}
272
+ </Nav>
273
+ </Col>
274
+ <Col sm={9}>
275
+ <Tab.Content>
276
+ <p>idk directed</p>
277
+ </Tab.Content>
278
+ </Col>
279
+ </Row>
280
+ </Tab.Container>
281
+ </Tab.Pane>
282
+ <Tab.Pane eventKey={`networks-undirected`} >
283
+ <Tab.Container defaultActiveKey={`networks-undirected-0`}>
284
+ <Row>
285
+ <Col sm={3}>
286
+ <Nav variant="pills" className="flex-column">
287
+ {data.summaries.undirected.map((g: any, ndx2) => <Nav.Item key={ndx2}>
288
+ <Nav.Link eventKey={`networks-undirected-${ndx2}`}>
289
+ {g.name}
290
+ </Nav.Link>
291
+ </Nav.Item>)}
292
+ </Nav>
293
+ </Col>
294
+ <Col sm={9}>
295
+ <Tab.Content>
296
+ <p>idk undirected</p>
297
+ </Tab.Content>
298
+ </Col>
299
+ </Row>
300
+ </Tab.Container>
301
+ </Tab.Pane>
302
+ </Tab.Content>
303
+ </Col>
304
+ </Row>
305
+ </Tab.Container>
306
+ </Tab>
307
+
308
+ <Tab eventKey="summary" title="summaries">
309
+ <Tab.Container id="left-tabs-example3" defaultActiveKey={`summary-0`}>
310
+ <Row>
311
+ <Col sm={3}>
312
+ <Nav variant="pills" className="flex-column">
313
+ {data.summaries.dags?.map((summary: any, ndx) => <Nav.Item key={ndx}>
314
+ <Nav.Link eventKey={`summary-${ndx}`}>
315
+ {summary.name}
316
+ </Nav.Link>
317
+ </Nav.Item>)}
318
+ </Nav>
319
+ </Col>
320
+ <Col sm={9}>
321
+ <Tab.Content>
322
+ {Object.keys(data.summaries.dags).map((summaryKey, ndx2) => <Tab.Pane key={ndx2} eventKey={`summary-${ndx2}`} >
323
+ <pre><code>{JSON.stringify(data.summaries.dags[summaryKey].dagReduction, null, 2)}</code></pre>
324
+ </Tab.Pane>)}
325
+ </Tab.Content>
326
+ </Col>
327
+ </Row>
328
+ </Tab.Container>
329
+ </Tab>
330
+ </Tabs>
331
+ </>
332
+ }
333
+ {
334
+ !data && <p>LOADING</p>
335
+ }
336
+
337
+ <footer style={{ position: 'fixed', bottom: 0, right: 0 }}>made with ❤️ and <a href="https://adamwong246.github.io/testeranto/" >testeranto</a></footer>
338
+
339
+ </div>)
340
+ }
341
+
342
+ document.addEventListener("DOMContentLoaded", function () {
343
+ const elem = document.getElementById("root");
344
+ if (elem) {
345
+ ReactDom.createRoot(elem).render(React.createElement(Report));
346
+ }
347
+ });
package/src/build.js ADDED
@@ -0,0 +1,88 @@
1
+ // /* eslint-disable no-undef */
2
+ /* eslint-disable @typescript-eslint/no-var-requires */
3
+
4
+ const esbuild = require("esbuild");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const createHash = require("node:crypto").createHash;
8
+
9
+ console.log("hello build.sh", process.cwd(), process.argv);
10
+
11
+ const testerantoConfig = require(process.argv[2]);
12
+
13
+ fs.promises.writeFile("./dist/testeranto.config.json", JSON.stringify(testerantoConfig));
14
+
15
+ esbuild.build({
16
+ entryPoints: [testerantoConfig.features],
17
+ bundle: true,
18
+ minify: false,
19
+ format: "esm",
20
+ target: ["esnext"],
21
+ write: false,
22
+ packages: 'external',
23
+ }).then((res) => {
24
+ const text = res.outputFiles[0].text;
25
+ const hash = createHash('md5').update(text).digest('hex');
26
+ const jsFile = process.cwd() + "/dist/tests/testerantoFeatures.test.js";
27
+ const md5File = process.cwd() + "/dist/tests/testerantoFeatures.test.md5";
28
+
29
+ fs.promises.mkdir(path.dirname(process.cwd() + "./dist/tests/"), { recursive: true }).then(x => {
30
+ console.log("build.js feature", hash, jsFile);
31
+
32
+ fs.promises.writeFile(jsFile, text);
33
+ fs.promises.writeFile(md5File, hash)
34
+ })
35
+ });
36
+
37
+ /////////////////////////////////////////////////////////////////////////////////
38
+
39
+ testerantoConfig.tests.forEach(([key, sourcefile, className]) => {
40
+ esbuild.build({
41
+ entryPoints: [sourcefile],
42
+ bundle: true,
43
+ minify: false,
44
+ format: "esm",
45
+ target: ["esnext"],
46
+ write: false,
47
+ packages: 'external',
48
+ plugins: [{
49
+ name: 'import-path',
50
+ setup(build) {
51
+ build.onResolve({ filter: /^\.{1,2}\// }, args => {
52
+
53
+ const importedPath = args.resolveDir + "/" + args.path;
54
+ const absolutePath = path.resolve(importedPath);
55
+
56
+ const absolutePath2 = path.resolve(testerantoConfig.features).split(".ts").slice(0, -1).join('.ts');
57
+
58
+ if (absolutePath === absolutePath2) {
59
+ return {
60
+ path: process.cwd() + "/dist/tests/testerantoFeatures.test.js",
61
+ external: true
62
+ }
63
+ } else {
64
+ // return {
65
+ // path: path.resolve(importedPath), external: false
66
+ // }
67
+ }
68
+ })
69
+ },
70
+ }],
71
+ external: [
72
+ testerantoConfig.features
73
+ ],
74
+ }).then((res) => {
75
+
76
+ const text = res.outputFiles[0].text;
77
+
78
+ const pp = "./dist/" + (sourcefile.split(process.cwd()).pop()).split(".ts")[0] + '.js';
79
+ const xx = "./dist/" + (sourcefile.split(process.cwd()).pop()).split(".ts")[0] + `.md5`;
80
+
81
+ fs.promises.mkdir(path.dirname(pp), { recursive: true }).then(x => {
82
+ const hash = createHash('md5').update(text).digest('hex');
83
+ console.log("build.js test", key, hash);
84
+ fs.promises.writeFile(pp, text);
85
+ fs.promises.writeFile(xx, hash);
86
+ })
87
+ })
88
+ });
package/src/index.ts ADDED
@@ -0,0 +1,153 @@
1
+ import { BaseGiven, BaseCheck, BaseSuite, BaseFeature, BaseWhen, BaseThen } from "./BaseClasses";
2
+ import { TesterantoLevelOne } from "./lib/level1";
3
+ import { ITestImplementation, ITestSpecification, ITTestResource, ITTestResourceRequirement, ITTestShape, Modify } from "./types"
4
+
5
+ export type { ITestImplementation, ITestSpecification, ITTestShape, Modify };
6
+
7
+ export const Testeranto = <
8
+ TestShape extends ITTestShape,
9
+ Input,
10
+ Subject,
11
+ Store,
12
+ Selection,
13
+ WhenShape,
14
+ ThenShape,
15
+ InitialStateShape
16
+ >(
17
+ input: Input,
18
+ testSpecification: ITestSpecification<TestShape>,
19
+ testImplementation,
20
+ // testImplementation: ITestImplementation<
21
+ // InitialStateShape,
22
+ // Selection,
23
+ // WhenShape,
24
+ // ThenShape,
25
+ // TestShape
26
+ // >,
27
+ testResource: ITTestResourceRequirement,
28
+
29
+ testInterface: {
30
+ actionHandler?: (b: (...any) => any) => any,
31
+ afterEach?: (store: Store, ndx: number, cb) => unknown,
32
+ andWhen: (store: Store, actioner, testResource: ITTestResource) => Promise<Selection>,
33
+ assertioner?: (t: ThenShape) => any,
34
+ beforeAll?: (input: Input) => Promise<Subject>,
35
+ beforeEach?: (subject: Subject, initialValues, testResource: ITTestResource) => Promise<Store>,
36
+ butThen?: (store: Store, callback, testResource: ITTestResource) => Promise<Selection>,
37
+ },
38
+
39
+ ) => {
40
+
41
+ const butThen = testInterface.butThen || (async (a) => a as any);
42
+ const { andWhen } = testInterface;
43
+ const actionHandler = testInterface.actionHandler || function (b: (...any: any[]) => any) {
44
+ return b;
45
+ };
46
+ const assertioner = testInterface.assertioner || (async (t) => t as any);
47
+ const beforeAll = testInterface.beforeAll || (async (input) => input as any);
48
+ const beforeEach = testInterface.beforeEach || async function (subject: Input, initialValues: any, testResource: any) {
49
+ return subject as any;
50
+ }
51
+ const afterEach = testInterface.afterEach || (async (s) => s);
52
+
53
+ return class extends TesterantoLevelOne<
54
+ TestShape,
55
+ InitialStateShape,
56
+ Selection,
57
+ Store,
58
+ Subject,
59
+ WhenShape,
60
+ ThenShape,
61
+ Input
62
+ > {
63
+ constructor() {
64
+ super(
65
+ testImplementation,
66
+ /* @ts-ignore:next-line */
67
+ testSpecification,
68
+ input,
69
+ (class extends BaseSuite<Input, Subject, Store, Selection, ThenShape, TestShape> {
70
+ async setup(s: Input): Promise<Subject> {
71
+ return beforeAll(s);
72
+ }
73
+ test(t: ThenShape): unknown {
74
+ return assertioner(t);
75
+ }
76
+ }),
77
+
78
+ class Given extends BaseGiven<Subject, Store, Selection, ThenShape> {
79
+ initialValues: any;
80
+ constructor(
81
+ name: string,
82
+ features: BaseFeature[],
83
+ whens: BaseWhen<Store, Selection, ThenShape>[],
84
+ thens: BaseThen<Selection, Store, ThenShape>[],
85
+ initialValues: any
86
+ ) {
87
+ super(name, features, whens, thens);
88
+ this.initialValues = initialValues;
89
+ }
90
+ async givenThat(subject, testResource) {
91
+ return beforeEach(subject, this.initialValues, testResource);
92
+ }
93
+ afterEach(store: Store, ndx: number, cb): Promise<unknown> {
94
+ return new Promise((res) => res(afterEach(store, ndx, cb)))
95
+ }
96
+ },
97
+
98
+ class When extends BaseWhen<Store, Selection, WhenShape> {
99
+ payload?: any;
100
+
101
+ constructor(name: string, actioner: (...any) => any, payload?: any) {
102
+ super(name, (store) => {
103
+ return actionHandler(actioner)
104
+ });
105
+ this.payload = payload;
106
+ }
107
+
108
+ async andWhen(store, actioner, testResource) {
109
+ return await andWhen(store, actioner, testResource);
110
+ }
111
+ },
112
+
113
+ class Then extends BaseThen<Selection, Store, ThenShape> {
114
+ constructor(
115
+ name: string,
116
+ callback: (val: Selection) => ThenShape
117
+ ) {
118
+ super(name, callback);
119
+ }
120
+
121
+ async butThen(store: any, testResourceConfiguration?: any): Promise<Selection> {
122
+ return await butThen(store, this.thenCB, testResourceConfiguration)
123
+ }
124
+ },
125
+
126
+ class Check extends BaseCheck<Subject, Store, Selection, ThenShape, TestShape> {
127
+ initialValues: any;
128
+
129
+ constructor(
130
+ name: string,
131
+ features: BaseFeature[],
132
+ checkCallback: (a, b) => any,
133
+ whens,
134
+ thens,
135
+ initialValues: any,
136
+ ) {
137
+ super(name, features, checkCallback, whens, thens);
138
+ this.initialValues = initialValues;
139
+ }
140
+
141
+ async checkThat(subject, testResource) {
142
+ return beforeEach(subject, this.initialValues, testResource);
143
+ }
144
+
145
+ afterEach(store: Store, ndx: number, cb): Promise<unknown> {
146
+ return new Promise((res) => res(afterEach(store, ndx, cb)))
147
+ }
148
+ },
149
+ testResource
150
+ );
151
+ }
152
+ }
153
+ };