harmonyc 0.7.2 → 0.8.1

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
@@ -56,11 +56,11 @@ The lines of a file are nodes of a tree. The tree is specified with the indentat
56
56
 
57
57
  `+` means a fork: the node directly follows its parent node. All siblings with `+` are separate branches, they will generate separate scenarios.
58
58
 
59
- ### Actions and responses (phrases)
59
+ ### Phrases (actions and responses)
60
60
 
61
- After the mark, every node can contain an action and zero or more responses. The action is the text before the `=>`, and the responses are the text after the `=>`.
61
+ After the mark, every node can contain an **action** and zero or more **responses**, together called **phrases**. The action is the text before the `=>`, and the responses are the text after the `=>`.
62
62
 
63
- Both actions and responses get compiled to simple function calls - in JavaScript, awaited function calls. The return value of the action is passed to the responses of the same step as the last argument.
63
+ Both actions and responses get compiled to simple function calls - in JavaScript, awaited function calls. Actions will become `When_*` functions, and responses will become `Then_*` functions. The return value of the action is passed to the responses of the same step as the last argument.
64
64
 
65
65
  ### Arguments
66
66
 
@@ -75,11 +75,6 @@ They are not included in the test case, but the test case name is generated from
75
75
 
76
76
  Lines starting with `#` or `//` are comments and are ignored.
77
77
 
78
- ### Steps
79
-
80
- All other lines are steps. A step consists of an action, and one or more responses denoted by `=>`.
81
- Actions will become `When` steps, and responses will become `Then` steps.
82
-
83
78
  ### Error matching
84
79
 
85
80
  You can use `!!` to denote an error response. This will verify that the action throws an error. You can specify the error message after the `!!`.
@@ -11,18 +11,18 @@ export class NodeTest {
11
11
  this.extraArgs = [];
12
12
  }
