harmonyc 0.22.1 → 0.23.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/AGENTS.md CHANGED
@@ -1,105 +1,105 @@
1
- # Writing unit tests in .harmony files
2
-
3
- When writing unit tests, write them in `.harmony` files using a special structured format. This specification will be compiled to JS on-the-fly and imports the corresponding `.phrases.ts` files for step implementations.
4
-
5
- ## File Structure
6
-
7
- For tests belonging to a feature, you need:
8
-
9
- - `example.harmony` - Test specifications (this file)
10
- - `example.phrases.ts` - Step implementations (you create this)
11
-
12
- ## .harmony File Format
13
-
14
- ### Syntax Rules
15
-
16
- #### Line start
17
-
18
- 1. **Lines starting with `+ `** on the same level become forks, each creating a separate test case.
19
- 2. **Lines starting with `- `** become consecutive test steps in the same test case
20
- 3. Every other line is ignored
21
-
22
- #### Line content
23
-
24
- 1. **Sections** are any line ending in `:`. It is ignored but is used for naming test cases and groups.
25
- 2. **Steps** consist of an _action_ and an _expected outcome_ separated by `=>`.
26
- 3. **Error steps** consist of an _action_ followed by `=> !!` and an _expected error message_ in double quotes.
27
-
28
- #### Action and Expected Outcome syntax
29
-
30
- Use human-readable phrases for both actions and expected outcomes.
31
- Both actions and expected outcomes can contain:
32
-
33
- - **words**
34
- - **punctuation** is ignored
35
- - **string arguments** enclosed in double quotes`"like this"`
36
- - **code arguments** enclosed in backticks `` `like this` ``
37
-
38
- Use string arguments for string parameters and code arguments for numbers, booleans, objects, arrays etc.
39
-
40
- If there is one obvious default action in the test case with one parameter, use an action that contains just that parameter.
41
- If there is one obvious default expected outcome with one parameter, use an expected outcome that contains just that parameter.
42
-
43
- ### Example
44
-
45
- ```harmony
46
- # Calculator Functions
47
-
48
- This library provides basic calculator functions.
49
-
50
- + multiply():
51
- - multiply `2` and `3` => `6`
52
- - multiply `-2` and `3` => `-6`
53
-
54
- + divide():
55
- - divide `6` by `2` => `3`
56
-
57
- Division by zero is an error.
58
-
59
- + by zero:
60
- We need to test both non-zero and zero dividends.
61
- + divide `5` by `0` => !! "Division by zero"
62
- + divide `0` by `0` => !! "Division by zero"
63
- ```
64
-
65
- ## .phrases.ts File Implementation
66
-
67
- Create a class that implements step methods corresponding to your test specifications. Use imports as needed, e.g. import the functions you are testing.
68
-
69
- `When_*` methods implement actions, and `Then_*` methods implement expected outcomes. `Then_*` methods receive
70
- an extra parameter, the return value of the preceding `When_*` method. Add the words and parameters from the action / expected outcome, separated by underscores, with parameters denoted by `X`,`Y`,`Z`,`A`,`B`,...
71
-
72
- Use vitest.expect in assertions.
73
-
74
- Error responses need no implementation.
75
-
76
- ```typescript
77
- import { expect } from 'vitest'
78
- import { multiply, divide } from './calculator'
79
-
80
- export default class CalculatorPhrases {
81
- result: number | undefined
82
-
83
- async When_multiply_X_and_Y(a: number, b: number) {
84
- return multiply(a, b)
85
- }
86
-
87
- async When_divide_X_by_Y(a: number, b: number) {
88
- return divide(a, b)
89
- }
90
-
91
- async Then_X(expected: number, actual: number) {
92
- expect(actual).toBe(expected)
93
- }
94
- }
95
- ```
96
-
97
- ## Best Practices
98
-
99
- 1. **Keep test descriptions clear and concise**
100
- 2. **Group related tests using sections**
101
- 3. **Use meaningful parameter names in method signatures, and add TypeScript types**
102
- 4. **Store state in class properties for complex scenarios**
103
- 5. **Handle edge cases explicitly (errors, null values, etc.)**
104
- 6. **Use descriptive comments in-line to explain test groups**
105
- 7. **Test both happy path and error conditions**
1
+ # Writing unit tests in .harmony files
2
+
3
+ When writing unit tests, write them in `.harmony` files using a special structured format. This specification will be compiled to JS on-the-fly and imports the corresponding `.phrases.ts` files for step implementations.
4
+
5
+ ## File Structure
6
+
7
+ For tests belonging to a feature, you need:
8
+
9
+ - `example.harmony` - Test specifications (this file)
10
+ - `example.phrases.ts` - Step implementations (you create this)
11
+
12
+ ## .harmony File Format
13
+
14
+ ### Syntax Rules
15
+
16
+ #### Line start
17
+
18
+ 1. **Lines starting with `+ `** on the same level become forks, each creating a separate test case.
19
+ 2. **Lines starting with `- `** become consecutive test steps in the same test case
20
+ 3. Every other line is ignored
21
+
22
+ #### Line content
23
+
24
+ 1. **Sections** are any line ending in `:`. It is ignored but is used for naming test cases and groups.
25
+ 2. **Steps** consist of an _action_ and an _expected outcome_ separated by `=>`.
26
+ 3. **Error steps** consist of an _action_ followed by `=> !!` and an _expected error message_ in double quotes.
27
+
28
+ #### Action and Expected Outcome syntax
29
+
30
+ Use human-readable phrases for both actions and expected outcomes.
31
+ Both actions and expected outcomes can contain:
32
+
33
+ - **words**
34
+ - **punctuation** is ignored
35
+ - **string arguments** enclosed in double quotes`"like this"`
36
+ - **code arguments** enclosed in backticks `` `like this` ``
37
+
38
+ Use string arguments for string parameters and code arguments for numbers, booleans, objects, arrays etc.
39
+
40
+ If there is one obvious default action in the test case with one parameter, use an action that contains just that parameter.
41
+ If there is one obvious default expected outcome with one parameter, use an expected outcome that contains just that parameter.
42
+
43
+ ### Example
44
+
45
+ ```harmony
46
+ # Calculator Functions
47
+
48
+ This library provides basic calculator functions.
49
+
50
+ + multiply():
51
+ - multiply `2` and `3` => `6`
52
+ - multiply `-2` and `3` => `-6`
53
+
54
+ + divide():
55
+ - divide `6` by `2` => `3`
56
+
57
+ Division by zero is an error.
58
+
59
+ + by zero:
60
+ We need to test both non-zero and zero dividends.
61
+ + divide `5` by `0` => !! "Division by zero"
62
+ + divide `0` by `0` => !! "Division by zero"
63
+ ```
64
+
65
+ ## .phrases.ts File Implementation
66
+
67
+ Create a class that implements step methods corresponding to your test specifications. Use imports as needed, e.g. import the functions you are testing.
68
+
69
+ `When_*` methods implement actions, and `Then_*` methods implement expected outcomes. `Then_*` methods receive
70
+ an extra parameter, the return value of the preceding `When_*` method. Add the words and parameters from the action / expected outcome, separated by underscores, with parameters denoted by `X`,`Y`,`Z`,`A`,`B`,...
71
+
72
+ Use vitest.expect in assertions.
73
+
74
+ Error responses need no implementation.
75
+
76
+ ```typescript
77
+ import { expect } from 'vitest'
78
+ import { multiply, divide } from './calculator'
79
+
80
+ export default class CalculatorPhrases {
81
+ result: number | undefined
82
+
83
+ async When_multiply_X_and_Y(a: number, b: number) {
84
+ return multiply(a, b)
85
+ }
86
+
87
+ async When_divide_X_by_Y(a: number, b: number) {
88
+ return divide(a, b)
89
+ }
90
+
91
+ async Then_X(expected: number, actual: number) {
92
+ expect(actual).toBe(expected)
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## Best Practices
98
+
99
+ 1. **Keep test descriptions clear and concise**
100
+ 2. **Group related tests using sections**
101
+ 3. **Use meaningful parameter names in method signatures, and add TypeScript types**
102
+ 4. **Store state in class properties for complex scenarios**
103
+ 5. **Handle edge cases explicitly (errors, null values, etc.)**
104
+ 6. **Use descriptive comments in-line to explain test groups**
105
+ 7. **Test both happy path and error conditions**
package/README.md CHANGED
@@ -1,148 +1,271 @@
1
1
  # Harmony Code
2
2
 
3
- A test design & BDD tool that helps you separate _what_ you test and _how_ you automate it. You write test cases in a simple easy-to-read format, and then automate them with Vitest.
3
+ **Write unit tests that speak human language**
4
4
 
5
- ## Setup
5
+ Harmony Code transforms how you write and maintain tests. Instead of wrestling with complex test code, you describe _what_ you want to test in plain English (or whichever language), and Harmony generates the Vitest test code for you.
6
6
 
7
- You need to have Node.js installed. Then you can install Harmony Code in your project folder by:
7
+ ## Why Harmony Code?
8
+
9
+ **💭 Think in scenarios, not syntax**
10
+ Focus on business logic and user flows instead of test framework boilerplate.
11
+
12
+ **📖 Tests as living documentation**
13
+ Your `.harmony` files become readable specifications that anyone can understand.
14
+
15
+ **🖇️ Separation of concerns**
16
+ Test design (`.harmony`) stays separate from test implementation (`.phrases.ts`).
17
+
18
+ **⚡ Vitest integration**
19
+ Get all the benefits of Vitest's speed and developer experience.
20
+
21
+ ## 🚀 Quick Start
22
+
23
+ ### 1. Install Harmony Code
8
24
 
9
25
  ```bash
