@poleski/quality-tools 0.2.0 → 0.2.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.
@@ -0,0 +1,170 @@
1
+ /* Generated by quality-tools acceptance generate. Do not edit. */
2
+ /* eslint-disable playwright/expect-expect */
3
+ import fs from 'node:fs';
4
+ import type { TestInfo } from '@playwright/test';
5
+
6
+ export interface AcceptanceIrStep {
7
+ keyword: string;
8
+ text: string;
9
+ line: number;
10
+ parameters: string[];
11
+ }
12
+
13
+ export interface AcceptanceIrExampleRow {
14
+ line: number;
15
+ values: Record<string, string>;
16
+ }
17
+
18
+ export interface AcceptanceIrScenario {
19
+ name: string;
20
+ line: number;
21
+ steps: AcceptanceIrStep[];
22
+ examples: AcceptanceIrExampleRow[];
23
+ }
24
+
25
+ export interface AcceptanceIrDocument {
26
+ schema_version: 1;
27
+ source_path: string;
28
+ feature: { name: string; line: number };
29
+ background?: { line: number; steps: AcceptanceIrStep[] };
30
+ scenarios: AcceptanceIrScenario[];
31
+ }
32
+
33
+ export type AcceptanceContext = { cleanup?: () => unknown | Promise<unknown> };
34
+
35
+ export interface AcceptanceRuntimeStep {
36
+ keyword: string;
37
+ text: string;
38
+ sourceText: string;
39
+ sourcePath: string;
40
+ line: number;
41
+ parameters: string[];
42
+ example: Record<string, string>;
43
+ }
44
+
45
+ export type AcceptanceStepImplementation = (
46
+ context: AcceptanceContext,
47
+ step: AcceptanceRuntimeStep
48
+ ) => unknown | Promise<unknown>;
49
+
50
+ export type AcceptanceStepRegistry = Record<string, AcceptanceStepImplementation>;
51
+
52
+ export interface AcceptanceBindings {
53
+ acceptanceSteps: unknown;
54
+ createAcceptanceContext: (input: {
55
+ testInfo: TestInfo;
56
+ sourcePath: string;
57
+ scenario: string;
58
+ example: Record<string, string>;
59
+ }) => unknown | Promise<unknown>;
60
+ }
61
+
62
+ type PlaywrightTest = {
63
+ (title: string, callback: (fixtures: object, testInfo: TestInfo) => Promise<void>): void;
64
+ describe(title: string, callback: () => void): void;
65
+ step(title: string, callback: () => Promise<void>): Promise<void>;
66
+ };
67
+
68
+ interface ScenarioExecution {
69
+ name: string;
70
+ scenario: AcceptanceIrScenario;
71
+ example: AcceptanceIrExampleRow;
72
+ steps: AcceptanceRuntimeStep[];
73
+ }
74
+
75
+ export function loadAcceptanceIr(filePath: string): AcceptanceIrDocument {
76
+ return JSON.parse(fs.readFileSync(filePath, 'utf8')) as AcceptanceIrDocument;
77
+ }
78
+
79
+ export function runAcceptanceFeature(
80
+ test: PlaywrightTest,
81
+ feature: AcceptanceIrDocument,
82
+ bindings: AcceptanceBindings
83
+ ): void {
84
+ test.describe(feature.feature.name, () => {
85
+ expandScenarioExecutions(feature).forEach((execution) => {
86
+ test(execution.name, async ({}, testInfo) => {
87
+ const context = await bindings.createAcceptanceContext({
88
+ testInfo,
89
+ sourcePath: feature.source_path,
90
+ scenario: execution.name,
91
+ example: execution.example.values
92
+ }) as AcceptanceContext;
93
+
94
+ try {
95
+ for (const step of execution.steps) {
96
+ await test.step(`${step.keyword} ${step.text}`, async () => {
97
+ await runAcceptanceStep(bindings.acceptanceSteps, context, step);
98
+ });
99
+ }
100
+ } finally {
101
+ await context.cleanup?.();
102
+ }
103
+ });
104
+ });
105
+ });
106
+ }
107
+
108
+ function expandScenarioExecutions(feature: AcceptanceIrDocument): ScenarioExecution[] {
109
+ return feature.scenarios.flatMap((scenario) => {
110
+ const examples = scenario.examples.length > 0
111
+ ? scenario.examples
112
+ : [{ line: scenario.line, values: {} }];
113
+
114
+ return examples.map((example) => ({
115
+ name: scenario.examples.length > 0
116
+ ? `${scenario.name} (${formatExampleName(example.values)})`
117
+ : scenario.name,
118
+ scenario,
119
+ example,
120
+ steps: [...(feature.background?.steps ?? []), ...scenario.steps].map((step) => ({
121
+ keyword: step.keyword,
122
+ text: renderStepText(step.text, example.values),
123
+ sourceText: step.text,
124
+ sourcePath: feature.source_path,
125
+ line: step.line,
126
+ parameters: step.parameters,
127
+ example: example.values
128
+ }))
129
+ }));
130
+ });
131
+ }
132
+
133
+ async function runAcceptanceStep(
134
+ registryInput: unknown,
135
+ context: AcceptanceContext,
136
+ step: AcceptanceRuntimeStep
137
+ ): Promise<void> {
138
+ const implementation = findAcceptanceStepImplementation(registryInput as AcceptanceStepRegistry, step);
139
+
140
+ if (!implementation) {
141
+ throw new Error(`Missing acceptance step "${step.keyword} ${step.text}" at ${step.sourcePath}:${step.line}`);
142
+ }
143
+
144
+ await implementation(context, step);
145
+ }
146
+
147
+ function findAcceptanceStepImplementation(
148
+ registry: AcceptanceStepRegistry,
149
+ step: AcceptanceRuntimeStep
150
+ ): AcceptanceStepImplementation | undefined {
151
+ return registry[step.text]
152
+ ?? registry[`${step.keyword} ${step.text}`]
153
+ ?? registry[step.sourceText]
154
+ ?? registry[`${step.keyword} ${step.sourceText}`];
155
+ }
156
+
157
+ function renderStepText(text: string, values: Record<string, string>): string {
158
+ return text.replace(/<([^>]+)>/g, (placeholder, key: string) => values[key] ?? placeholder);
159
+ }
160
+
161
+ function formatExampleName(values: Record<string, string>): string {
162
+ const explicitName = values.case ?? values.name ?? values.title ?? values.example;
163
+ if (explicitName) {
164
+ return explicitName;
165
+ }
166
+
167
+ return Object.entries(values)
168
+ .map(([key, value]) => `${key}: ${value}`)
169
+ .join(', ');
170
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poleski/quality-tools",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Portable TypeScript quality checks for project structure, complexity, mutation, and test health",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "typescript-eslint": "^8.0.0"
42
42
  },
43
43
  "scripts": {
44
- "build": "esbuild src/cli/main.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/cli/main.js",
44
+ "build": "esbuild src/cli/main.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/cli/main.js && node scripts/copy-acceptance-templates.mjs",
45
45
  "ci": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
46
46
  "release": "pnpm run ci && pnpm publish --no-git-checks",
47
47
  "changeset": "changeset",