@sungen/driver-ui 3.2.32 → 3.2.34
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sungen/driver-ui",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.34",
|
|
4
4
|
"description": "Sungen UI capability (Playwright) — step patterns, viewpoint gate, and phase hooks. Plugs into @sun-asterisk/sungen via the capability SPI.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"author": "eqe team (engineer & quality) — Sun Asterisk",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@sun-asterisk/sungen": "3.2.
|
|
16
|
+
"@sun-asterisk/sungen": "3.2.34"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"@playwright/test": "^1.57.0"
|
|
@@ -65,11 +65,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
65
65
|
resolver: (step, context): StepTemplateData => {
|
|
66
66
|
let dataValue = step.selectorRef || '';
|
|
67
67
|
if (step.dataRef) {
|
|
68
|
-
|
|
69
|
-
dataValue = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
70
|
-
} catch {
|
|
71
|
-
dataValue = `\${${step.dataRef}}`;
|
|
72
|
-
}
|
|
68
|
+
dataValue = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
73
69
|
}
|
|
74
70
|
return {
|
|
75
71
|
templateName: 'alert-text-assertion',
|
|
@@ -92,11 +88,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
92
88
|
(step.elementType === 'column' || step.elementType === 'columnheader'),
|
|
93
89
|
resolver: (step, context): StepTemplateData => {
|
|
94
90
|
let dataValue: string;
|
|
95
|
-
|
|
96
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
97
|
-
} catch (error) {
|
|
98
|
-
dataValue = `\${${step.dataRef}}`;
|
|
99
|
-
}
|
|
91
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
100
92
|
|
|
101
93
|
// getByRole('row') includes header row as nth(0), so Gherkin nth=1 maps to locator nth(1)
|
|
102
94
|
const rowNth = step.nth || 1;
|
|
@@ -138,6 +130,8 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
138
130
|
// step is a URL-only oracle, which a client-side router can satisfy for a few hundred
|
|
139
131
|
// milliseconds before a guard bounces the user away (#662).
|
|
140
132
|
let anchor: Record<string, unknown> | undefined;
|
|
133
|
+
// The page entry's `query: any` (#748) — set below, once the entry is known to BE a page.
|
|
134
|
+
let ignoreQuery = false;
|
|
141
135
|
|
|
142
136
|
// If selector is present, extract path from selector's value attribute
|
|
143
137
|
if (step.selectorRef) {
|
|
@@ -155,6 +149,10 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
155
149
|
// the feature path rather than compiling `toHaveURL(/button/)`.
|
|
156
150
|
path = resolved.selectorType === 'page' ? (resolved.value || fallbackPath) : fallbackPath;
|
|
157
151
|
if (resolved.selectorType === 'page' && resolved.anchor) anchor = { ...resolved.anchor };
|
|
152
|
+
// `query: any` — the query is not part of this page's identity (#748). Gated on the entry
|
|
153
|
+
// KIND for the same reason `value` and `anchor` are: a key collision can hand back a
|
|
154
|
+
// non-page entry, and reading a stray field off it would silently weaken the assertion.
|
|
155
|
+
if (resolved.selectorType === 'page' && resolved.query === 'any') ignoreQuery = true;
|
|
158
156
|
} catch (error) {
|
|
159
157
|
// Fallback to feature path if selector not found
|
|
160
158
|
path = fallbackPath;
|
|
@@ -167,11 +165,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
167
165
|
// has asserted it since issue #464; this is the same assertion, so it asserts the same way.
|
|
168
166
|
let query = '';
|
|
169
167
|
if (step.dataRef) {
|
|
170
|
-
|
|
171
|
-
query = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
172
|
-
} catch {
|
|
173
|
-
query = `\${${step.dataRef}}`;
|
|
174
|
-
}
|
|
168
|
+
query = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
175
169
|
}
|
|
176
170
|
// Convert path to regex-safe string (dynamic :id segments → [^/]+ wildcard). An ABSOLUTE
|
|
177
171
|
// page value also yields a host pattern: matching only the path let this assertion pass on
|
|
@@ -183,7 +177,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
183
177
|
templateName: 'page-assertion',
|
|
184
178
|
// `dataRef` rides along so a runtime failure can name the test-data key an author must go
|
|
185
179
|
// fix — an empty/absent value is otherwise the hardest kind of red to trace.
|
|
186
|
-
data: { pathRegex, hostRegex, query, dataRef: step.dataRef || '', anchor },
|
|
180
|
+
data: { pathRegex, hostRegex, query, dataRef: step.dataRef || '', anchor, ignoreQuery },
|
|
187
181
|
comment: step.selectorRef ? `Assert on ${step.selectorRef} page${withSuffix}` : `Assert on page${withSuffix}`,
|
|
188
182
|
};
|
|
189
183
|
},
|
|
@@ -211,11 +205,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
211
205
|
} catch (error) {}
|
|
212
206
|
|
|
213
207
|
let dataValue: string;
|
|
214
|
-
|
|
215
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
dataValue = `\${${step.dataRef}}`;
|
|
218
|
-
}
|
|
208
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
219
209
|
|
|
220
210
|
const escapedVariable = dataValue.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
|
|
221
211
|
|
|
@@ -284,11 +274,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
284
274
|
} catch (error) {}
|
|
285
275
|
|
|
286
276
|
let dataValue: string;
|
|
287
|
-
|
|
288
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
dataValue = `\${${step.dataRef}}`;
|
|
291
|
-
}
|
|
277
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
292
278
|
|
|
293
279
|
const escapedVariable = dataValue.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
|
|
294
280
|
|
|
@@ -359,11 +345,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
359
345
|
} catch (error) {}
|
|
360
346
|
|
|
361
347
|
let dataValue: string;
|
|
362
|
-
|
|
363
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
364
|
-
} catch (error) {
|
|
365
|
-
dataValue = `\${${step.dataRef}}`;
|
|
366
|
-
}
|
|
348
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
367
349
|
|
|
368
350
|
// "selected" mirrors selected-assertion: checkbox/radio/switch roles have no
|
|
369
351
|
// aria-selected — their state is `checked`; other targets probe at runtime.
|
|
@@ -419,11 +401,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
419
401
|
|
|
420
402
|
// Resolve data variable value
|
|
421
403
|
let dataValue: string;
|
|
422
|
-
|
|
423
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
424
|
-
} catch (error) {
|
|
425
|
-
dataValue = `\${${step.dataRef}}`;
|
|
426
|
-
}
|
|
404
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
427
405
|
|
|
428
406
|
// Resolve selector from YAML with feature context
|
|
429
407
|
let resolved: any = {};
|
|
@@ -535,9 +513,15 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
535
513
|
const label = resolved.value !== undefined && resolved.value.trim() === ''
|
|
536
514
|
? undefined
|
|
537
515
|
: step.selectorRef;
|
|
516
|
+
// The locator here is built from the CAPTION, not from `resolved` — the common case has no
|
|
517
|
+
// selectors.yaml entry at all, so `resolved` is `{}` and carries nothing. The scope
|
|
518
|
+
// therefore comes off the context, which `withParentScope` sets whether or not the entry
|
|
519
|
+
// lookup succeeded; without it `… in [Education Card 6] card` was dropped silently and the
|
|
520
|
+
// assertion read whichever card the page happened to render first (#747).
|
|
538
521
|
const code = context.templateEngine.renderStep('label-value-assertion', {
|
|
539
522
|
label,
|
|
540
523
|
dataValue,
|
|
524
|
+
parentLocator: context.parentLocator,
|
|
541
525
|
});
|
|
542
526
|
return {
|
|
543
527
|
code,
|
|
@@ -564,6 +548,9 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
564
548
|
name: hasName ? resolved.name : dataValue,
|
|
565
549
|
exact: resolved.exact || false,
|
|
566
550
|
nth: resolved.nth,
|
|
551
|
+
// Rebuilt field by field rather than spread from `resolved`, so the scope `withParentScope`
|
|
552
|
+
// stamped there never arrives — the label branch's twin defect (#747).
|
|
553
|
+
parentLocator: resolved.parentLocator ?? context.parentLocator,
|
|
567
554
|
});
|
|
568
555
|
return {
|
|
569
556
|
code,
|
|
@@ -865,11 +852,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
865
852
|
resolver: (step, context): StepTemplateData => {
|
|
866
853
|
// Resolve data reference to actual value
|
|
867
854
|
let dataValue: string;
|
|
868
|
-
|
|
869
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
870
|
-
} catch (error) {
|
|
871
|
-
dataValue = `\${${step.dataRef}}`;
|
|
872
|
-
}
|
|
855
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
873
856
|
|
|
874
857
|
return {
|
|
875
858
|
templateName: 'visible-assertion',
|
|
@@ -895,13 +878,9 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
895
878
|
if (match) {
|
|
896
879
|
expectedCount = parseInt(match[1]);
|
|
897
880
|
} else if (step.dataRef) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
expectedCount = isNaN(parsed) ? 1 : parsed;
|
|
902
|
-
} catch {
|
|
903
|
-
expectedCount = `\${${step.dataRef}}`;
|
|
904
|
-
}
|
|
881
|
+
const resolvedData = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
882
|
+
const parsed = parseInt(resolvedData);
|
|
883
|
+
expectedCount = isNaN(parsed) ? 1 : parsed;
|
|
905
884
|
}
|
|
906
885
|
|
|
907
886
|
const resolved = context.selectorResolver.resolveSelector(step.selectorRef!, undefined, step.elementType, step.nth);
|
|
@@ -926,13 +905,9 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
926
905
|
expectedCount = parseInt(match[1]);
|
|
927
906
|
} else if (step.dataRef) {
|
|
928
907
|
// Resolve data reference: "count {{number_items}}"
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
expectedCount = isNaN(parsed) ? 1 : parsed;
|
|
933
|
-
} catch {
|
|
934
|
-
expectedCount = `\${${step.dataRef}}`;
|
|
935
|
-
}
|
|
908
|
+
const resolvedData = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
909
|
+
const parsed = parseInt(resolvedData);
|
|
910
|
+
expectedCount = isNaN(parsed) ? 1 : parsed;
|
|
936
911
|
}
|
|
937
912
|
|
|
938
913
|
const resolved = context.selectorResolver.resolveSelector(step.selectorRef!, undefined, step.elementType, step.nth);
|
|
@@ -20,11 +20,7 @@ export const formPatterns: StepPattern[] = [
|
|
|
20
20
|
|
|
21
21
|
let fileName: string;
|
|
22
22
|
if (step.dataRef) {
|
|
23
|
-
|
|
24
|
-
fileName = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
fileName = `\${${step.dataRef}}`;
|
|
27
|
-
}
|
|
23
|
+
fileName = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
28
24
|
} else {
|
|
29
25
|
fileName = step.value!;
|
|
30
26
|
}
|
|
@@ -46,11 +42,7 @@ export const formPatterns: StepPattern[] = [
|
|
|
46
42
|
|
|
47
43
|
let value: string;
|
|
48
44
|
if (step.dataRef) {
|
|
49
|
-
|
|
50
|
-
value = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
value = `\${${step.dataRef}}`;
|
|
53
|
-
}
|
|
45
|
+
value = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
54
46
|
} else {
|
|
55
47
|
value = step.value!;
|
|
56
48
|
}
|
|
@@ -169,11 +161,7 @@ export const formPatterns: StepPattern[] = [
|
|
|
169
161
|
|
|
170
162
|
let value: string;
|
|
171
163
|
if (step.dataRef) {
|
|
172
|
-
|
|
173
|
-
value = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
174
|
-
} catch (error) {
|
|
175
|
-
value = `\${${step.dataRef}}`;
|
|
176
|
-
}
|
|
164
|
+
value = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
177
165
|
} else {
|
|
178
166
|
value = step.value!;
|
|
179
167
|
}
|
|
@@ -163,11 +163,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
163
163
|
!!step.dataRef,
|
|
164
164
|
resolver: (step, context) => {
|
|
165
165
|
let fillValue: string;
|
|
166
|
-
|
|
167
|
-
fillValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
168
|
-
} catch {
|
|
169
|
-
fillValue = `\${${step.dataRef}}`;
|
|
170
|
-
}
|
|
166
|
+
fillValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
171
167
|
return {
|
|
172
168
|
templateName: 'alert-fill-action',
|
|
173
169
|
data: { fillValue },
|
|
@@ -221,11 +217,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
221
217
|
if (step.value) {
|
|
222
218
|
dataValue = step.value;
|
|
223
219
|
} else {
|
|
224
|
-
|
|
225
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
226
|
-
} catch {
|
|
227
|
-
dataValue = `\${${step.dataRef}}`;
|
|
228
|
-
}
|
|
220
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
229
221
|
}
|
|
230
222
|
const { name: _name, ...resolvedWithoutName } = resolved;
|
|
231
223
|
|
|
@@ -261,11 +253,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
261
253
|
}
|
|
262
254
|
|
|
263
255
|
let dataValue: string;
|
|
264
|
-
|
|
265
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
266
|
-
} catch (error) {
|
|
267
|
-
dataValue = `\${${step.dataRef}}`;
|
|
268
|
-
}
|
|
256
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
269
257
|
|
|
270
258
|
let actionMethod = 'click';
|
|
271
259
|
if (step.text.includes('hover')) actionMethod = 'hover';
|
|
@@ -341,11 +329,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
341
329
|
if (step.value) {
|
|
342
330
|
dataValue = step.value;
|
|
343
331
|
} else {
|
|
344
|
-
|
|
345
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
346
|
-
} catch {
|
|
347
|
-
dataValue = `\${${step.dataRef}}`;
|
|
348
|
-
}
|
|
332
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
349
333
|
}
|
|
350
334
|
|
|
351
335
|
const { name: _name, ...resolvedWithoutName } = resolved;
|
|
@@ -412,11 +396,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
412
396
|
if (step.value) {
|
|
413
397
|
dataValue = step.value;
|
|
414
398
|
} else {
|
|
415
|
-
|
|
416
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
417
|
-
} catch {
|
|
418
|
-
dataValue = `\${${step.dataRef}}`;
|
|
419
|
-
}
|
|
399
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
420
400
|
}
|
|
421
401
|
|
|
422
402
|
const { name: _name, ...resolvedWithoutName } = resolved;
|
|
@@ -453,11 +433,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
453
433
|
(step.text.includes('clicks') || step.text.includes('click')) && !step.selectorRef && !!step.dataRef,
|
|
454
434
|
resolver: (step, context) => {
|
|
455
435
|
let value: string;
|
|
456
|
-
|
|
457
|
-
value = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
458
|
-
} catch (error) {
|
|
459
|
-
value = `\${${step.dataRef}}`;
|
|
460
|
-
}
|
|
436
|
+
value = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
461
437
|
|
|
462
438
|
return {
|
|
463
439
|
templateName: 'click-action',
|
|
@@ -503,11 +479,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
503
479
|
const role = elementKeywordToRole(step.text)!;
|
|
504
480
|
|
|
505
481
|
let dataValue: string;
|
|
506
|
-
|
|
507
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
508
|
-
} catch (error) {
|
|
509
|
-
dataValue = `\${${step.dataRef}}`;
|
|
510
|
-
}
|
|
482
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
511
483
|
|
|
512
484
|
const state = extractWaitState(step.text);
|
|
513
485
|
|
|
@@ -597,11 +569,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
597
569
|
const resolved = context.selectorResolver.resolveSelector(step.selectorRef!, undefined, step.elementType, step.nth);
|
|
598
570
|
|
|
599
571
|
let dataValue: string;
|
|
600
|
-
|
|
601
|
-
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
602
|
-
} catch (error) {
|
|
603
|
-
dataValue = `\${${step.dataRef}}`;
|
|
604
|
-
}
|
|
572
|
+
dataValue = context.dataResolver.resolveData(step.dataRef!, context.featureName);
|
|
605
573
|
|
|
606
574
|
const state = extractWaitState(step.text);
|
|
607
575
|
|
|
@@ -44,6 +44,10 @@ export const navigationPatterns: StepPattern[] = [
|
|
|
44
44
|
? `/${step.selectorRef}/`
|
|
45
45
|
: (context.featurePath || '/');
|
|
46
46
|
let path = pathFallback;
|
|
47
|
+
// The page entry's `query: any` (#748) — the twin of the `see [X] page` reading. These two
|
|
48
|
+
// resolvers have drifted apart twice; a rule one honours and the other does not is exactly
|
|
49
|
+
// that drift, so it is read here on the same line as the path.
|
|
50
|
+
let ignoreQuery = false;
|
|
47
51
|
|
|
48
52
|
if (step.selectorRef) {
|
|
49
53
|
try {
|
|
@@ -57,6 +61,7 @@ export const navigationPatterns: StepPattern[] = [
|
|
|
57
61
|
// documented fallback. These two resolvers have drifted apart twice already; keep them
|
|
58
62
|
// spelling the same rule.
|
|
59
63
|
path = resolved.selectorType === 'page' ? (resolved.value || pathFallback) : pathFallback;
|
|
64
|
+
if (resolved.selectorType === 'page' && resolved.query === 'any') ignoreQuery = true;
|
|
60
65
|
} catch (error) {
|
|
61
66
|
path = pathFallback;
|
|
62
67
|
}
|
|
@@ -71,11 +76,7 @@ export const navigationPatterns: StepPattern[] = [
|
|
|
71
76
|
// Runtime mode resolves to a __SUNGEN_TD$…$__ marker → `path + testData.get(...)`.
|
|
72
77
|
let query = '';
|
|
73
78
|
if (step.dataRef) {
|
|
74
|
-
|
|
75
|
-
query = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
76
|
-
} catch {
|
|
77
|
-
query = `\${${step.dataRef}}`;
|
|
78
|
-
}
|
|
79
|
+
query = context.dataResolver.resolveData(step.dataRef, context.featureName);
|
|
79
80
|
}
|
|
80
81
|
const withSuffix = step.dataRef ? ` with ${step.dataRef}` : '';
|
|
81
82
|
|
|
@@ -91,7 +92,7 @@ export const navigationPatterns: StepPattern[] = [
|
|
|
91
92
|
templateName: 'route-assertion',
|
|
92
93
|
// `dataRef` rides along so a runtime failure names the test-data key (see the `see` twin).
|
|
93
94
|
// An absolute page value also carries a host pattern — see urlToPatternParts.
|
|
94
|
-
data: { ...urlToPatternParts(path), query, dataRef: step.dataRef || '' },
|
|
95
|
+
data: { ...urlToPatternParts(path), query, dataRef: step.dataRef || '', ignoreQuery },
|
|
95
96
|
comment: step.selectorRef ? `Assert on ${step.selectorRef} page${withSuffix}` : `Assert page route`,
|
|
96
97
|
};
|
|
97
98
|
}
|
package/src/utils/row-index.ts
CHANGED
|
@@ -24,12 +24,11 @@ export function rowIndexExpr(
|
|
|
24
24
|
const varMatch = rowToken.match(/^\{\{([^}]+)\}\}$/);
|
|
25
25
|
if (!varMatch) return String(Number(rowToken) + offset);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
27
|
+
// No fallback to the placeholder text. This value is spliced RAW into `.nth({{{rowIndexExpr}}})`,
|
|
28
|
+
// so `${n}` landed as a bare expression outside any template literal — a syntax error that took
|
|
29
|
+
// the whole spec file at parse time, not just the step, and named nothing useful. The throw
|
|
30
|
+
// reaches StepMapper.mapStep and becomes SG-E038 against this step alone (#781).
|
|
31
|
+
const expr: string = context.dataResolver.resolveData(varMatch[1].trim(), context.featureName);
|
|
33
32
|
if (/^\d+$/.test(expr)) return String(Number(expr) + offset); // inline-data mode
|
|
34
33
|
if (offset === 0) return expr;
|
|
35
34
|
return offset > 0 ? `${expr} + ${offset}` : `${expr} - ${-offset}`;
|