10
26
  npm install harmonyc
11
27
  ```
12
28
 
13
- Then add it as a plugin to your `vitest.config.js` or `vite.config.js` file, and make sure to include your `.harmony` files:
29
+ ### 2. Configure Vitest
30
+
31
+ Add Harmony plugin to your `vitest.config.js` or `vite.config.js` and include `.harmony` files in tests:
14
32
 
15
33
  ```js
16
34
  import harmony from 'harmonyc/vitest'
17
35
 
18
36
  export default {
19
37
  plugins: [harmony()],
20
- include: ['src/**/*.harmony'],
38
+ test: {
39
+ include: ['**/*.{test,spec}.{js,ts}', '**/*.harmony'],
40
+ },
21
41
  }
22
42
  ```
23
43
 
24
- This will run .harmony files in vitest.
44
+ ### 3. Write your first test
25
45
 
26
- ### Auto-generating phrases files and methods
46
+ Create `formatCurrency.harmony`:
27
47
 
28
- The Vitest plugin will auto-generate phrases files or edit them to add necessary phrase methods whenever you change your .harmony files. It will automatically sort phrase methods.
48
+ ```harmony
49
+ # Currency Formatting
50
+
51
+ + Dollar formatting:
52
+ - format amount `123.45` => "$123.45"
53
+ - format amount `100` => "$100.00"
54
+
55
+ + Euro formatting:
56
+ - format amount `67.98` with "EUR" => "€67.98"
57
+ ```
29
58
 
30
- ## VSCode plugin
59
+ ### 4. Implement test steps
31
60
 
32
- Harmony Code has a [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=harmony-ac.harmony-code) that supports syntax highlighting.
61
+ Harmony automatically generates `formatCurrency.phrases.ts` for you, adding new method stubs and keeping your existing implementations. It intelligently sorts methods alphabetically within `When_` and `Then_` categories. Fill them in like this:
33
62
 
34
- Harmony Code is compatible with Vitest's VSCode plugin, so you can run and debug tests from the editor.
63
+ ```typescript
64
+ import { expect } from 'vitest'
65
+ import { formatCurrency } from '../src/currency'
35
66
 
