@specsafe/test-gen 0.1.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/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +13 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +48 -0
- package/dist/parser.js.map +1 -0
- package/dist/typescript.d.ts +31 -0
- package/dist/typescript.d.ts.map +1 -0
- package/dist/typescript.js +80 -0
- package/dist/typescript.js.map +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agentic Engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# @specsafe/test-gen
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/npm/v/@specsafe/test-gen.svg" alt="npm version">
|
|
5
|
+
<img src="https://img.shields.io/npm/l/@specsafe/test-gen.svg" alt="license">
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
Test generation library for the SpecSafe spec-driven development framework. Parses specifications and generates TypeScript test files.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @specsafe/test-gen
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What It Provides
|
|
17
|
+
|
|
18
|
+
- **ScenarioParser** - Parse requirement scenarios from spec files
|
|
19
|
+
- **TypeScriptTestGenerator** - Generate TypeScript test files from parsed requirements
|
|
20
|
+
- **Framework Support** - Vitest (default) and Jest
|
|
21
|
+
|
|
22
|
+
## API Reference
|
|
23
|
+
|
|
24
|
+
### ScenarioParser
|
|
25
|
+
|
|
26
|
+
Parses specification files and extracts requirements.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { ScenarioParser } from '@specsafe/test-gen';
|
|
30
|
+
|
|
31
|
+
const parser = new ScenarioParser();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### `parseRequirements(specContent: string): Requirement[]`
|
|
35
|
+
|
|
36
|
+
Parse requirements from spec content.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const specContent = `
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
### REQ-001: User Login
|
|
43
|
+
**Given** a registered user with email "user@example.com"
|
|
44
|
+
**When** they enter the correct password
|
|
45
|
+
**Then** they should be redirected to the dashboard
|
|
46
|
+
|
|
47
|
+
### REQ-002: Invalid Password
|
|
48
|
+
**Given** a registered user
|
|
49
|
+
**When** they enter an incorrect password
|
|
50
|
+
**Then** an error message should be displayed
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const requirements = parser.parseRequirements(specContent);
|
|
54
|
+
console.log(requirements);
|
|
55
|
+
// [
|
|
56
|
+
// {
|
|
57
|
+
// id: 'REQ-001',
|
|
58
|
+
// title: 'User Login',
|
|
59
|
+
// given: 'a registered user with email "user@example.com"',
|
|
60
|
+
// when: 'they enter the correct password',
|
|
61
|
+
// then: 'they should be redirected to the dashboard'
|
|
62
|
+
// },
|
|
63
|
+
// ...
|
|
64
|
+
// ]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### TypeScriptTestGenerator
|
|
70
|
+
|
|
71
|
+
Generates TypeScript test files from requirements.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { TypeScriptTestGenerator } from '@specsafe/test-gen';
|
|
75
|
+
|
|
76
|
+
const generator = new TypeScriptTestGenerator({
|
|
77
|
+
framework: 'vitest', // or 'jest'
|
|
78
|
+
specId: 'user-authentication'
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### `generate(requirements: Requirement[]): string`
|
|
83
|
+
|
|
84
|
+
Generate test file content.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const requirements = [
|
|
88
|
+
{
|
|
89
|
+
id: 'REQ-001',
|
|
90
|
+
title: 'User Login',
|
|
91
|
+
given: 'a registered user',
|
|
92
|
+
when: 'they enter valid credentials',
|
|
93
|
+
then: 'they should be logged in'
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const testCode = generator.generate(requirements);
|
|
98
|
+
console.log(testCode);
|
|
99
|
+
// Output:
|
|
100
|
+
// import { describe, it, expect } from 'vitest';
|
|
101
|
+
//
|
|
102
|
+
// describe('User Authentication', () => {
|
|
103
|
+
// it('REQ-001: User Login', () => {
|
|
104
|
+
// // Given: a registered user
|
|
105
|
+
// // When: they enter valid credentials
|
|
106
|
+
// // Then: they should be logged in
|
|
107
|
+
// expect(true).toBe(true);
|
|
108
|
+
// });
|
|
109
|
+
// });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Framework Support
|
|
115
|
+
|
|
116
|
+
### Vitest (Default)
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const generator = new TypeScriptTestGenerator({
|
|
120
|
+
framework: 'vitest',
|
|
121
|
+
specId: 'my-feature'
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Generates tests using Vitest's `describe`, `it`, and `expect`:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { describe, it, expect } from 'vitest';
|
|
129
|
+
|
|
130
|
+
describe('My Feature', () => {
|
|
131
|
+
it('REQ-001: Requirement Title', () => {
|
|
132
|
+
// Test implementation
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Jest
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const generator = new TypeScriptTestGenerator({
|
|
141
|
+
framework: 'jest',
|
|
142
|
+
specId: 'my-feature'
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Generates tests using Jest's globals:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
describe('My Feature', () => {
|
|
150
|
+
it('REQ-001: Requirement Title', () => {
|
|
151
|
+
// Test implementation
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Usage Examples
|
|
159
|
+
|
|
160
|
+
### Basic Usage
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { ScenarioParser, TypeScriptTestGenerator } from '@specsafe/test-gen';
|
|
164
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
165
|
+
|
|
166
|
+
// Read spec file
|
|
167
|
+
const specContent = readFileSync('./specs/active/login.spec.md', 'utf-8');
|
|
168
|
+
|
|
169
|
+
// Parse requirements
|
|
170
|
+
const parser = new ScenarioParser();
|
|
171
|
+
const requirements = parser.parseRequirements(specContent);
|
|
172
|
+
|
|
173
|
+
// Generate tests
|
|
174
|
+
const generator = new TypeScriptTestGenerator({
|
|
175
|
+
framework: 'vitest',
|
|
176
|
+
specId: 'login'
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const testCode = generator.generate(requirements);
|
|
180
|
+
|
|
181
|
+
// Write to file
|
|
182
|
+
writeFileSync('./src/specs/login.spec.ts', testCode);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Custom Test Body
|
|
186
|
+
|
|
187
|
+
The generated tests include comments based on the Gherkin-style requirements:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Given: a registered user with valid credentials
|
|
191
|
+
// When: they submit the login form
|
|
192
|
+
// Then: they should be authenticated
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
You fill in the actual test implementation:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
it('REQ-001: User Login', () => {
|
|
199
|
+
// Given: a registered user with valid credentials
|
|
200
|
+
const user = createUser({ email: 'test@example.com', password: 'secret' });
|
|
201
|
+
|
|
202
|
+
// When: they submit the login form
|
|
203
|
+
const result = login(user.email, user.password);
|
|
204
|
+
|
|
205
|
+
// Then: they should be authenticated
|
|
206
|
+
expect(result.authenticated).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Processing Multiple Specs
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { ScenarioParser, TypeScriptTestGenerator } from '@specsafe/test-gen';
|
|
214
|
+
import { readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
215
|
+
import { join } from 'path';
|
|
216
|
+
|
|
217
|
+
const parser = new ScenarioParser();
|
|
218
|
+
const specsDir = './specs/active';
|
|
219
|
+
const outputDir = './src/specs';
|
|
220
|
+
|
|
221
|
+
const specFiles = readdirSync(specsDir).filter(f => f.endsWith('.spec.md'));
|
|
222
|
+
|
|
223
|
+
for (const file of specFiles) {
|
|
224
|
+
const specId = file.replace('.spec.md', '');
|
|
225
|
+
const content = readFileSync(join(specsDir, file), 'utf-8');
|
|
226
|
+
|
|
227
|
+
const requirements = parser.parseRequirements(content);
|
|
228
|
+
|
|
229
|
+
const generator = new TypeScriptTestGenerator({
|
|
230
|
+
framework: 'vitest',
|
|
231
|
+
specId
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const testCode = generator.generate(requirements);
|
|
235
|
+
writeFileSync(join(outputDir, `${specId}.spec.ts`), testCode);
|
|
236
|
+
|
|
237
|
+
console.log(`Generated tests for ${specId}`);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Specification Format
|
|
244
|
+
|
|
245
|
+
Requirements should follow this format in your spec files:
|
|
246
|
+
|
|
247
|
+
```markdown
|
|
248
|
+
## Requirements
|
|
249
|
+
|
|
250
|
+
### REQ-XXX: Requirement Title
|
|
251
|
+
**Given** [context]
|
|
252
|
+
**When** [action]
|
|
253
|
+
**Then** [expected result]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Where:
|
|
257
|
+
- `REQ-XXX` is a unique requirement ID (e.g., REQ-001, REQ-002)
|
|
258
|
+
- **Given** describes the initial context/state
|
|
259
|
+
- **When** describes the action taken
|
|
260
|
+
- **Then** describes the expected outcome
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT © Agentic Engineering
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario Parser
|
|
3
|
+
* Extracts scenarios from spec markdown files
|
|
4
|
+
*/
|
|
5
|
+
import type { Requirement } from '@specsafe/core';
|
|
6
|
+
export declare class ScenarioParser {
|
|
7
|
+
/**
|
|
8
|
+
* Parse requirements from spec text
|
|
9
|
+
*/
|
|
10
|
+
parseRequirements(text: string): Requirement[];
|
|
11
|
+
private parseScenarios;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,gBAAgB,CAAC;AAE5D,qBAAa,cAAc;IACzB;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE;IAsB9C,OAAO,CAAC,cAAc;CAyBvB"}
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario Parser
|
|
3
|
+
* Extracts scenarios from spec markdown files
|
|
4
|
+
*/
|
|
5
|
+
export class ScenarioParser {
|
|
6
|
+
/**
|
|
7
|
+
* Parse requirements from spec text
|
|
8
|
+
*/
|
|
9
|
+
parseRequirements(text) {
|
|
10
|
+
// Simple parsing - could be enhanced with proper markdown parser
|
|
11
|
+
const requirements = [];
|
|
12
|
+
// Find requirement sections
|
|
13
|
+
const requirementMatches = text.matchAll(/###\s+Requirement:\s*(.+)/g);
|
|
14
|
+
for (const match of requirementMatches) {
|
|
15
|
+
const reqText = match[1].trim();
|
|
16
|
+
const scenarios = this.parseScenarios(text, match.index || 0);
|
|
17
|
+
requirements.push({
|
|
18
|
+
id: `REQ-${requirements.length + 1}`,
|
|
19
|
+
text: reqText,
|
|
20
|
+
priority: 'P1',
|
|
21
|
+
scenarios
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return requirements;
|
|
25
|
+
}
|
|
26
|
+
parseScenarios(text, startIndex) {
|
|
27
|
+
const scenarios = [];
|
|
28
|
+
// Find scenario table after requirement, bounded by next requirement or end of text
|
|
29
|
+
const nextReqMatch = text.slice(startIndex + 1).search(/###\s+Requirement:/);
|
|
30
|
+
const endIndex = nextReqMatch !== -1 ? startIndex + 1 + nextReqMatch : text.length;
|
|
31
|
+
const section = text.slice(startIndex, endIndex);
|
|
32
|
+
const scenarioMatches = section.matchAll(/\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g);
|
|
33
|
+
let index = 0;
|
|
34
|
+
for (const match of scenarioMatches) {
|
|
35
|
+
const [, given, when, then] = match;
|
|
36
|
+
if (given && when && then && given.trim() !== 'GIVEN') {
|
|
37
|
+
scenarios.push({
|
|
38
|
+
id: `SC-${index++}`,
|
|
39
|
+
given: given.trim(),
|
|
40
|
+
when: when.trim(),
|
|
41
|
+
then: then.trim()
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return scenarios;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,OAAO,cAAc;IACzB;;OAEG;IACH,iBAAiB,CAAC,IAAY;QAC5B,iEAAiE;QACjE,MAAM,YAAY,GAAkB,EAAE,CAAC;QAEvC,4BAA4B;QAC5B,MAAM,kBAAkB,GAAG,IAAI,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC;QAEvE,KAAK,MAAM,KAAK,IAAI,kBAAkB,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAChC,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;YAE9D,YAAY,CAAC,IAAI,CAAC;gBAChB,EAAE,EAAE,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;gBACpC,IAAI,EAAE,OAAO;gBACb,QAAQ,EAAE,IAAI;gBACd,SAAS;aACV,CAAC,CAAC;QACL,CAAC;QAED,OAAO,YAAY,CAAC;IACtB,CAAC;IAEO,cAAc,CAAC,IAAY,EAAE,UAAkB;QACrD,MAAM,SAAS,GAAe,EAAE,CAAC;QAEjC,oFAAoF;QACpF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC7E,MAAM,QAAQ,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;QACnF,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAEjD,MAAM,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,kDAAkD,CAAC,CAAC;QAE7F,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;YACpC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC;YACpC,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;gBACtD,SAAS,CAAC,IAAI,CAAC;oBACb,EAAE,EAAE,MAAM,KAAK,EAAE,EAAE;oBACnB,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;oBACnB,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;oBACjB,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;iBAClB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript/Vitest Test Generator
|
|
3
|
+
* Converts spec scenarios to Vitest test files
|
|
4
|
+
*/
|
|
5
|
+
import type { Spec, Scenario } from '@specsafe/core';
|
|
6
|
+
export interface TestGenerationOptions {
|
|
7
|
+
framework: 'vitest' | 'jest';
|
|
8
|
+
includeComments: boolean;
|
|
9
|
+
generatePlaceholders: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class TypeScriptTestGenerator {
|
|
12
|
+
private options;
|
|
13
|
+
constructor(options?: Partial<TestGenerationOptions>);
|
|
14
|
+
/**
|
|
15
|
+
* Generate test file content from spec
|
|
16
|
+
*/
|
|
17
|
+
generate(spec: Spec): string;
|
|
18
|
+
private generateImports;
|
|
19
|
+
private generateDescribe;
|
|
20
|
+
private generateTest;
|
|
21
|
+
private scenarioToTestName;
|
|
22
|
+
/**
|
|
23
|
+
* Escape special characters in a string for use in single-quoted JS strings
|
|
24
|
+
*/
|
|
25
|
+
private escapeString;
|
|
26
|
+
/**
|
|
27
|
+
* Parse scenarios from spec markdown content
|
|
28
|
+
*/
|
|
29
|
+
parseScenarios(content: string): Scenario[];
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=typescript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typescript.d.ts","sourceRoot":"","sources":["../src/typescript.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,OAAO,CAAwB;gBAE3B,OAAO,GAAE,OAAO,CAAC,qBAAqB,CAAM;IASxD;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAO5B,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE;CAkB5C"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript/Vitest Test Generator
|
|
3
|
+
* Converts spec scenarios to Vitest test files
|
|
4
|
+
*/
|
|
5
|
+
export class TypeScriptTestGenerator {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = {
|
|
9
|
+
framework: 'vitest',
|
|
10
|
+
includeComments: true,
|
|
11
|
+
generatePlaceholders: true,
|
|
12
|
+
...options
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate test file content from spec
|
|
17
|
+
*/
|
|
18
|
+
generate(spec) {
|
|
19
|
+
const imports = this.generateImports();
|
|
20
|
+
const describe = this.generateDescribe(spec);
|
|
21
|
+
return `${imports}\n\n${describe}`;
|
|
22
|
+
}
|
|
23
|
+
generateImports() {
|
|
24
|
+
if (this.options.framework === 'vitest') {
|
|
25
|
+
return "import { describe, it, expect } from 'vitest';";
|
|
26
|
+
}
|
|
27
|
+
return "import { describe, it, expect } from '@jest/globals';";
|
|
28
|
+
}
|
|
29
|
+
generateDescribe(spec) {
|
|
30
|
+
const tests = spec.requirements
|
|
31
|
+
.flatMap(r => r.scenarios)
|
|
32
|
+
.map(s => this.generateTest(s))
|
|
33
|
+
.join('\n\n');
|
|
34
|
+
return `describe('${this.escapeString(spec.name)}', () => {\n${tests}\n});`;
|
|
35
|
+
}
|
|
36
|
+
generateTest(scenario) {
|
|
37
|
+
const testName = this.escapeString(this.scenarioToTestName(scenario));
|
|
38
|
+
const comments = this.options.includeComments
|
|
39
|
+
? ` // GIVEN: ${scenario.given}\n // WHEN: ${scenario.when}\n // THEN: ${scenario.then}\n`
|
|
40
|
+
: '';
|
|
41
|
+
const body = this.options.generatePlaceholders
|
|
42
|
+
? ` expect(true).toBe(true); // TODO: Implement test`
|
|
43
|
+
: '';
|
|
44
|
+
return `${comments} it('${testName}', () => {\n${body}\n });`;
|
|
45
|
+
}
|
|
46
|
+
scenarioToTestName(scenario) {
|
|
47
|
+
// Convert scenario to readable test name, preserving original case
|
|
48
|
+
return scenario.then
|
|
49
|
+
.replace(/^then\s+/i, '')
|
|
50
|
+
.replace(/\.$/, '');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Escape special characters in a string for use in single-quoted JS strings
|
|
54
|
+
*/
|
|
55
|
+
escapeString(str) {
|
|
56
|
+
return str
|
|
57
|
+
.replace(/\\/g, '\\\\')
|
|
58
|
+
.replace(/'/g, "\\'")
|
|
59
|
+
.replace(/\n/g, '\\n');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse scenarios from spec markdown content
|
|
63
|
+
*/
|
|
64
|
+
parseScenarios(content) {
|
|
65
|
+
const scenarios = [];
|
|
66
|
+
// Match GIVEN/WHEN/THEN patterns in the content
|
|
67
|
+
const scenarioRegex = /(?:GIVEN|Given)\s*:\s*(.+?)(?:\n|\r)(?:WHEN|When)\s*:\s*(.+?)(?:\n|\r)(?:THEN|Then)\s*:\s*(.+?)(?:\n|\r|$)/gi;
|
|
68
|
+
let match;
|
|
69
|
+
while ((match = scenarioRegex.exec(content)) !== null) {
|
|
70
|
+
scenarios.push({
|
|
71
|
+
id: `SC-${scenarios.length + 1}`,
|
|
72
|
+
given: match[1].trim(),
|
|
73
|
+
when: match[2].trim(),
|
|
74
|
+
then: match[3].trim()
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return scenarios;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=typescript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typescript.js","sourceRoot":"","sources":["../src/typescript.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@specsafe/test-gen",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Test generators for SpecSafe",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@specsafe/core": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"typescript": "^5.3.0",
|
|
31
|
+
"vitest": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"dev": "tsc --watch",
|
|
36
|
+
"test": "vitest --passWithNoTests",
|
|
37
|
+
"typecheck": "tsc --noEmit"
|
|
38
|
+
}
|
|
39
|
+
}
|