Skip to content

Commit da31d1b

Browse files
committed
review fixes
1 parent 986c360 commit da31d1b

8 files changed

Lines changed: 113 additions & 20 deletions

File tree

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,16 @@ Supported options:
235235

236236
- `-i, --inputfile` path to the input text spec (required)
237237
- `-n, --numberOfLines` number of rows to generate (default `1`)
238+
- `--pairwise` generate pairwise combinations for enum fields (requires at least 2 enum rules)
238239
- `-f, --format` output format (default `csv`)
239240
- `-o, --outputfile` write output to file instead of stdout
240241
- `-t, --testMode` enable diagnostics mode and generate one row
241242
- `--unsafe-faker-expressions` allow expression-style faker args (disabled by default)
242243

244+
Pairwise note:
245+
246+
- when `--pairwise` is enabled, `--numberOfLines` is accepted but ignored (the pairwise engine determines row count)
247+
243248
## REST API Quick Start
244249

245250
Start the API:
@@ -295,7 +300,8 @@ Raw multiline schema/spec endpoint:
295300

296301
- `Content-Type` must be `text/plain`
297302
- request body is the full multiline spec
298-
- query params: `rowCount` (required), `outputFormat` (optional), `seed` (optional), `responseFormat` (optional: `rows|rendered|all|raw`)
303+
- query params: `rowCount` (required), `outputFormat` (optional), `seed` (optional), `pairwise` (optional), `responseFormat` (optional: `rows|rendered|all|raw`)
304+
- pairwise note: when `pairwise=true`, `rowCount` is accepted for compatibility but ignored (pairwise output size is computed automatically)
299305

300306
Set format default options:
301307

@@ -411,6 +417,7 @@ Inputs:
411417

412418
- `textSpec` (required string)
413419
- `rowCount` (required integer, >= 0)
420+
- `pairwise` (optional boolean, default `false`; when `true`, `rowCount` is ignored)
414421
- `outputFormat` (required string e.g. `csv`, `json`, `jsonl`, `xml`, `sql`)
415422
- `options` (optional object)
416423
- `seed` (optional number)

