harmonyc 0.6.0-8 → 0.7.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/model/model.js ADDED
@@ -0,0 +1,364 @@
1
+ import { Routers } from "./Router.js";
2
+ export class Feature {
3
+ constructor(name) {
4
+ this.name = name;
5
+ this.root = new Section();
6
+ this.prelude = '';
7
+ }
8
+ get tests() {
9
+ return makeTests(this.root);
10
+ }
11
+ get testGroups() {
12
+ return makeGroups(this.tests);
13
+ }
14
+ toCode(cg) {
15
+ cg.feature(this);
16
+ }
17
+ }
18
+ export class Branch {
19
+ constructor(children = []) {
20
+ this.isFork = false;
21
+ this.isEnd = false;
22
+ this.children = children;
23
+ children.forEach((child) => (child.parent = this));
24
+ }
25
+ setFork(isFork) {
26
+ this.isFork = isFork;
27
+ return this;
28
+ }
29
+ setFeature(feature) {
30
+ for (const child of this.children)
31
+ child.setFeature(feature);
32
+ return this;
33
+ }
34
+ addChild(child, index = this.children.length) {
35
+ this.children.splice(index, 0, child);
36
+ child.parent = this;
37
+ return child;
38
+ }
39
+ get isLeaf() {
40
+ return this.children.length === 0;
41
+ }
42
+ get successors() {
43
+ if (!this.isLeaf)
44
+ return this.children.filter((c, i) => i === 0 || c.isFork);
45
+ else {
46
+ if (this.isEnd)
47
+ return [];
48
+ const next = this.nextNonForkAncestorSibling;
49
+ if (next)
50
+ return [next];
51
+ return [];
52
+ }
53
+ }
54
+ get nextNonForkAncestorSibling() {
55
+ if (!this.parent)
56
+ return undefined;
57
+ const { nextSibling } = this;
58
+ if (nextSibling && !nextSibling.isFork)
59
+ return nextSibling;
60
+ return this.parent.nextNonForkAncestorSibling;
61
+ }
62
+ get nextSibling() {
63
+ if (!this.parent)
64
+ return undefined;
65
+ return this.parent.children[this.siblingIndex + 1];
66
+ }
67
+ get siblingIndex() {
68
+ var _a, _b;
69
+ return (_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.children.indexOf(this)) !== null && _b !== void 0 ? _b : -1;
70
+ }
71
+ toString() {
72
+ return this.children
73
+ .map((c) => (c.isFork ? '+ ' : '- ') + c.toString())
74
+ .join('\n');
75
+ }
76
+ replaceWith(newBranch) {
77
+ if (!this.parent)
78
+ throw new Error('cannot replace root');
79
+ this.parent.children.splice(this.siblingIndex, 1, newBranch);
80
+ newBranch.parent = this.parent;
81
+ this.parent = undefined;
82
+ return this;
83
+ }
84
+ }
85
+ export class Step extends Branch {
86
+ constructor(action, responses = [], children, isFork = false) {
87
+ super(children);
88
+ this.action = action;
89
+ this.responses = responses;
90
+ this.isFork = isFork;
91
+ }
92
+ get phrases() {
93
+ return [this.action, ...this.responses];
94
+ }
95
+ toCode(cg) {
96
+ if (this.responses[0] instanceof ErrorResponse) {
97
+ cg.errorStep(this.action, this.responses[0].message);
98
+ }
99
+ else {
100
+ cg.step(this.action, this.responses);
101
+ }
102
+ }
103
+ setFeature(feature) {
104
+ this.action.setFeature(feature);
105
+ for (const response of this.responses)
106
+ response.setFeature(feature);
107
+ return super.setFeature(feature);
108
+ }
109
+ headToString() {
110
+ return `${this.action}` + this.responses.map((r) => ` => ${r}`).join('');
111
+ }
112
+ toString() {
113
+ return this.headToString() + indent(super.toString());
114
+ }
115
+ get isEmpty() {
116
+ return this.phrases.every((phrase) => phrase.isEmpty);
117
+ }
118
+ }
119
+ export class State {
120
+ constructor(text = '') {
121
+ this.text = text;
122
+ }
123
+ }
124
+ export class Label {
125
+ constructor(text = '') {
126
+ this.text = text;
127
+ }
128
+ get isEmpty() {
129
+ return this.text === '';
130
+ }
131
+ }
132
+ export class Section extends Branch {
133
+ constructor(label, children, isFork = false) {
134
+ super(children);
135
+ this.label = label !== null && label !== void 0 ? label : new Label();
136
+ this.isFork = isFork;
137
+ }
138
+ toString() {
139
+ if (this.label.text === '')
140
+ return super.toString();
141
+ return this.label.text + ':' + indent(super.toString());
142
+ }
143
+ get isEmpty() {
144
+ return this.label.isEmpty;
145
+ }
146
+ }
147
+ export class Part {
148
+ }
149
+ export class Word extends Part {
150
+ constructor(text = '') {
151
+ super();
152
+ this.text = text;
153
+ }
154
+ toString() {
155
+ return this.text;
156
+ }
157
+ }
158
+ export class Arg extends Part {
159
+ }
160
+ export class StringLiteral extends Arg {
161
+ constructor(text = '') {
162
+ super();
163
+ this.text = text;
164
+ }
165
+ toString() {
166
+ return JSON.stringify(this.text);
167
+ }
168
+ toCode(cg) {
169
+ return cg.stringLiteral(this.text);
170
+ }
171
+ toDeclaration(cg, index) {
172
+ return cg.stringParamDeclaration(index);
173
+ }
174
+ }
175
+ export class CodeLiteral extends Arg {
176
+ constructor(src = '') {
177
+ super();
178
+ this.src = src;
179
+ }
180
+ toString() {
181
+ return '`' + this.src + '`';
182
+ }
183
+ toCode(cg) {
184
+ return cg.codeLiteral(this.src);
185
+ }
186
+ toDeclaration(cg, index) {
187
+ return cg.variantParamDeclaration(index);
188
+ }
189
+ }
190
+ export class Phrase {
191
+ constructor(content = [], docstring) {
192
+ this.content = content;
193
+ this.docstring =
194
+ docstring === undefined ? undefined : new StringLiteral(docstring);
195
+ }
196
+ setFeature(feature) {
197
+ this.feature = feature;
198
+ }
199
+ get keyword() {
200
+ return this.kind === 'action' ? 'When' : 'Then';
201
+ }
202
+ get args() {
203
+ return [...this.content, this.docstring].filter((c) => c instanceof Arg);
204
+ }
205
+ get isEmpty() {
206
+ return this.content.length === 0 && this.docstring === undefined;
207
+ }
208
+ toString() {
209
+ return [
210
+ ...(this.content.length > 0
211
+ ? [this.content.map((c) => c.toString()).join(' ')]
212
+ : []),
213
+ ...(this.docstring !== undefined
214
+ ? this.docstring.text.split('\n').map((l) => '| ' + l)
215
+ : []),
216
+ ].join('\n');
217
+ }
218
+ toSingleLineString() {
219
+ return [...this.content, ...(this.docstring ? [this.docstring] : [])]
220
+ .map((c) => c.toString())
221
+ .join(' ');
222
+ }
223
+ }
224
+ export class Action extends Phrase {
225
+ constructor() {
226
+ super(...arguments);
227
+ this.kind = 'action';
228
+ }
229
+ toCode(cg) {
230
+ if (!this.content.length && this.docstring === undefined)
231
+ return;
232
+ cg.phrase(this);
233
+ }
234
+ }
235
+ export class Response extends Phrase {
236
+ constructor() {
237
+ super(...arguments);
238
+ this.kind = 'response';
239
+ }
240
+ get isErrorResponse() {
241
+ if (this.content.length === 1 &&
242
+ this.content[0] instanceof Word &&
243
+ this.content[0].text === '!!')
244
+ return true;
245
+ if (this.content.length === 2 &&
246
+ this.content[0] instanceof Word &&
247
+ this.content[0].text === '!!' &&
248
+ this.content[1] instanceof StringLiteral)
249
+ return true;
250
+ }
251
+ toCode(cg) {
252
+ if (!this.content.length && this.docstring === undefined)
253
+ return;
254
+ cg.phrase(this);
255
+ }
256
+ }
257
+ export class ErrorResponse extends Response {
258
+ get message() {
259
+ var _a;
260
+ return (_a = this.content[0]) !== null && _a !== void 0 ? _a : this.docstring;
261
+ }
262
+ }
263
+ export class Precondition extends Branch {
264
+ constructor(state = '') {
265
+ super();
266
+ this.state = new State();
267
+ this.state.text = state;
268
+ }
269
+ get isEmpty() {
270
+ return this.state.text === '';
271
+ }
272
+ }
273
+ export function makeTests(root) {
274
+ const routers = new Routers(root);
275
+ let tests = [];
276
+ let ic = routers.getIncompleteCount();
277
+ let newIc;
278
+ do {
279
+ const newTest = new Test(root, routers.nextWalk());
280
+ newIc = routers.getIncompleteCount();
281
+ if (newIc < ic)
282
+ tests.push(newTest);
283
+ ic = newIc;
284
+ } while (ic > 0);
285
+ // sort by order of appearance of the last branch
286
+ const branchIndex = new Map();
287
+ let i = 0;
288
+ function walk(branch) {
289
+ branchIndex.set(branch, i++);
290
+ for (const child of branch.children)
291
+ walk(child);
292
+ }
293
+ walk(root);
294
+ tests = tests.filter((t) => t.steps.length > 0);
295
+ tests.sort((a, b) => branchIndex.get(a.last) - branchIndex.get(b.last));
296
+ tests.forEach((test, i) => (test.testNumber = `T${i + 1}`));
297
+ return tests;
298
+ }
299
+ export class Test {
300
+ constructor(root, branches) {
301
+ this.root = root;
302
+ this.branches = branches;
303
+ this.branches = this.branches.filter((b) => !b.isEmpty);
304
+ this.labels = this.branches
305
+ .filter((b) => b instanceof Section)
306
+ .filter((s) => !s.isEmpty)
307
+ .map((s) => s.label.text);
308
+ }
309
+ get steps() {
310
+ return this.branches.filter((b) => b instanceof Step);
311
+ }
312
+ get last() {
313
+ return this.steps[this.steps.length - 1];
314
+ }
315
+ get name() {
316
+ return `${[this.testNumber, ...this.labels].join(' - ')}`;
317
+ }
318
+ toCode(cg) {
319
+ cg.test(this);
320
+ }
321
+ toString() {
322
+ return `+ ${this.name}:\n${this.steps
323
+ .map((s) => ` - ${s.headToString()}`)
324
+ .join('\n')}`;
325
+ }
326
+ }
327
+ export function makeGroups(tests) {
328
+ if (tests.length === 0)
329
+ return [];
330
+ if (tests[0].labels.length === 0)
331
+ return [tests[0], ...makeGroups(tests.slice(1))];
332
+ const name = tests[0].labels[0];
333
+ let count = tests.findIndex((t) => t.labels[0] !== name);
334
+ if (count === -1)
335
+ count = tests.length;
336
+ if (count === 1)
337
+ return [tests[0], ...makeGroups(tests.slice(1))];
338
+ tests.slice(0, count).forEach((test) => test.labels.shift());
339
+ return [
340
+ new TestGroup(name, makeGroups(tests.slice(0, count))),
341
+ ...makeGroups(tests.slice(count)),
342
+ ];
343
+ }
344
+ export class TestGroup {
345
+ constructor(name, items) {
346
+ this.name = name;
347
+ this.items = items;
348
+ }
349
+ toString() {
350
+ return `+ ${this.name}:` + indent(this.items.join('\n'));
351
+ }
352
+ toCode(cg) {
353
+ cg.testGroup(this);
354
+ }
355
+ }
356
+ function indent(s) {
357
+ if (!s)
358
+ return '';
359
+ return ('\n' +
360
+ s
361
+ .split('\n')
362
+ .map((l) => ' ' + l)
363
+ .join('\n'));
364
+ }
@@ -0,0 +1,15 @@
1
+ import { Label, Section, Step } from "../../model/model.js";
2
+ export function autoLabel(s) {
3
+ const forks = s.children.filter((c, i) => c.isFork || i === 0);
4
+ if (forks.length > 1) {
5
+ forks
6
+ .filter((c) => c instanceof Step)
7
+ .forEach((c) => {
8
+ const label = c.action.toSingleLineString();
9
+ const autoSection = new Section(new Label(label), [], c.isFork);
10
+ c.replaceWith(autoSection);
11
+ autoSection.addChild(c);
12
+ });
13
+ }
14
+ s.children.forEach((c) => autoLabel(c));
15
+ }
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.6.0-8",
4
+ "version": "0.7.0",
5
5
  "author": "Bernát Kalló",