36
- ## Syntax
67
+ export default class FormatCurrencyPhrases {
68
+ async When_format_amount_X(amount: number) {
69
+ return formatCurrency(amount)
70
+ }
37
71
 
38
- A `.harmony` file is a text file with a syntax that looks like this:
72
+ async When_format_amount_X_with_Y(amount: number, currency: string) {
73
+ return formatCurrency(amount, currency)
74
+ }
39
75
 
76
+ async Then_X(expected: string, actual: string) {
77
+ expect(actual).toBe(expected)
78
+ }
79
+ }
40
80
  ```
41
- + Products API:
42
- + Create:
43
- + Anonymous:
44
- - create product => !! "unauthorized"
45
- + Admin:
46
- - authenticate with "admin" => product count `0`
47
- - create product
48
- => product created
49
- => product count `1`
50
- - Delete:
51
- - delete product => product deleted => product count `0`
81
+
82
+ ### 5. Run your tests
83
+
84
+ ```bash
85
+ npx vitest
52
86
  ```
53
87
 
54
- ### Indentation
88
+ That's it! Your tests run just like regular Vitest tests, but with the clarity of human language.
89
+
90
+ ## 🛠 IDE Support
55
91
 
56
- The lines of a file are nodes of a tree. The tree is specified with the indentation of the lines, which is n times 2 spaces and a `+` or `-` with one more space. The `+` or `-` sign is considered to be part of the indentation.
92
+ **VS Code Extension**: Get syntax highlighting and IntelliSense for `.harmony` files.
57
93
 
58
- ### Sequences and forks
94
+ Install: [Harmony Code Extension](https://marketplace.visualstudio.com/items?itemName=harmony-ac.harmony-code)
59
95
 
60
- `-` means a sequence: the node follows the previous sibling node and its descendants.
96
+ **Vitest Integration**: Run and debug tests directly from VS Code using the official Vitest extension.
61
97
 
62
- `+` means a fork: the node directly follows its parent node. All siblings with `+` are separate branches, they will generate separate scenarios.
98
+ ## 📚 Harmony Code Syntax Guide
63
99
 
64
- ### Phrases (actions and responses)
100
+ ### Test Structure
65
101
 
66
- 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 `=>`.
102
+ Harmony Code uses indentation and symbols to create test scenarios:
67
103
 
68
- 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.
104
+ - **`+ `** creates test **forks** - each becomes a separate test case
105
+ - **`- `** creates **sequential steps** within the same test case
106
+ - **Lines ending with `:`** are sections for organizing tests
69
107
 
70
- ### Arguments
108
+ ### Actions and Expectations
71
109
 
72
- Phrases (actions and responses) can have arguments which are passed to the implementation function. There are two types of arguments: strings and code fragments:
110
+ Each step can have an action and expected outcomes:
73
111
 
74
112
  ```harmony
