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 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
- - ### 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
- ```
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 of = new OutFile(outFn);
8
- const cg = new NodeTest(of);
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 of;
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 _FeatureContext_defs, _FeatureContext_parameterTypeRegistry;
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
- _FeatureContext_defs.set(this, []);
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, _FeatureContext_defs, "f").push(def);
24
+ __classPrivateFieldGet(this, _FeatureContext_actions, "f").push(def);
24
25
  return;
25
26
  }
26
27
  // call the action
27
- const matches = __classPrivateFieldGet(this, _FeatureContext_defs, "f").map((def) => def.expr.match(s));
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, _FeatureContext_defs, "f")[i].expr.source)
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, _FeatureContext_defs, "f")[matching[0]];
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
- _FeatureContext_defs = new WeakMap(), _FeatureContext_parameterTypeRegistry = new WeakMap();
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) {
@@ -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-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
@@ -0,0 +1,7 @@
1
+ type FeatureContext = {
2
+ Action(s: string): Promise<void>
3
+ Action(s: string, fn: Function): void
4
+ Response(s: string): Promise<void>
5
+ Response(s: string, fn: Function): void
6
+ }
7
+ export function Feature(name: string, fn: (ctx: FeatureContext) => void): void
@@ -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
- });