13
13
  feature(feature) {
14
- const stepsModule = './' + basename(this.sf.name.replace(/.(js|ts)$/, ''));
14
+ const phrasesModule = './' + basename(this.sf.name.replace(/.(js|ts)$/, ''));
15
15
  const fn = (this.currentFeatureName = pascalCase(feature.name));
16
16
  this.phraseFns = new Map();
17
17
  if (this.framework === 'vitest') {
18
18
  this.tf.print(`import { describe, test, expect } from 'vitest';`);
19
19
  }
20
- this.tf.print(`import ${fn}Steps from ${str(stepsModule)};`);
20
+ this.tf.print(`import ${fn}Phrases from ${str(phrasesModule)};`);
21
21
  this.tf.print(``);
22
22
  for (const item of feature.testGroups) {
23
23
  item.toCode(this);
24
24
  }
25
- this.sf.print(`export default class ${pascalCase(feature.name)}Steps {`);
25
+ this.sf.print(`export default class ${pascalCase(feature.name)}Phrases {`);
26
26
  this.sf.indent(() => {
27
27
  for (const ph of this.phraseFns.keys()) {
28
28
  const p = this.phraseFns.get(ph);
@@ -60,6 +60,7 @@ export class NodeTest {
60
60
  }
61
61
  errorStep(action, errorMessage) {
62
62
  var _a;
63
+ this.declareFeatureVariables([action]);
63
64
  this.tf.print(`expect(async () => {`);
64
65
  this.tf.indent(() => {
65
66
  action.toCode(this);
@@ -67,14 +68,7 @@ export class NodeTest {
67
68
  this.tf.print(`}).rejects.toThrow(${(_a = errorMessage === null || errorMessage === void 0 ? void 0 : errorMessage.toCode(this)) !== null && _a !== void 0 ? _a : ''});`);
68
69
  }
69
70
  step(action, responses) {
70
- for (const p of [action, ...responses]) {
71
- const feature = p.feature.name;
72
- let f = this.featureVars.get(feature);
73
- if (!f) {
74
- f = toId(feature, abbrev, this.featureVars);
75
- this.tf.print(`const ${f} = new ${pascalCase(feature)}Steps();`);
76
- }
77
- }
71
+ this.declareFeatureVariables([action, ...responses]);
78
72
  if (responses.length === 0) {
79
73
  action.toCode(this);
80
74
  return;
@@ -94,6 +88,16 @@ export class NodeTest {
94
88
  }
95
89
  });
96
90
  }
91
+ declareFeatureVariables(phrases) {
92
+ for (const p of phrases) {
93
+ const feature = p.feature.name;
94
+ let f = this.featureVars.get(feature);
95
+ if (!f) {
96
+ f = toId(feature, abbrev, this.featureVars);
97
+ this.tf.print(`const ${f} = new ${pascalCase(feature)}Phrases();`);
98
+ }
99
+ }
100
+ }
97
101
  phrase(p) {
98
102
  const phrasefn = this.functionName(p);
99
103
  if (!this.phraseFns.has(phrasefn))
@@ -1,7 +1,7 @@
1
1
  import { NodeTest } from "../code_generator/JavaScript.js";
2
2
  import { OutFile } from "../code_generator/outFile.js";
3
3
  import { parse } from "../parser/parser.js";
4
- import { base, stepsFileName, testFileName } from "../filenames/filenames.js";
4
+ import { base, phrasesFileName, testFileName } from "../filenames/filenames.js";
5
5
  import { Feature } from "../model/model.js";
6
6
  import { basename } from 'node:path';
7
7
  import { autoLabel } from "../optimizations/autoLabel/autoLabel.js";
@@ -24,9 +24,9 @@ export function compileFeature(fileName, src) {
24
24
  autoLabel(feature.root);
25
25
  const testFn = testFileName(fileName);
26
26
  const testFile = new OutFile(testFn);
27
- const stepsFn = stepsFileName(fileName);
28
- const stepsFile = new OutFile(stepsFn);
29
- const cg = new NodeTest(testFile, stepsFile);
27
+ const phrasesFn = phrasesFileName(fileName);
28
+ const phrasesFile = new OutFile(phrasesFn);
29
+ const cg = new NodeTest(testFile, phrasesFile);
30
30
  feature.toCode(cg);
31
- return { outFile: testFile, stepsFile };
31
+ return { outFile: testFile, phrasesFile };
32
32
  }
@@ -14,9 +14,9 @@ export async function compileFiles(pattern) {
14
14
  console.log((_a = error.message) !== null && _a !== void 0 ? _a : error);
15
15
  }
16
16
  console.log(`Compiled ${fns.length} file${fns.length === 1 ? '' : 's'}.`);
17
- const generated = features.filter((f) => f.stepsFileAction === 'generated');
17
+ const generated = features.filter((f) => f.phrasesFileAction === 'generated');
18
18
  if (generated.length) {
19
- console.log(`Generated ${generated.length} steps file${generated.length === 1 ? '' : 's'}.`);
19
+ console.log(`Generated ${generated.length} phrases file${generated.length === 1 ? '' : 's'}.`);
20
20
  }
21
21
  return { fns, outFns: features.map((f) => f.outFile.name) };
22
22
  }
@@ -26,12 +26,12 @@ export async function compileFile(fn) {
26
26
  .toString()
27
27
  .replace(/\r\n/g, '\n')
28
28
  .replace(/\r/g, '\n');
29
- const { outFile, stepsFile } = compileFeature(fn, src);
29
+ const { outFile, phrasesFile } = compileFeature(fn, src);
30
30
  writeFileSync(outFile.name, outFile.value);
31
- let stepsFileAction = 'ignored';
32
- if (!existsSync(stepsFile.name)) {
33
- stepsFileAction = 'generated';
34
- writeFileSync(stepsFile.name, stepsFile.value);
31
+ let phrasesFileAction = 'ignored';
32
+ if (!existsSync(phrasesFile.name)) {
33
+ phrasesFileAction = 'generated';
34
+ writeFileSync(phrasesFile.name, phrasesFile.value);
35
35
  }
36
- return { stepsFileAction, outFile, stepsFile };
36
+ return { phrasesFileAction, outFile, phrasesFile };
37
37
  }
@@ -6,12 +6,12 @@ export function base(fn) {
6
6
  export function testFileName(fn) {
7
7
  return base(fn) + '.test.mjs';
8
8
  }
9
- export function stepsFileName(fn) {
9
+ export function phrasesFileName(fn) {
10
10
  const baseFn = base(fn);
11
11
  const pattern = convertPathToPattern(baseFn);
12
- const existing = globSync(`${pattern}.steps.{tsx,jsx,ts,js}`);
12
+ const existing = globSync(`${pattern}.phrases.{tsx,jsx,ts,js}`);
13
13
  if (existing.length) {
14
14
  return existing.sort().at(-1);
15
15
  }
16
- return `${baseFn}.steps.ts`;
16
+ return `${baseFn}.phrases.ts`;
17
17
  }
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.7.2",
4
+ "version": "0.8.1",
5
5
  "author": "Bernát Kalló",
6
6
  "type": "module",
7
7
  "bin": {
@@ -0,0 +1,9 @@
1
+ export default class LexerSteps {
2
+ async When__(x) {
3
+ throw new Error("Pending: When__");
4
+ }
5
+ async Then__(x) {
6
+ throw new Error("Pending: Then__");
7
+ }
8
+ }
9
+ ;
@@ -34,7 +34,7 @@ const rules = [
34
34
  [true, /^:(?=\s*(?:\n|$))/g, T.Colon],
35
35
  [
36
36
  true,
37
- /^(?!\s|=>|!!|- |\+ |[\[\]"`|]).+?(?=[\[\]"`|]|\n|$|=>|!!|:\s*(?:\n|$)|$)/g,
37
+ /^(?!\s|=>|!!|- |\+ |[\[\]"`|]|:\s*(?:\n|$)).+?(?=[\[\]"`|]|\n|$|=>|!!|:\s*(?:\n|$)|$)/g,
38
38
  T.Words,
39
39
  ],
40
40
  [true, /^-/g, T.Minus],
package/parser/parser.js CHANGED
@@ -25,7 +25,7 @@ export const RESPONSE = apply(PHRASE, ([parts, docstring]) => {
25
25
  export const ARROW = kmid(seq(opt_sc(NEWLINES), SPACES), tok(T.ResponseArrow), SPACES);
26
26
  export const RESPONSE_ITEM = kright(ARROW, RESPONSE);
27
27
  export const STEP = apply(seq(ACTION, rep_sc(RESPONSE_ITEM)), ([action, responses]) => new Step(action, responses).setFork(true));
28
- export const LABEL = apply(kleft(list_sc(PART, SPACES), tok(T.Colon)), (words) => new Label(words.map((w) => w.toString()).join(' ')));
28
+ export const LABEL = apply(kleft(list_sc(PART, SPACES), seq(tok(T.Colon), SPACES)), (words) => new Label(words.map((w) => w.toString()).join(' ')));
29
29
  export const SECTION = apply(LABEL, (text) => new Section(text));
30
30
  export const BRANCH = alt_sc(SECTION, STEP); // section first, to make sure there is no colon after step
31
31
  export const DENTS = apply(opt_sc(seq(SPACES, alt_sc(tok(T.Plus), tok(T.Minus)), tok(T.Space))), (lineHead) => {
@@ -0,0 +1,12 @@
1
+ export default class ParserSteps {
2
+ async When_production__(x) {
3
+ throw new Error("Pending: When_production__");
4
+ }
5
+ async When__(x) {
6
+ throw new Error("Pending: When__");
7
+ }
8
+ async Then__(x) {
9
+ throw new Error("Pending: Then__");
10
+ }
11
+ }
12
+ ;