75
- + strings:
76
- + hello "John"
77
- + code fragment:
78
- + greet `3` times
113
+ - action => expected result
114
+ - action => !! "expected error message"
79
115
  ```
80
116
 
81
- becomes
82
-
83
- ```javascript
84
- test('T1 - strings', async () => {
85
- const P = new Phrases()
86
- await P.When_hello_X('John')
87
- })
88
- test('T2 - code fragment', async () => {
89
- const P = new Phrases()
90
- await P.When_greet_X_times(3)
91
- })
92
- ```
117
+ **Actions** become `When_*` methods, **expectations** become `Then_*` methods.
93
118
 
94
- Arguments are added to the function name as `X`, `Y`, `Z`, `A`, `B` etc.
119
+ ### Parameters
95
120
 
96
- ### Labels
121
+ Use quotes for strings and backticks for other values:
97
122
 
98
- Labels are lines that start with `-` or `+` and end with `:`. You can use them to structure your test design.
99
- They are not included in the test case, but the test case name is generated from the labels.
123
+ ```harmony
124
+ - login with "john@example.com" remember `true` => user logged in
125
+ ```
100
126
 
101
- ### Comments
127
+ Becomes:
102
128
 
103
- Lines starting with `#` or `//` are comments and are ignored.
129
+ ```typescript
130
+ async When_login_with_X_remember_Y(email: string, rememberMe: boolean) {
131
+ // your implementation
132
+ }
133
+ ```
104
134
 