6
6
  "type": "module",
7
7
  "bin": {
@@ -0,0 +1,4 @@
1
+ import { buildLexer } from 'typescript-parsec';
2
+ import rules from './lexer_rules.js';
3
+ export { T } from './lexer_rules.js';
4
+ export const lexer = buildLexer(rules);
@@ -0,0 +1,62 @@
1
+ export var T;
2
+ (function (T) {
3
+ T["Newline"] = "newline";
4
+ T["Comment"] = "comment";
5
+ /** the => */
6
+ T["ResponseArrow"] = "=>";
7
+ T["ErrorMark"] = "!!";
8
+ T["Words"] = "words";
9
+ T["Minus"] = "-";
10
+ T["Plus"] = "+";
11
+ T["Colon"] = ":";
12
+ T["Space"] = "space";
13
+ T["OpeningBracket"] = "[";
14
+ T["ClosingBracket"] = "]";
15
+ T["DoubleQuoteString"] = "double-quote string";
16
+ T["UnclosedDoubleQuoteString"] = "unclosed double-quote string";
17
+ T["BacktickString"] = "backtick string";
18
+ T["UnclosedBacktickString"] = "unclosed backtick string";
19
+ T["InvalidEmptyBacktickString"] = "invalid empty backtick string";
20
+ T["InvalidWhitespace"] = "invalid whitespace";
21
+ T["InvalidTab"] = "invalid tab";
22
+ T["MultilineString"] = "multiline string";
23
+ T["InvalidMultilineStringMark"] = "invalid multiline string mark";
24
+ })(T || (T = {}));
25
+ const rules = [
26
+ // false = ignore token
27
+ // if multiple patterns match, the longest one wins, if same, the former
28
+ // patterns must start with ^ and be /g
29
+ [true, /^\n/g, T.Newline],
30
+ [true, /^\t/g, T.InvalidTab],
31
+ [true, /^[\x00-\x1f]/g, T.InvalidWhitespace],
32
+ [true, /^ /g, T.Space],
33
+ [false, /^(#|>|\/\/).*?(?=\n|$)/g, T.Comment],
34
+ [true, /^:(?=\s*(?:\n|$))/g, T.Colon],
35
+ [
36
+ true,
37
+ /^(?!\s|=>|!!|- |\+ |[\[\]"`|]).+?(?=[\[\]"`|]|\n|$|=>|!!|:\s*(?:\n|$)|$)/g,
38
+ T.Words,
39
+ ],
40
+ [true, /^-/g, T.Minus],
41
+ [true, /^\+/g, T.Plus],
42
+ [true, /^\[/g, T.OpeningBracket],
43
+ [true, /^\]/g, T.ClosingBracket],
44
+ [true, /^!!/g, T.ErrorMark],
45
+ [true, /^=>/g, T.ResponseArrow],
46
+ [
47
+ true,
48
+ /^"(?:[^"\\\n]|\\(?:[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*"/g,
49
+ T.DoubleQuoteString,
50
+ ],
51
+ [
52
+ true,
53
+ /^"(?:[^"\\\n]|\\(?:[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*/g,
54
+ T.UnclosedDoubleQuoteString,
55
+ ],
56
+ [true, /^``/g, T.InvalidEmptyBacktickString],
57
+ [true, /^`[^`]+`/g, T.BacktickString],
58
+ [true, /^`[^`]*/g, T.UnclosedBacktickString],
59
+ [true, /^\|(?: .*|(?=\n|$))/g, T.MultilineString],
60
+ [true, /^\|[^ ]/g, T.InvalidMultilineStringMark],
61
+ ];
62
+ export default rules;
@@ -0,0 +1,78 @@
1
+ import { alt_sc, apply, expectEOF, expectSingleResult, kright, opt_sc, rep_sc, seq, tok, list_sc, kleft, kmid, fail, } from 'typescript-parsec';
2
+ import { T, lexer } from "./lexer.js";
3
+ import { Action, Response, CodeLiteral, StringLiteral, Section, Step, Word, Label, ErrorResponse, } from "../model/model.js";
4
+ export function parse(input, production = TEST_DESIGN) {
5
+ const tokens = lexer.parse(input);
6
+ return expectSingleResult(expectEOF(production.parse(tokens)));
7
+ }
8
+ export const SPACES = rep_sc(tok(T.Space));
9
+ export const NEWLINES = list_sc(tok(T.Newline), SPACES); // empty lines can have spaces
10
+ export const WORDS = apply(tok(T.Words), ({ text }) => new Word(text.trimEnd().split(/\s+/).join(' ')));
11
+ export const ERROR_MARK = apply(tok(T.ErrorMark), ({ text }) => new Word(text));
12
+ export const BULLET_POINT_LIKE_WORD = apply(alt_sc(tok(T.Plus), tok(T.Minus)), ({ text }) => new Word(text));
13
+ export const DOUBLE_QUOTE_STRING = alt_sc(apply(tok(T.DoubleQuoteString), ({ text }) => new StringLiteral(JSON.parse(text))), seq(tok(T.UnclosedDoubleQuoteString), fail('unclosed double-quote string')));
14
+ export const BACKTICK_STRING = apply(tok(T.BacktickString), ({ text }) => new CodeLiteral(text.slice(1, -1)));
15
+ export const DOCSTRING = apply(list_sc(tok(T.MultilineString), seq(tok(T.Newline), SPACES)), (lines) => lines.map(({ text }) => text.slice(2)).join('\n'));
16
+ export const PART = alt_sc(WORDS, ERROR_MARK, BULLET_POINT_LIKE_WORD, DOUBLE_QUOTE_STRING, BACKTICK_STRING);
17
+ export const PHRASE = seq(opt_sc(list_sc(PART, SPACES)), opt_sc(kright(opt_sc(NEWLINES), kright(SPACES, DOCSTRING))));
18
+ export const ACTION = apply(PHRASE, ([parts, docstring]) => new Action(parts, docstring));
19
+ export const RESPONSE = apply(PHRASE, ([parts, docstring]) => {
20
+ if ((parts === null || parts === void 0 ? void 0 : parts[0]) instanceof Word && parts[0].text === '!!') {
21
+ return new ErrorResponse(parts.slice(1), docstring);
22
+ }
23
+ return new Response(parts, docstring);
24
+ });
25
+ export const ARROW = kmid(seq(opt_sc(NEWLINES), SPACES), tok(T.ResponseArrow), SPACES);
26
+ export const RESPONSE_ITEM = kright(ARROW, RESPONSE);
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(' ')));
29
+ export const SECTION = apply(LABEL, (text) => new Section(text));
30
+ export const BRANCH = alt_sc(SECTION, STEP); // section first, to make sure there is no colon after step
31
+ export const DENTS = apply(opt_sc(seq(SPACES, alt_sc(tok(T.Plus), tok(T.Minus)), tok(T.Space))), (lineHead) => {
32
+ if (!lineHead)
33
+ return { dent: 0, isFork: true };
34
+ const [dents, seqOrFork] = lineHead;
35
+ return { dent: dents.length / 2, isFork: seqOrFork.kind === T.Plus };
36
+ });
37
+ export const LINE = apply(seq(DENTS, BRANCH), ([{ dent, isFork }, branch], [start, end]) => ({
38
+ dent,
39
+ branch: branch.setFork(isFork),
40
+ }));
41
+ export const TEST_DESIGN = kmid(rep_sc(NEWLINES), apply(list_sc(apply(LINE, (line, [start, end]) => ({ line, start, end })), NEWLINES), (lines) => {
42
+ const startDent = 0;
43
+ let dent = startDent;
44
+ const root = new Section(new Label(''));
45
+ let parent = root;
46
+ for (const { line, start } of lines) {
47
+ const { dent: d, branch } = line;
48
+ if (Math.round(d) !== d) {
49
+ throw new Error(`invalid odd indent of ${d * 2} at line ${start.pos.rowBegin}`);
50
+ }
51
+ else if (d > dent + 1) {
52
+ throw new Error(`invalid indent ${d} at line ${start.pos.rowBegin}`);
53
+ }
54
+ else if (d === dent + 1) {
55
+ parent = parent.children[parent.children.length - 1];
56
+ ++dent;
57
+ }
58
+ else if (d < startDent) {
59
+ throw new Error(`invalid indent ${d} at line ${start.pos.rowBegin}`);
60
+ }
61
+ else
62
+ while (d < dent) {
63
+ parent = parent.parent;
64
+ --dent;
65
+ }
66
+ parent.addChild(branch);
67
+ }
68
+ return root;
69
+ }), rep_sc(NEWLINES));
70
+ function inputText(start, end) {
71
+ let text = '';
72
+ let t = start;
73
+ while (t && t !== end) {
74
+ text += t.text;
75
+ t = t.next;
76
+ }
77
+ return text;
78
+ }
@@ -0,0 +1,9 @@
1
+ import { watchFiles } from "../cli/watch.js";
2
+ export default function harmonyPlugin({ watchDir, }) {
3
+ return {
4
+ name: 'harmony',
5
+ configureServer(server) {
6
+ watchFiles([`${watchDir}/**/*.harmony`]);
7
+ },
8
+ };
9
+ }
package/compile.js DELETED
@@ -1,14 +0,0 @@
1
- import { NodeTest } from './languages/JavaScript.js';
2
- import { OutFile } from './outFile.js';
3
- import { parse } from './syntax.js';
4
- import { stepsFileName, testFileName } from './filenames/filenames.js';
5
- export function compileFeature(fileName, src) {
6
- const feature = parse({ fileName, src });
7
- const testFn = testFileName(fileName);
8
- const testFile = new OutFile(testFn);
9
- const stepsFn = stepsFileName(fileName);
10
- const stepsFile = new OutFile(stepsFn);
11
- const cg = new NodeTest(testFile, stepsFile);
12
- feature.toCode(cg);
13
- return { outFile: testFile, stepsFile };
14
- }
package/config.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,37 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Gherkin = void 0;
4
- class Gherkin {
5
- constructor(outFile) {
6
- this.outFile = outFile;
7
- }
8
- feature(name, tests) {
9
- this.outFile.print(`Feature: ${name}`);
10
- this.outFile.print('');
11
- this.outFile.indent(() => {
12
- for (const test of tests) {
13
- test.toCode(this);
14
- }
15
- });
16
- }
17
- test(t) {
18
- this.outFile.print(`Scenario: ${t.name}`);
19
- this.outFile.indent(() => {
20
- for (const step of t.steps) {
21
- step.toCode(this);
22
- }
23
- });
24
- this.outFile.print('');
25
- }
26
- phrase(p) {
27
- this.outFile.print(`${p.keyword} ${p.text} || ${p.feature}`);
28
- if (p.docstring !== undefined) {
29
- this.outFile.indent(() => {
30
- this.outFile.print(`"""`);
31
- this.outFile.print(...p.docstring.split('\n'));
32
- this.outFile.print(`"""`);
33
- });
34
- }
35
- }
36
- }
37
- exports.Gherkin = Gherkin;
@@ -1,35 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NodeTest = void 0;
4
- class NodeTest {
5
- constructor(outFile) {
6
- this.outFile = outFile;
7
- this.framework = 'vitest';
8
- }
9
- feature(feature) {
10
- if (this.framework === 'vitest') {
11
- this.outFile.print(`import { test, expect } from 'vitest';`);
12
- }
13
- this.outFile.print(feature.prelude);
14
- for (const test of feature.tests) {
15
- test.toCode(this);
16
- }
17
- }
18
- test(t) {
19
- this.outFile.print(`test('${t.name}', async () => {`);
20
- this.outFile.indent(() => {
21
- for (const step of t.steps) {
22
- step.toCode(this);
23
- }
24
- });
25
- this.outFile.print('})');
26
- this.outFile.print('');
27
- }
28
- phrase(p) {
29
- var _a;
30
- this.outFile.loc(p).print('/// ' + p.text);
31
- const code = (_a = p.definition()) !== null && _a !== void 0 ? _a : `throw 'Not defined: ' + ${JSON.stringify(p.text)};`;
32
- this.outFile.print(...code.split('\n'));
33
- }
34
- }
35
- exports.NodeTest = NodeTest;