harmonyc 0.6.0-0 → 0.6.0-2
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 +4 -25
- package/cli.js +2 -2
- package/compile.js +5 -3
- package/compiler.js +5 -2
- package/js_api/js_api.js +30 -8
- package/languages/JavaScript.js +16 -4
- package/package.json +7 -1
- package/test.d.ts +7 -0
- package/js_api/js_api.steps.js +0 -11
package/README.md
CHANGED
|
@@ -2,39 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
A test design & BDD tool that helps you separate the _what_ to test from the _how_ to automate it. You write test cases in a simple Markdown format, and then automate them with Vitest (and soon with many more frameworks and languages).
|
|
4
4
|
|
|
5
|
-
(**Note**: There have been big changes since v0.2.)
|
|
6
|
-
|
|
7
5
|
## Usage
|
|
8
6
|
|
|
9
7
|
- ### watch and run mode
|
|
10
8
|
|
|
11
|
-
- You can compile and run your `*.md` files in the `src` folder, and watch it, by running
|
|
12
|
-
|
|
13
|
-
```bash script
|
|
14
|
-
npx harmonyc --run --watch src/**/*.md
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
- ### compiling and running
|
|
18
|
-
|
|
19
|
-
- You can compile and run your `*.md` files in the `src` folder by running
|
|
9
|
+
- You can compile and run your `*.spec.md` files in the `src` folder, and watch it, by running
|
|
20
10
|
|
|
21
11
|
```bash script
|
|
22
|
-
npx harmonyc --run src/**/*.md
|
|
12
|
+
npx harmonyc --run --watch 'src/**/*.spec.md'
|
|
23
13
|
```
|
|
24
14
|
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
1. You can compile your `*.md` files in the `src` folder by running
|
|
28
|
-
|
|
29
|
-
```bash script
|
|
30
|
-
npx harmonyc src/**/*.md
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
2. Then you can run the generated tests with Vitest by running
|
|
34
|
-
|
|
35
|
-
```bash script
|
|
36
|
-
npx vitest
|
|
37
|
-
```
|
|
15
|
+
- => this will generate tests into `*.spec.mjs` files
|
|
16
|
+
- => this will create a stub `*.steps.ts` file if it doesn't exist
|
|
38
17
|
|
|
39
18
|
## Syntax
|
|
40
19
|
|
package/cli.js
CHANGED
|
@@ -6,8 +6,8 @@ import { run, runWatch } from './run.js';
|
|
|
6
6
|
const args = parseArgs({
|
|
7
7
|
options: {
|
|
8
8
|
help: { type: 'boolean' },
|
|
9
|
-
watch: { type: 'boolean' },
|
|
10
|
-
run: { type: 'boolean' },
|
|
9
|
+
watch: { type: 'boolean', short: 'w' },
|
|
10
|
+
run: { type: 'boolean', short: 'r' },
|
|
11
11
|
},
|
|
12
12
|
allowPositionals: true,
|
|
13
13
|
});
|
package/compile.js
CHANGED
|
@@ -4,8 +4,10 @@ import { parse } from './syntax.js';
|
|
|
4
4
|
export function compileFeature(fileName, src) {
|
|
5
5
|
const feature = parse({ fileName, src });
|
|
6
6
|
const outFn = `${fileName.replace(/\.[a-z]+$/i, '')}.mjs`;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const outFile = new OutFile(outFn);
|
|
8
|
+
const stepsFn = outFn.replace(/(\.(spec|test)s?)?\.[a-z]+$/i, '.steps.ts');
|
|
9
|
+
const stepsFile = new OutFile(stepsFn);
|
|
10
|
+
const cg = new NodeTest(outFile, stepsFile);
|
|
9
11
|
feature.toCode(cg);
|
|
10
|
-
return
|
|
12
|
+
return { outFile, stepsFile };
|
|
11
13
|
}
|
package/compiler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import glob from 'fast-glob';
|
|
2
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
3
|
import { compileFeature } from './compile.js';
|
|
4
4
|
export async function compileFiles(pattern) {
|
|
5
5
|
const fns = await glob(pattern);
|
|
@@ -11,7 +11,10 @@ export async function compileFiles(pattern) {
|
|
|
11
11
|
}
|
|
12
12
|
export async function compileFile(fn) {
|
|
13
13
|
const src = readFileSync(fn, 'utf8').toString();
|
|
14
|
-
const outFile = compileFeature(fn, src);
|
|
14
|
+
const { outFile, stepsFile } = compileFeature(fn, src);
|
|
15
15
|
writeFileSync(outFile.name, outFile.value);
|
|
16
|
+
if (!existsSync(stepsFile.name)) {
|
|
17
|
+
writeFileSync(stepsFile.name, stepsFile.value);
|
|
18
|
+
}
|
|
16
19
|
return outFile.name;
|
|
17
20
|
}
|
package/js_api/js_api.js
CHANGED
|
@@ -3,7 +3,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
3
3
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
4
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
5
|
};
|
|
6
|
-
var
|
|
6
|
+
var _FeatureContext_actions, _FeatureContext_responses, _FeatureContext_parameterTypeRegistry;
|
|
7
7
|
import { CucumberExpression, ParameterTypeRegistry, } from '@cucumber/cucumber-expressions';
|
|
8
8
|
class Definition {
|
|
9
9
|
constructor(expr, fn) {
|
|
@@ -14,34 +14,56 @@ class Definition {
|
|
|
14
14
|
const features = new Map();
|
|
15
15
|
class FeatureContext {
|
|
16
16
|
constructor() {
|
|
17
|
-
|
|
17
|
+
_FeatureContext_actions.set(this, []);
|
|
18
|
+
_FeatureContext_responses.set(this, []);
|
|
18
19
|
_FeatureContext_parameterTypeRegistry.set(this, new ParameterTypeRegistry());
|
|
19
20
|
this.Action = ((s, fn) => {
|
|
20
21
|
if (fn) {
|
|
21
22
|
const expr = new CucumberExpression(s, __classPrivateFieldGet(this, _FeatureContext_parameterTypeRegistry, "f"));
|
|
22
23
|
const def = new Definition(expr, fn);
|
|
23
|
-
__classPrivateFieldGet(this,
|
|
24
|
+
__classPrivateFieldGet(this, _FeatureContext_actions, "f").push(def);
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
27
|
// call the action
|
|
27
|
-
const matches = __classPrivateFieldGet(this,
|
|
28
|
+
const matches = __classPrivateFieldGet(this, _FeatureContext_actions, "f").map((def) => def.expr.match(s));
|
|
28
29
|
const matching = [...matches.keys()].filter((i) => matches[i]);
|
|
29
30
|
if (matching.length === 0) {
|
|
30
31
|
throw new Error(`Not defined: ${s}`);
|
|
31
32
|
}
|
|
32
33
|
if (matching.length > 1) {
|
|
33
34
|
throw new Error(`Ambiguous: ${s}\n${matching
|
|
34
|
-
.map((i) => __classPrivateFieldGet(this,
|
|
35
|
+
.map((i) => __classPrivateFieldGet(this, _FeatureContext_actions, "f")[i].expr.source)
|
|
35
36
|
.join('\n')}`);
|
|
36
37
|
}
|
|
37
38
|
const match = matches[matching[0]];
|
|
38
|
-
const def = __classPrivateFieldGet(this,
|
|
39
|
+
const def = __classPrivateFieldGet(this, _FeatureContext_actions, "f")[matching[0]];
|
|
40
|
+
return Promise.resolve(def.fn(...match.map((m) => m.getValue(undefined))));
|
|
41
|
+
});
|
|
42
|
+
this.Response = ((s, fn) => {
|
|
43
|
+
if (fn) {
|
|
44
|
+
const expr = new CucumberExpression(s, __classPrivateFieldGet(this, _FeatureContext_parameterTypeRegistry, "f"));
|
|
45
|
+
const def = new Definition(expr, fn);
|
|
46
|
+
__classPrivateFieldGet(this, _FeatureContext_responses, "f").push(def);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// call the action
|
|
50
|
+
const matches = __classPrivateFieldGet(this, _FeatureContext_responses, "f").map((def) => def.expr.match(s));
|
|
51
|
+
const matching = [...matches.keys()].filter((i) => matches[i]);
|
|
52
|
+
if (matching.length === 0) {
|
|
53
|
+
throw new Error(`Not defined: ${s}`);
|
|
54
|
+
}
|
|
55
|
+
if (matching.length > 1) {
|
|
56
|
+
throw new Error(`Ambiguous: ${s}\n${matching
|
|
57
|
+
.map((i) => __classPrivateFieldGet(this, _FeatureContext_responses, "f")[i].expr.source)
|
|
58
|
+
.join('\n')}`);
|
|
59
|
+
}
|
|
60
|
+
const match = matches[matching[0]];
|
|
61
|
+
const def = __classPrivateFieldGet(this, _FeatureContext_responses, "f")[matching[0]];
|
|
39
62
|
return Promise.resolve(def.fn(...match.map((m) => m.getValue(undefined))));
|
|
40
63
|
});
|
|
41
|
-
this.Response = this.Action;
|
|
42
64
|
}
|
|
43
65
|
}
|
|
44
|
-
|
|
66
|
+
_FeatureContext_actions = new WeakMap(), _FeatureContext_responses = new WeakMap(), _FeatureContext_parameterTypeRegistry = new WeakMap();
|
|
45
67
|
export function Feature(s, fn) {
|
|
46
68
|
let ctx;
|
|
47
69
|
if (!fn) {
|
package/languages/JavaScript.js
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
import { basename } from 'path';
|
|
2
2
|
export class NodeTest {
|
|
3
|
-
constructor(of) {
|
|
3
|
+
constructor(of, sf) {
|
|
4
4
|
this.of = of;
|
|
5
|
+
this.sf = sf;
|
|
5
6
|
this.framework = 'vitest';
|
|
6
7
|
this.phrases = [];
|
|
7
8
|
}
|
|
8
9
|
feature(feature) {
|
|
9
|
-
const stepsModule = './' +
|
|
10
|
-
basename(this.of.name.replace(/(\.(spec|test)s?)?\.[a-z]+$/i, '.steps'));
|
|
10
|
+
const stepsModule = './' + basename(this.sf.name.replace(/.(js|ts)$/, ''));
|
|
11
11
|
this.phrases = [];
|
|
12
12
|
if (this.framework === 'vitest') {
|
|
13
13
|
this.of.print(`import { test, expect } from 'vitest';`);
|
|
14
14
|
this.of.print(`import { Feature } from 'harmonyc/test';`);
|
|
15
15
|
this.of.print(`import ${JSON.stringify(stepsModule)};`);
|
|
16
16
|
}
|
|
17
|
-
this.of.print(feature.prelude);
|
|
18
17
|
for (const test of feature.tests) {
|
|
19
18
|
test.toCode(this);
|
|
20
19
|
}
|
|
20
|
+
this.sf.print(`import { Feature } from 'harmonyc/test';`);
|
|
21
|
+
this.sf.print('');
|
|
22
|
+
this.sf.print(`Feature(${JSON.stringify(feature.name)}, ({ Action, Response }) => {`);
|
|
23
|
+
this.sf.indent(() => {
|
|
24
|
+
for (const phrase of this.phrases) {
|
|
25
|
+
this.sf.print(`${capitalize(phrase.kind)}(${JSON.stringify(phrase.text)}, () => {`);
|
|
26
|
+
this.sf.indent(() => {
|
|
27
|
+
this.sf.print(`throw new Error(${JSON.stringify(`Pending: ${phrase.text}`)})`);
|
|
28
|
+
});
|
|
29
|
+
this.sf.print('})');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
this.sf.print('})');
|
|
21
33
|
}
|
|
22
34
|
test(t) {
|
|
23
35
|
this.of.print(`test('${t.name}', async (context) => {`);
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harmonyc",
|
|
3
3
|
"description": "Harmony Code - model-driven BDD for Vitest",
|
|
4
|
-
"version": "0.6.0-
|
|
4
|
+
"version": "0.6.0-2",
|
|
5
5
|
"author": "Bernát Kalló",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"harmonyc": "./cli.js"
|
|
9
9
|
},
|
|
10
|
+
"exports": {
|
|
11
|
+
"./test": {
|
|
12
|
+
"types": "./test.d.ts",
|
|
13
|
+
"default": "./dist/js_api/js_api.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
10
16
|
"homepage": "https://github.com/harmony-ac/code#readme",
|
|
11
17
|
"repository": {
|
|
12
18
|
"type": "git",
|
package/test.d.ts
ADDED
package/js_api/js_api.steps.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { expect } from 'vitest';
|
|
2
|
-
import { Feature } from 'harmonyc/test';
|
|
3
|
-
Feature('js api', ({ Action, Response }) => {
|
|
4
|
-
let n = 0;
|
|
5
|
-
Action('add {int}', async function (k) {
|
|
6
|
-
n += k;
|
|
7
|
-
});
|
|
8
|
-
Response('result is {float}', async function (k) {
|
|
9
|
-
expect(n).toBe(k);
|
|
10
|
-
});
|
|
11
|
-
});
|