105
- ### Switches
135
+ Parameters are mapped to `X`, `Y`, `Z`, `A`, `B`, `C`... (alphabetically) in method names.
106
136
 
107
- You can generate multiple test cases by adding a `{ A / B / C }` syntax into action(s) and possibly response(s).
137
+ ### Example: User Authentication
108
138
 
109
139
  ```harmony
110
- + password is { "A" / "asdf" / "password123" } => !! "password is too weak"
140
+ # User Authentication
141
+
142
+ + successful login:
143
+ - enter email "user@test.com"
144
+ - enter password "password123"
145
+ - click login button => redirected to dashboard
146
+
147
+ + failed login:
148
+ - enter email "wrong@test.com"
149
+ - enter password "wrongpass"
150
+ - click login button => !! "Invalid credentials"
151
+
152
+ + password requirements:
153
+ + too short:
154
+ - set password "123" => !! "Password must be at least 8 characters"
155
+ + missing special character:
156
+ - set password "password123" => !! "Password must contain a special character"
111
157
  ```
112
158
 
113
- ### Error matching
159
+ ## 🔄 How It Works
114
160
 
115
- 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 `!!`.
161
+ 1. **Write scenarios** in `.harmony` files using human-readable language
162
+ 2. **Harmony generates** the corresponding Vitest test structure
163
+ 3. **Harmony updates** your `.phrases.ts` files, adding new method stubs while preserving existing implementations
164
+ 4. **You implement** the step methods (only the new ones need your attention)
165
+ 5. **Run tests** with Vitest watch mode automatically, or with VSCode Vitest extension
166
+
167
+ The beauty is in the separation: your test scenarios remain clean and business-focused, while implementation details live in separate files.
168
+
169
+ ### Smart Code Generation
170
+
171
+ - **Preserves your work**: Existing method implementations are never overwritten
172
+ - **Adds only what's needed**: New method stubs are generated for missing steps
173
+ - **Removes unused methods**: Methods no longer referenced in `.harmony` files are cleaned up
174
+ - **Keeps things organized**: Methods are sorted alphabetically within `When_` and `Then_` categories
175
+
176
+ ## 🎯 Advanced Features
116
177
 
117
178
  ### Variables
118
179
 
119
- You can set variables in the tests and use them in strings and code fragments:
180
+ Store and reuse values across test steps:
120
181
 
182
+ ```harmony
183
+ + user workflow:
184
+ - create user => ${userId}
185
+ - login with user id "${userId}" => success
186
+ - delete user "${userId}" => user removed
121
187
  ```
122
- + set variable:
123
- + ${name} "John"
124
- + greet "${name}" => "hello John"
125
- + store result into variable:
126
- + run process => ${result}
127
- + "${result}" is "success"
188
+
189
+ ### Test Switches
190
+
191
+ Generate multiple test cases with variations:
192
+
193
+ ```harmony
194
+ + password validation:
195
+ - password { "123" / "abc" / "" } => !! "Invalid password"
196
+ ```
197
+
198
+ This creates three separate test cases, one for each password value.
199
+
200
+ ### Error Testing
201
+
202
+ Use `!!` to test error conditions. The error message is optional - if provided, it checks that the thrown error contains that substring:
203
+
204
+ ```harmony
205
+ + division by zero:
206
+ - divide `10` by `0` => !! "Cannot divide by zero"
207
+ - divide `5` by `0` => !! # just checks that an error is thrown
128
208
  ```
129
209
 
