harmonyc 0.4.3 → 0.6.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/README.md CHANGED
@@ -6,18 +6,35 @@ A test design & BDD tool that helps you separate the _what_ to test from the _ho
6
6
 
7
7
  ## Usage
8
8
 
9
- 1. Have Node.js installed.
10
- 2. You can compile your `*.md` files in the `src` folder by running
9
+ - ### watch and run mode
11
10
 
12
- ```bash script
13
- npx harmonyc src/**/*.md
14
- ```
11
+ - You can compile and run your `*.md` files in the `src` folder, and watch it, by running
15
12
 
16
- 3. Then you can run the generated tests with Vitest by running
13
+ ```bash script
14
+ npx harmonyc --run --watch src/**/*.md
15
+ ```
17
16
 
18
- ```bash script
19
- npx vitest
20
- ```
17
+ - ### compiling and running
18
+
19
+ - You can compile and run your `*.md` files in the `src` folder by running
20
+
21
+ ```bash script
22
+ npx harmonyc --run src/**/*.md
23
+ ```
24
+
25
+ - ### compiling and running separately
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
+ ```
21
38
 
22
39
  ## Syntax
23
40
 
package/cli.js CHANGED
@@ -2,10 +2,12 @@
2
2
  import { compileFiles } from './compiler.js';
3
3
  import { parseArgs } from 'node:util';
4
4
  import { watchFiles } from './watch.js';
5
+ import { run, runWatch } from './run.js';
5
6
  const args = parseArgs({
6
7
  options: {
7
8
  help: { type: 'boolean' },
8
9
  watch: { type: 'boolean' },
10
+ run: { type: 'boolean' },
9
11
  },
10
12
  allowPositionals: true,
11
13
  });
@@ -13,9 +15,18 @@ if (args.positionals.length === 0 || args.values.help) {
13
15
  console.error('Usage: harmonyc <input files glob pattern>');
14
16
  process.exit(1);
15
17
  }
16
- if (args.values.watch) {
17
- void watchFiles(args.positionals);
18
- }
19
- else {
20
- void compileFiles(args.positionals);
21
- }
18
+ ;
19
+ (async () => {
20
+ if (args.values.watch) {
21
+ const outFns = await watchFiles(args.positionals);
22
+ if (args.values.run) {
23
+ runWatch(outFns);
24
+ }
25
+ }
26
+ else {
27
+ const { outFns } = await compileFiles(args.positionals);
28
+ if (args.values.run) {
29
+ run(outFns);
30
+ }
31
+ }
32
+ })();
package/compile.js CHANGED
@@ -3,8 +3,9 @@ import { OutFile } from './outFile.js';
3
3
  import { parse } from './syntax.js';
4
4
  export function compileFeature(fileName, src) {
5
5
  const feature = parse({ fileName, src });
6
- const of = new OutFile();
6
+ const outFn = `${fileName.replace(/\.[a-z]+$/i, '')}.mjs`;
7
+ const of = new OutFile(outFn);
7
8
  const cg = new NodeTest(of);
8
9
  feature.toCode(cg);
9
- return of.value;
10
+ return of;
10
11
  }
package/compiler.js CHANGED
@@ -1,18 +1,17 @@
1
1
  import glob from 'fast-glob';
2
2
  import { readFileSync, writeFileSync } from 'fs';
3
- import { basename } from 'path';
4
3
  import { compileFeature } from './compile.js';
5
4
  export async function compileFiles(pattern) {
6
5
  const fns = await glob(pattern);
7
6
  if (!fns.length)
8
7
  throw new Error(`No files found for pattern: ${String(pattern)}`);
9
- await Promise.all(fns.map((fn) => compileFile(fn)));
8
+ const outFns = await Promise.all(fns.map((fn) => compileFile(fn)));
10
9
  console.log(`Compiled ${fns.length} file${fns.length === 1 ? '' : 's'}.`);
11
- return fns;
10
+ return { fns, outFns };
12
11
  }
13
12
  export async function compileFile(fn) {
14
13
  const src = readFileSync(fn, 'utf8').toString();
15
- const name = basename(fn).replace(/\.[a-z]+$/i, '');
16
- const outFn = `${fn.replace(/\.[a-z]+$/i, '')}.mjs`;
17
- writeFileSync(outFn, compileFeature(fn, src));
14
+ const outFile = compileFeature(fn, src);
15
+ writeFileSync(outFile.name, outFile.value);
16
+ return outFile.name;
18
17
  }
@@ -0,0 +1,59 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
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
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _FeatureContext_defs, _FeatureContext_parameterTypeRegistry;
7
+ import { CucumberExpression, ParameterTypeRegistry, } from '@cucumber/cucumber-expressions';
8
+ class Definition {
9
+ constructor(expr, fn) {
10
+ this.expr = expr;
11
+ this.fn = fn;
12
+ }
13
+ }
14
+ const features = new Map();
15
+ class FeatureContext {
16
+ constructor() {
17
+ _FeatureContext_defs.set(this, []);
18
+ _FeatureContext_parameterTypeRegistry.set(this, new ParameterTypeRegistry());
19
+ this.Action = ((s, fn) => {
20
+ if (fn) {
21
+ const expr = new CucumberExpression(s, __classPrivateFieldGet(this, _FeatureContext_parameterTypeRegistry, "f"));
22
+ const def = new Definition(expr, fn);
23
+ __classPrivateFieldGet(this, _FeatureContext_defs, "f").push(def);
24
+ return;
25
+ }
26
+ // call the action
27
+ const matches = __classPrivateFieldGet(this, _FeatureContext_defs, "f").map((def) => def.expr.match(s));
28
+ const matching = [...matches.keys()].filter((i) => matches[i]);
29
+ if (matching.length === 0) {
30
+ throw new Error(`Not defined: ${s}`);
31
+ }
32
+ if (matching.length > 1) {
33
+ throw new Error(`Ambiguous: ${s}\n${matching
34
+ .map((i) => __classPrivateFieldGet(this, _FeatureContext_defs, "f")[i].expr.source)
35
+ .join('\n')}`);
36
+ }
37
+ const match = matches[matching[0]];
38
+ const def = __classPrivateFieldGet(this, _FeatureContext_defs, "f")[matching[0]];
39
+ return Promise.resolve(def.fn(...match.map((m) => m.getValue(undefined))));
40
+ });
41
+ this.Response = this.Action;
42
+ }
43
+ }
44
+ _FeatureContext_defs = new WeakMap(), _FeatureContext_parameterTypeRegistry = new WeakMap();
45
+ export function Feature(s, fn) {
46
+ let ctx;
47
+ if (!fn) {
48
+ ctx = features.get(s);
49
+ if (!ctx)
50
+ throw new Error(`Feature not found: ${s}`);
51
+ }
52
+ else {
53
+ // redefine the feature
54
+ ctx = new FeatureContext();
55
+ features.set(s, ctx);
56
+ }
57
+ fn === null || fn === void 0 ? void 0 : fn(ctx);
58
+ return ctx;
59
+ }
@@ -0,0 +1,11 @@
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
+ });
@@ -1,31 +1,41 @@
1
+ import { basename } from 'path';
1
2
  export class NodeTest {
2
- constructor(outFile) {
3
- this.outFile = outFile;
3
+ constructor(of) {
4
+ this.of = of;
4
5
  this.framework = 'vitest';
6
+ this.phrases = [];
5
7
  }
6
8
  feature(feature) {
9
+ const stepsModule = './' +
10
+ basename(this.of.name.replace(/(\.(spec|test)s?)?\.[a-z]+$/i, '.steps'));
11
+ this.phrases = [];
7
12
  if (this.framework === 'vitest') {
8
- this.outFile.print(`import { test, expect } from 'vitest';`);
13
+ this.of.print(`import { test, expect } from 'vitest';`);
14
+ this.of.print(`import { Feature } from 'harmonyc/test';`);
15
+ this.of.print(`import ${JSON.stringify(stepsModule)};`);
9
16
  }
10
- this.outFile.print(feature.prelude);
17
+ this.of.print(feature.prelude);
11
18
  for (const test of feature.tests) {
12
19
  test.toCode(this);
13
20
  }
14
21
  }
15
22
  test(t) {
16
- this.outFile.print(`test('${t.name}', async () => {`);
17
- this.outFile.indent(() => {
23
+ this.of.print(`test('${t.name}', async (context) => {`);
24
+ this.of.indent(() => {
18
25
  for (const step of t.steps) {
19
26
  step.toCode(this);
20
27
  }
21
28
  });
22
- this.outFile.print('})');
23
- this.outFile.print('');
29
+ this.of.print('})');
30
+ this.of.print('');
24
31
  }
25
32
  phrase(p) {
26
- var _a;
27
- this.outFile.loc(p).print('/// ' + p.text);
28
- const code = (_a = p.definition()) !== null && _a !== void 0 ? _a : `throw 'Not defined: ' + ${JSON.stringify(p.text)};`;
29
- this.outFile.print(...code.split('\n'));
33
+ if (!this.phrases.some((x) => x.text === p.text))
34
+ this.phrases.push(p);
35
+ const feature = p.feature.name;
36
+ this.of.print(`await Feature(${JSON.stringify(feature)}).${capitalize(p.kind)}(${JSON.stringify(p.text)})`);
30
37
  }
31
38
  }
39
+ function capitalize(s) {
40
+ return s.charAt(0).toUpperCase() + s.slice(1);
41
+ }
package/outFile.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { SourceMapGenerator } from 'source-map-js';
2
2
  export class OutFile {
3
- constructor(indentSpaces = 2) {
4
- this.indentSpaces = indentSpaces;
3
+ constructor(name) {
4
+ this.name = name;
5
5
  this.lines = [];
6
6
  this.level = 0;
7
7
  this.sm = new SourceMapGenerator();
8
+ this.indentSpaces = 2;
8
9
  }
9
10
  indent(fn) {
10
11
  this.level++;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harmonyc",
3
3
  "description": "Harmony Code - model-driven BDD for Vitest",
4
- "version": "0.4.3",
4
+ "version": "0.6.0-0",
5
5
  "author": "Bernát Kalló",
6
6
  "type": "module",
7
7
  "bin": {
package/run.js ADDED
@@ -0,0 +1,24 @@
1
+ import { exec } from 'child_process';
2
+ function runCommand(patterns) {
3
+ return `npx vitest run ${args(patterns)}`;
4
+ }
5
+ function runWatchCommand(patterns) {
6
+ return `npx vitest ${args(patterns)}`;
7
+ }
8
+ function args(patterns) {
9
+ return patterns.map((s) => JSON.stringify(s)).join(' ');
10
+ }
11
+ export function run(patterns) {
12
+ var _a, _b;
13
+ const cmd = runCommand(patterns);
14
+ const p = exec(cmd, { cwd: process.cwd() });
15
+ (_a = p.stdout) === null || _a === void 0 ? void 0 : _a.pipe(process.stdout);
16
+ (_b = p.stderr) === null || _b === void 0 ? void 0 : _b.pipe(process.stderr);
17
+ }
18
+ export function runWatch(patterns) {
19
+ var _a, _b;
20
+ const cmd = runWatchCommand(patterns);
21
+ const p = exec(cmd, { cwd: process.cwd() });
22
+ (_a = p.stdout) === null || _a === void 0 ? void 0 : _a.pipe(process.stdout);
23
+ (_b = p.stderr) === null || _b === void 0 ? void 0 : _b.pipe(process.stderr);
24
+ }
package/watch.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { watch } from 'node:fs';
2
2
  import { compileFile, compileFiles } from './compiler.js';
3
3
  export async function watchFiles(patterns) {
4
- const fns = await compileFiles(patterns);
4
+ const { fns, outFns } = await compileFiles(patterns);
5
5
  for (const file of fns) {
6
6
  watch(file, () => {
7
7
  compileFile(file);
8
8
  });
9
9
  }
10
+ return outFns;
10
11
  }