apps/api/src/openapi.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ const openApiDocument = {
4141
required: ['textSpec', 'rowCount'],
4242
properties: {
4343
textSpec: { type: 'string' },
44-
rowCount: { type: 'integer', minimum: 0 },
44+
rowCount: {
45+
type: 'integer',
46+
minimum: 0,
47+
description: 'Requested row count. Ignored when pairwise is true.',
48+
},
4549
outputFormat: {
4650
type: 'string',
4751
enum: [
@@ -106,7 +110,11 @@ const openApiDocument = {
106110
required: ['textSpec', 'rowCount'],
107111
properties: {
108112
textSpec: { type: 'string' },
109-
rowCount: { type: 'integer', minimum: 0 },
113+
rowCount: {
114+
type: 'integer',
115+
minimum: 0,
116+
description: 'Requested row count. Ignored when pairwise is true.',
117+
},
110118
outputFormat: {
111119
type: 'string',
112120
enum: [
@@ -233,7 +241,7 @@ const openApiDocument = {
233241
name: 'rowCount',
234242
required: true,
235243
schema: { type: 'integer', minimum: 0 },
236-
description: 'Number of rows to generate',
244+
description: 'Number of rows to generate. Ignored when pairwise=true.',
237245
},
238246
{
239247
in: 'query',
@@ -296,7 +304,8 @@ const openApiDocument = {
296304
type: 'boolean',
297305
default: false,
298306
},
299-
description: 'Generate pairwise combinations for ENUM fields (requires at least 2 ENUM rules).',
307+
description:
308+
'Generate pairwise combinations for ENUM fields (requires at least 2 ENUM rules). When true, rowCount is ignored.',
300309
},
301310
{
302311
in: 'query',

apps/cli/src/run-cli.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ export async function runCliCommand({ options, platform }) {
103103
}
104104

105105
progress(result.diagnostics.report);
106+
if (Array.isArray(result.diagnostics.warnings)) {
107+
for (const warning of result.diagnostics.warnings) {
108+
progress(`WARNING: ${warning}`);
109+
}
110+
}
106111
if (options.testMode && result.rows.length > 0) {
107112
progress('e.g.');
108113
progress(JSON.stringify(result.rows[0]));

apps/cli/src/tests/run-cli.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ test('reports warning in test mode when stream mode is ignored for pairwise', as
146146
});
147147
expect(code).toBe(0);
148148
expect(platform.out.join('')).toContain('WARNING: Streaming is ignored when pairwise generation is enabled');
149+
expect(platform.out.join('')).toContain('WARNING: rowCount is ignored when pairwise generation is enabled.');
149150
});
150151

151152
test('generates deterministic pairwise output in buffered mode', async () => {

apps/web/src/tests/browser/abstractions/components/grid-editor.component.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,20 @@ class GridEditorComponent {
8585
}
8686

8787
async clearFilters() {
88-
await this.clearFiltersButton.click();
88+
for (let attempt = 0; attempt < 3; attempt += 1) {
89+
await this.clearFiltersButton.click();
90+
for (let check = 0; check < 20; check += 1) {
91+
const quickFilterValue = await this.quickFilterInput.inputValue();
92+
const hasActiveColumnFilter = await this.grid
93+
.locator('.tabulator-header-filter input')
94+
.evaluateAll((inputs) => inputs.some((input) => String(input.value || '').trim().length > 0));
95+
if (quickFilterValue === '' && !hasActiveColumnFilter) {
96+
return;
97+
}
98+
await this.page.waitForTimeout(50);
99+
}
100+
}
101+
throw new Error('Failed to clear all filters.');
89102
}
90103

91104
async clearSort() {

apps/web/src/tests/browser/app-coverage/filtering-sorting/filtering-sorting.spec.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ test('global filter, column filter, sort, and clear filters produce expected vis
77
const appPage = new AppPage(page);
88
await appPage.goto();
99
await seedInstructionsRows(appPage, ['Apple', 'Banana', 'Cherry']);
10+
await expect.poll(async () => (await appPage.gridEditor.header.getColumnNames()).length).toBeGreaterThan(0);
1011
const [primaryColumnName] = await appPage.gridEditor.header.getColumnNames();
12+
expect(primaryColumnName).toBeTruthy();
1113

1214
await appPage.gridEditor.setQuickFilter('App');
1315
await expect.poll(async () => appPage.gridEditor.renderer.countVisibleRows()).toBe(1);
@@ -20,6 +22,7 @@ test('global filter, column filter, sort, and clear filters produce expected vis
2022

2123
await appPage.gridEditor.clearFilters();
2224
await expect.poll(async () => appPage.gridEditor.renderer.countVisibleRows()).toBe(3);
25+
await expect.poll(async () => appPage.gridEditor.quickFilterInput.inputValue()).toBe('');
2326
await expect.poll(async () => appPage.gridEditor.header.getColumnFilterValue(primaryColumnName)).toBe('');
2427

2528
await appPage.gridEditor.header.sortAsc(primaryColumnName);

packages/core/src/index.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,12 @@ export function generateFromTextSpec({
201201

202202
const dataTable = new GenericDataTable();
203203
let effectiveRowCount = safeRowCount;
204+
const diagnosticsWarnings = [];
204205

205206
if (pairwise) {
207+
if (rowCount !== undefined && rowCount !== null) {
208+
diagnosticsWarnings.push('rowCount is ignored when pairwise generation is enabled.');
209+
}
206210
const pairwiseGenerator = new PairwiseTestDataGenerator(scopedFaker, RandExp);
207211
const initResult = pairwiseGenerator.initializeFromRules(generator.testDataRules());
208212
if (initResult?.isError) {
@@ -227,10 +231,17 @@ export function generateFromTextSpec({
227231
}
228232

229233
const generatedRows = rowsResult?.data?.data || [];
230-
const headers = Array.isArray(generatedRows[0]) ? generatedRows[0] : [];
231-
dataTable.setHeaders(headers);
234+
const generatedHeaders = Array.isArray(generatedRows[0]) ? generatedRows[0] : [];
235+
const originalHeaders = generator.generateHeadersArray();
236+
const generatedHeaderIndexes = new Map(generatedHeaders.map((header, index) => [header, index]));
237+
dataTable.setHeaders(originalHeaders);
232238
for (let rowIndex = 1; rowIndex < generatedRows.length; rowIndex += 1) {
233-
dataTable.appendDataRow(generatedRows[rowIndex]);
239+
const generatedRow = Array.isArray(generatedRows[rowIndex]) ? generatedRows[rowIndex] : [];
240+
const reorderedRow = originalHeaders.map((header) => {
241+
const generatedIndex = generatedHeaderIndexes.get(header);
242+
return generatedIndex === undefined ? '' : generatedRow[generatedIndex];
243+
});
244+
dataTable.appendDataRow(reorderedRow);
234245
}
235246
effectiveRowCount = Math.max(generatedRows.length - 1, 0);
236247
} else {
@@ -260,6 +271,7 @@ export function generateFromTextSpec({
260271
report: generator.compilationReport(),
261272
rowCount: effectiveRowCount,
262273
pairwise,
274+
warnings: diagnosticsWarnings,
263275
},
264276
};
265277
}

packages/core/src/tests/core-api/generateFromTextSpec.test.js

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -177,24 +177,47 @@ test('generateFromTextSpec remains deterministic under overlapping seeded invoca
177177

178178
test('generateFromTextSpec supports pairwise generation for enum rules', () => {
179179
const result = generateFromTextSpec({
180-
textSpec: 'Browser\nChrome,Firefox,Safari\nTheme\nLight,Dark',
180+
textSpec: 'Browser\nChrome,Firefox,Safari\nTheme\nLight,Dark\nRegion\nUS,EU',
181181
rowCount: 100,
182182
outputFormat: 'json',
183183
pairwise: true,
184184
});
185185

186186
expect(result.ok).toBe(true);
187-
expect(result.headers).toEqual(['Browser', 'Theme']);
188-
expect(result.rows).toEqual([
189-
['Chrome', 'Light'],
190-
['Chrome', 'Dark'],
191-
['Firefox', 'Light'],
192-
['Firefox', 'Dark'],
193-
['Safari', 'Light'],
194-
['Safari', 'Dark'],
195-
]);
187+
expect(result.headers).toEqual(['Browser', 'Theme', 'Region']);
188+
expect(result.rows.length).toBeLessThan(3 * 2 * 2);
189+
190+
const valuesByHeader = {
191+
Browser: ['Chrome', 'Firefox', 'Safari'],
192+
Theme: ['Light', 'Dark'],
193+
Region: ['US', 'EU'],
194+
};
195+
const getValue = (row, header) => row[result.headers.indexOf(header)];
196+
197+
const coveredPairs = new Set();
198+
for (const row of result.rows) {
199+
coveredPairs.add(`Browser:${getValue(row, 'Browser')}|Theme:${getValue(row, 'Theme')}`);
200+
coveredPairs.add(`Browser:${getValue(row, 'Browser')}|Region:${getValue(row, 'Region')}`);
201+
coveredPairs.add(`Theme:${getValue(row, 'Theme')}|Region:${getValue(row, 'Region')}`);
202+
}
203+
204+
for (const browser of valuesByHeader.Browser) {
205+
for (const theme of valuesByHeader.Theme) {
206+
expect(coveredPairs.has(`Browser:${browser}|Theme:${theme}`)).toBe(true);
207+
}
208+
for (const region of valuesByHeader.Region) {
209+
expect(coveredPairs.has(`Browser:${browser}|Region:${region}`)).toBe(true);
210+
}
211+
}
212+
for (const theme of valuesByHeader.Theme) {
213+
for (const region of valuesByHeader.Region) {
214+
expect(coveredPairs.has(`Theme:${theme}|Region:${region}`)).toBe(true);
215+
}
216+
}
217+
196218
expect(result.diagnostics.pairwise).toBe(true);
197-
expect(result.diagnostics.rowCount).toBe(6);
219+
expect(result.diagnostics.rowCount).toBe(result.rows.length);
220+
expect(result.diagnostics.warnings).toContain('rowCount is ignored when pairwise generation is enabled.');
198221
});
199222

200223
test('generateFromTextSpec rejects pairwise mode with fewer than 2 enum rules', () => {
@@ -208,3 +231,23 @@ test('generateFromTextSpec rejects pairwise mode with fewer than 2 enum rules',
208231
expect(result.ok).toBe(false);
209232
expect(result.errors[0]).toMatch(/requires at least 2 ENUM parameters/i);
210233
});
234+
235+
test('generateFromTextSpec pairwise preserves original interleaved column order', () => {
236+
const result = generateFromTextSpec({
237+
textSpec: 'Name\nBob\nBrowser\nChrome,Firefox,Safari\nAge\n30\nTheme\nLight,Dark',
238+
rowCount: 100,
239+
outputFormat: 'json',
240+
pairwise: true,
241+
});
242+
243+
expect(result.ok).toBe(true);
244+
expect(result.headers).toEqual(['Name', 'Browser', 'Age', 'Theme']);
245+
expect(result.rows).toEqual([
246+
['Bob', 'Chrome', '30', 'Light'],
247+
['Bob', 'Chrome', '30', 'Dark'],
248+
['Bob', 'Firefox', '30', 'Light'],
249+
['Bob', 'Firefox', '30', 'Dark'],
250+
['Bob', 'Safari', '30', 'Light'],
251+
['Bob', 'Safari', '30', 'Dark'],
252+
]);
253+
});

0 commit comments

Comments
 (0)