130
- becomes
131
-
132
- ```javascript
133
- test('T1 - set variable', (context) => {
134
- const P = new Phrases();
135
- (context.task.meta.variables ??= {})['name'] = "John";
136
- await P.When_greet_X(context.task.meta.variables?.['name']);
137
- })
138
- test('T2 - store result in variable', (context) => {
139
- const P = new Phrases();
140
- const r = await P.When_run_process();
141
- (context.task.meta.variables ??= {})['result'] = r;
142
- await P.Then__is_(`${context.task.meta.variables?.['result']});
143
- })
210
+ **No implementation needed**: Error steps don't require corresponding methods in your phrases file.
211
+
212
+ ### Complex Scenarios
213
+
214
+ Build sophisticated test flows:
215
+
216
+ ```harmony
217
+ # E-commerce Checkout
218
+
219
+ + guest checkout:
220
+ - add product "T-shirt" to cart
221
+ - go to checkout
222
+ - enter shipping address:
223
+ - name "John Doe"
224
+ - address "123 Main St"
225
+ - select payment method "credit card"
226
+ - complete purchase => order confirmation
227
+
228
+ + member checkout:
229
+ - login as "member@test.com"
230
+ - add product "Laptop" to cart
231
+ - use saved address => address populated
232
+ - complete purchase => order confirmation
233
+ - check order history => order appears
144
234
  ```
145
235
 
146
- ## License
236
+ ## 🧹 Best Practices
237
+
238
+ **Use natural language**: Write steps as if explaining to a manual tester. Write in the language you use for discussing requirements.
239
+ **Keep scenarios focused**: Each test should verify one main behavior
240
+ **Use descriptive names**: Make test intentions clear from the scenario text
241
+ **Use parameter-only names** if there is an obivous single-parameter input or output (e.g., `` => `true` ``, which will be `Then_X(x)` )
242
+ **Group related tests**: Use sections (lines ending with `:`) to organize
243
+ **Test edge cases**: Include error conditions and boundary values
244
+ **Maintain phrase files**: Keep step implementations simple and focused, add JSDoc if step documentation is needed
245
+
246
+ ## ⌨️ CLI Usage
247
+
248
+ Compile `.harmony` files manually:
249
+
250
+ ```bash
251
+ npx harmonyc "src/**/*.harmony"
252
+ ```
253
+
254
+ Watch for changes:
255
+
256
+ ```bash
257
+ npx harmonyc "src/**/*.harmony" --watch
258
+ ```
259
+
260
+ ## 📦 Package Structure
261
+
262
+ - **`harmonyc`** - Core compiler and Vitest plugin
263
+ - **VS Code Extension** - Syntax highlighting and language support
264
+
265
+ ## 🤝 Contributing
266
+
267
+ Harmony Code is open source and welcomes contributions. Check out our [GitHub repository](https://github.com/harmony-ac/code) for issues, feature requests, and development setup.
268
+
269
+ ## 📄 License
147
270
 
148
- MIT
271
+ MIT - see [LICENSE](LICENSE) for details.
@@ -22,7 +22,7 @@ export class VitestGenerator {
22
22
  this.extraArgs = [];
23
23
  }
24
24
  feature(feature) {
25
- const phrasesModule = './' + basename(this.sourceFileName.replace(/\.harmony$/, '.phrases'));
25
+ const phrasesModule = './' + basename(this.sourceFileName.replace(/\.harmony$/, '.phrases.js'));
26
26
  const fn = (this.featureClassName =
27
27
  this.currentFeatureName =
28
28
  pascalCase(feature.name) + 'Phrases');
@@ -121,12 +121,6 @@ export declare class Switch extends Part {
121
121
  toString(): string;
122
122
  toSingleLineString(): string;
123
123
  }
124
- export declare class Router extends Part {
125
- choices: Repeater[];
126
- constructor(choices: Repeater[]);
127
- toString(): string;
128
- toSingleLineString(): string;
129
- }
130
124
  export declare abstract class Arg extends Part {
131
125
  abstract toCode(cg: CodeGenerator): string;
132
126
  abstract toDeclaration(cg: CodeGenerator, index: number): string;
@@ -210,12 +210,12 @@ export class Repeater extends Part {
210
210
  this.choices = choices;
211
211
  }
212
212
  toString() {
213
- return `{${this.choices.map((ps) => ps.join(' ')).join(' & ')}}`;
213
+ return `{${this.choices.map((ps) => ps.join(' ')).join(' && ')}}`;
214
214
  }
215
215
  toSingleLineString() {
216
216
  return `{${this.choices
217
217
  .map((ps) => ps.map((p) => p.toSingleLineString()).join(' '))
218
- .join(' & ')}}`;
218
+ .join(' && ')}}`;
219
219
  }
220
220
  }
221
221
  export class Switch extends Part {
@@ -224,22 +224,10 @@ export class Switch extends Part {
224
224
  this.choices = choices;
225
225
  }
226
226
  toString() {
227
- return `{ ${this.choices.join(' / ')} }`;
227
+ return `{ ${this.choices.join('; ')} }`;
228
228
  }
229
229
  toSingleLineString() {
230
- return `{ ${this.choices.map((c) => c.toSingleLineString()).join(' / ')} }`;
231
- }
232
- }
233
- export class Router extends Part {
234
- constructor(choices) {
235
- super();
236
- this.choices = choices;
237
- }
238
- toString() {
239
- return `{ ${this.choices.join(' ; ')} }`;
240
- }
241
- toSingleLineString() {
242
- return `{ ${this.choices.map((c) => c.toSingleLineString()).join(' ; ')} }`;
230
+ return `{ ${this.choices.map((c) => c.toSingleLineString()).join('; ')} }`;
243
231
  }
244
232
  }
245
233
  export class Arg extends Part {
@@ -13,8 +13,7 @@ export declare enum T {
13
13
  ClosingBracket = "]",
14
14
  OpeningBrace = "{",
15
15
  ClosingBrace = "}",
16
- And = "&",
17
- Slash = "/",
16
+ And = "&&",
18
17
  Semicolon = ";",
19
18
  DoubleQuoteString = "double-quote string",
20
19
  UnclosedDoubleQuoteString = "unclosed double-quote string",
@@ -14,8 +14,7 @@ export var T;
14
14
  T["ClosingBracket"] = "]";
15
15
  T["OpeningBrace"] = "{";
16
16
  T["ClosingBrace"] = "}";
17
- T["And"] = "&";
18
- T["Slash"] = "/";
17
+ T["And"] = "&&";
19
18
  T["Semicolon"] = ";";
20
19
  T["DoubleQuoteString"] = "double-quote string";
21
20
  T["UnclosedDoubleQuoteString"] = "unclosed double-quote string";
@@ -46,8 +45,7 @@ const rules = [
46
45
  [true, /\]/y, T.ClosingBracket],
47
46
  [true, /\{/y, T.OpeningBrace],
48
47
  [true, /\}/y, T.ClosingBrace],
49
- [true, /\//y, T.Slash],
50
- [true, /&/y, T.And],
48
+ [true, /&&/y, T.And],
51
49
  [true, /;/y, T.Semicolon],
52
50
  [true, /!!/y, T.ErrorMark],
53
51
  [true, /=>/y, T.ResponseArrow],
@@ -69,6 +67,6 @@ const rules = [
69
67
  [true, /\$\{[^}\n]*/y, T.UnclosedVariable],
70
68
  [true, /\|(?: .*|(?=\n|$))/y, T.MultilineString],
71
69
  [true, /\|[^ \n]/y, T.InvalidMultilineStringMark],
72
- [true, /.+?(?=[\[\]"`|#{}&/;]|\$\{|$|=>|!!|:\s*$)/my, T.Words],
70
+ [true, /.+?(?=[\[\]"`|#{};]|\$\{|$|=>|!!|&&|\/\/|:\s*$)/my, T.Words],
73
71
  ];
74
72
  export default rules;
@@ -31,9 +31,7 @@ export const NEWLINES = list_sc(tok(T.Newline), nil()), WORDS = apply(tok(T.Word
31
31
  //list_sc(rep_sc(SIMPLE_PART), tok(T.And)),
32
32
  //(cs) => new Repeater(cs)
33
33
  //),
34
- SWITCH = apply(list_sc(SIMPLE_PART, tok(T.Slash)), (cs) => new Switch(cs)),
35
- //ROUTER = apply(list_sc(REPEATER, tok(T.Semicolon)), (cs) => new Router(cs)),
36
- BRACES = kmid(tok(T.OpeningBrace), SWITCH, tok(T.ClosingBrace)), PART = alt_sc(SIMPLE_PART, BRACES), PHRASE = rep_sc(PART), ARG = alt_sc(DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING), SET_VARIABLE = apply(seq(VARIABLE, ARG), ([variable, value]) => new SetVariable(variable, value)), ACTION = alt_sc(SET_VARIABLE, apply(PHRASE, (parts, range) => new Action(parts).at(range))), RESPONSE = apply(seq(PHRASE, opt_sc(VARIABLE)), ([parts, variable], range) => new Response(parts, variable !== undefined ? new SaveToVariable(variable) : undefined).at(range)), ERROR_RESPONSE = apply(seq(ERROR_MARK, opt_sc(alt_sc(DOUBLE_QUOTE_STRING, DOCSTRING))), ([, parts]) => new ErrorResponse(parts)), SAVE_TO_VARIABLE = apply(VARIABLE, (variable) => new Response([], new SaveToVariable(variable))), ARROW = kright(opt_sc(NEWLINES), tok(T.ResponseArrow)), RESPONSE_ITEM = kright(ARROW, alt_sc(SAVE_TO_VARIABLE, ERROR_RESPONSE, RESPONSE)), STEP = apply(seq(ACTION, rep_sc(RESPONSE_ITEM)), ([action, responses]) => new Step(action, responses).setFork(true)), LABEL = apply(kleft(list_sc(PART, nil()), tok(T.Colon)), (words, range) => new Label(words.map((w) => w.toString()).join(' ')).at(range)), SECTION = apply(LABEL, (text) => new Section(text)), BRANCH = apply(alt_sc(SECTION, STEP), (branch, range) => branch.at(range)), // section first, to make sure there is no colon after step
34
+ SWITCH = apply(list_sc(SIMPLE_PART, tok(T.Semicolon)), (cs) => new Switch(cs)), BRACES = kmid(tok(T.OpeningBrace), SWITCH, tok(T.ClosingBrace)), PART = alt_sc(SIMPLE_PART, BRACES), PHRASE = rep_sc(PART), ARG = alt_sc(DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING), SET_VARIABLE = apply(seq(VARIABLE, ARG), ([variable, value]) => new SetVariable(variable, value)), ACTION = alt_sc(SET_VARIABLE, apply(PHRASE, (parts, range) => new Action(parts).at(range))), RESPONSE = apply(seq(PHRASE, opt_sc(VARIABLE)), ([parts, variable], range) => new Response(parts, variable !== undefined ? new SaveToVariable(variable) : undefined).at(range)), ERROR_RESPONSE = apply(seq(ERROR_MARK, opt_sc(alt_sc(DOUBLE_QUOTE_STRING, DOCSTRING))), ([, parts]) => new ErrorResponse(parts)), SAVE_TO_VARIABLE = apply(VARIABLE, (variable) => new Response([], new SaveToVariable(variable))), ARROW = kright(opt_sc(NEWLINES), tok(T.ResponseArrow)), RESPONSE_ITEM = kright(ARROW, alt_sc(SAVE_TO_VARIABLE, ERROR_RESPONSE, RESPONSE)), STEP = apply(seq(ACTION, rep_sc(RESPONSE_ITEM)), ([action, responses]) => new Step(action, responses).setFork(true)), LABEL = apply(kleft(list_sc(PART, nil()), tok(T.Colon)), (words, range) => new Label(words.map((w) => w.toString()).join(' ')).at(range)), SECTION = apply(LABEL, (text) => new Section(text)), BRANCH = apply(alt_sc(SECTION, STEP), (branch, range) => branch.at(range)), // section first, to make sure there is no colon after step
37
35
  DENTS = apply(alt_sc(tok(T.Plus), tok(T.Minus)), (seqOrFork) => {
38
36
  return {
39
37
  dent: (seqOrFork.text.length - 2) / 2,
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.22.1",
4
+ "version": "0.23.1",
5
5
  "author": "Bernát Kalló",
6
6
  "homepage": "https://github.com/harmony-ac/code#readme",
7
7
  "repository": {