Skip to content

Commit 711dc53

Browse files
authored
Merge pull request #72 from eviltester/71-minor-bug-fixes
bug fixes
2 parents d229070 + a1a0506 commit 711dc53

5 files changed

Lines changed: 284 additions & 13 deletions

File tree

apps/web/styles.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,35 @@ Custom header styles for default items
463463
min-width: 9rem;
464464
}
465465

466+
.generator-button-with-help {
467+
display: inline-flex;
468+
align-items: center;
469+
gap: 0.35rem;
470+
}
471+
472+
.generator-file-icon {
473+
display: inline-block;
474+
width: 0.8rem;
475+
height: 0.95rem;
476+
margin-right: 0.4rem;
477+
vertical-align: -0.1rem;
478+
background: #ffffff;
479+
border: 1px solid #d8f3dc;
480+
border-radius: 1px;
481+
position: relative;
482+
}
483+
484+
.generator-file-icon::after {
485+
content: '';
486+
position: absolute;
487+
top: -1px;
488+
right: -1px;
489+
width: 0.28rem;
490+
height: 0.28rem;
491+
background: #74c69d;
492+
clip-path: polygon(100% 0, 0 0, 100% 100%);
493+
}
494+
466495
.generator-options-wrapper {
467496
flex: 1 1 100%;
468497
display: flex;

packages/core-ui/js/gui_components/data-generator-page.js

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,13 @@ function buildRuleSpecFromSchemaRow(row) {
7171
function schemaRowsToSpec(schemaRows) {
7272
const lines = [];
7373
(schemaRows || []).forEach((row) => {
74-
lines.push(String(row?.name ?? '').trim());
75-
lines.push(buildRuleSpecFromSchemaRow(row));
74+
const name = String(row?.name ?? '').trim();
75+
const ruleSpec = buildRuleSpecFromSchemaRow(row);
76+
if (name.length === 0 && ruleSpec.length === 0) {
77+
return;
78+
}
79+
lines.push(name);
80+
lines.push(ruleSpec);
7681
});
7782
return lines.join('\n');
7883
}
@@ -227,8 +232,28 @@ class DataGeneratorPage {
227232
<label>Output Format
228233
<select id="generatorOutputFormat"></select>
229234
</label>
230-
<button id="generateDataButton">Generate Data</button>
231-
<button id="generateAllPairsButton" style="display:none;">Generate Pairwise</button>
235+
<span class="generator-button-with-help">
236+
<span
237+
class="helpicon"
238+
data-help="generator-generate-data-help"
239+
data-help-text='
240+
<p>Generate Data for currently defined rows and output format to file.</p>
241+
<p><a class="helplink" href="https://anywaydata.com/docs/test-data/generate-to-file" target="_blank" rel="noopener noreferrer">Generate To File docs</a></p>
242+
'
243+
></span>
244+
<button id="generateDataButton"><span class="generator-file-icon" aria-hidden="true"></span>Generate Data</button>
245+
</span>
246+
<span class="generator-button-with-help" id="generateAllPairsButtonWrapper" style="display:none;">
247+
<span
248+
class="helpicon"
249+
data-help="generator-pairwise-help"
250+
data-help-text='
251+
<p>Generate Pairwise Data from schema to a file.</p>
252+
<p><a class="helplink" href="https://anywaydata.com/docs/test-data/pairwise-testing" target="_blank" rel="noopener noreferrer">Pairwise testing docs</a></p>
253+
'
254+
></span>
255+
<button id="generateAllPairsButton"><span class="generator-file-icon" aria-hidden="true"></span>Generate Pairwise</button>
256+
</span>
232257
<div class="generator-options-wrapper">
233258
<div id="generatorOptionsPanel" class="generator-options-panel"></div>
234259
<div id="generatorStatusText" class="generator-status-text" aria-live="polite" role="status"></div>
@@ -242,7 +267,16 @@ class DataGeneratorPage {
242267
<div class="generator-preview-controls" id="generatorPreviewControlsSection" data-subsection-order="1" aria-label="Preview Controls">
243268
<label for="previewRowsCount" class="generator-preview-count-label">Preview Items Count</label>
244269
<input type="number" id="previewRowsCount" min="0" max="50" value="10">
245-
<button id="previewDataButton">Preview</button>
270+
<span class="generator-button-with-help">
271+
<span
272+
class="helpicon"
273+
data-help="generator-preview-help"
274+
data-help-text='
275+
<p>Show a preview of the defined items count in the Output Preview area.</p>
276+
'
277+
></span>
278+
<button id="previewDataButton">Preview</button>
279+
</span>
246280
</div>
247281
<div class="generator-output-preview" id="generatorOutputPreviewSection" data-subsection-order="2" aria-label="Output Preview">
248282
<label for="generatorOutputPreview">Output Preview</label>
@@ -349,6 +383,10 @@ class DataGeneratorPage {
349383

350384
const schemaModeToggleButton = this.documentObj.getElementById('schemaModeToggleButton');
351385
schemaModeToggleButton.addEventListener('click', () => this.toggleSchemaEditMode());
386+
const schemaTextArea = this.documentObj.getElementById('generatorSchemaText');
387+
schemaTextArea?.addEventListener('input', () => {
388+
this.updateAllPairsButtonVisibility();
389+
});
352390

353391
const previewDataButton = this.documentObj.getElementById('previewDataButton');
354392
previewDataButton.addEventListener('click', () => this.previewData());
@@ -610,6 +648,26 @@ class DataGeneratorPage {
610648
return { rows, errors: [] };
611649
}
612650

651+
syncSchemaRowsFromTextMode({ showErrors = false } = {}) {
652+
if (!this.isTextMode) {
653+
return { rows: this.schemaRows, errors: [] };
654+
}
655+
656+
const textArea = this.documentObj.getElementById('generatorSchemaText');
657+
const parsed = this.parseSchemaTextToRows(textArea?.value || '');
658+
if (parsed.errors.length > 0) {
659+
if (showErrors) {
660+
this.alertFn(parsed.errors.join('\n'));
661+
}
662+
return parsed;
663+
}
664+
665+
// Keep empty text schemas as zero rows while in text mode so validation can
666+
// report "Add at least one schema row." instead of introducing a synthetic blank row.
667+
this.schemaRows = parsed.rows;
668+
return { rows: this.schemaRows, errors: [] };
669+
}
670+
613671
ruleToSchemaRow(rule) {
614672
const row = this.createBlankSchemaRow();
615673
row.name = String(rule?.name ?? '');
@@ -847,7 +905,12 @@ class DataGeneratorPage {
847905
}
848906

849907
createConfiguredGenerator() {
850-
const { errors, rows } = validateSchemaRows(this.schemaRows);
908+
const parsed = this.syncSchemaRowsFromTextMode({ showErrors: false });
909+
if (parsed.errors?.length > 0) {
910+
return { errors: parsed.errors };
911+
}
912+
913+
const { errors, rows } = validateSchemaRows(parsed.rows);
851914
if (errors.length > 0) {
852915
return { errors };
853916
}
@@ -1025,7 +1088,12 @@ class DataGeneratorPage {
10251088
}
10261089

10271090
countEnumColumns() {
1028-
const { errors, rows } = validateSchemaRows(this.schemaRows);
1091+
const parsed = this.syncSchemaRowsFromTextMode({ showErrors: false });
1092+
if (parsed.errors?.length > 0) {
1093+
return 0;
1094+
}
1095+
1096+
const { errors, rows } = validateSchemaRows(parsed.rows);
10291097
if (errors.length > 0) {
10301098
return 0;
10311099
}
@@ -1066,9 +1134,9 @@ class DataGeneratorPage {
10661134

10671135
updateAllPairsButtonVisibility() {
10681136
const enumCount = this.countEnumColumns();
1069-
const button = this.documentObj.getElementById('generateAllPairsButton');
1070-
if (button) {
1071-
button.style.display = enumCount >= 2 ? '' : 'none';
1137+
const buttonWrapper = this.documentObj.getElementById('generateAllPairsButtonWrapper');
1138+
if (buttonWrapper) {
1139+
buttonWrapper.style.display = enumCount >= 2 ? 'inline-flex' : 'none';
10721140
}
10731141
}
10741142

packages/core-ui/js/gui_components/import-export-controls.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ class ImportExportControls {
2525
constructor() {
2626
this.previewRowLimit = 10;
2727
this.textEditMode = 'preview';
28+
this.previewTextDirty = false;
2829
this.defaultOptionsPanelWidthPx = 272;
2930
this.minOptionsPanelWidthPx = 180;
3031
this.maxOptionsPanelWidthPx = 520;
3132
this.minPreviewPanelWidthPx = 220;
3233
this.currentOptionsPanelWidthPx = null;
3334
this._activeSplitDrag = null;
35+
this._hasBoundPreviewTextInput = false;
3436
}
3537

3638
addHTMLtoGui(parentelement) {
@@ -55,6 +57,8 @@ class ImportExportControls {
5557
setgridfromtextbutton.addEventListener('click', importTextAreaClickListener, false);
5658
this._syncGridFromTextButtonState();
5759

60+
this._bindPreviewTextInputIfAvailable();
61+
5862
this.fileInputElement = parentelement.querySelector('#csvinput');
5963
let csvinputchangelistener = this.loadFile.bind(this);
6064
// clear file upload on click to allow re-uploading same file after option changes e.g for delimiters
@@ -89,8 +93,16 @@ class ImportExportControls {
8993

9094
importTextArea() {
9195
if (this.isPreviewTextMode()) {
92-
alert('Grid to Text only availalable in Edit mode');
93-
return;
96+
if (!this.previewTextDirty) {
97+
alert('Grid to Text only availalable in Edit mode');
98+
return;
99+
}
100+
const typeToImport = document.querySelector('li.active-type a').getAttribute('data-type');
101+
const textToImport = document.getElementById('markdownarea').value;
102+
this.setCurrentTypeOptions();
103+
return Promise.resolve(this._previewThenImportToGrid(typeToImport, textToImport)).then(() => {
104+
this._setPreviewTextDirty(false);
105+
});
94106
}
95107

96108
const typeToImport = document.querySelector('li.active-type a').getAttribute('data-type');
@@ -474,6 +486,7 @@ class ImportExportControls {
474486
toggleTextEditMode() {
475487
if (this.isPreviewTextMode()) {
476488
this.textEditMode = 'edit';
489+
this._setPreviewTextDirty(false);
477490
this._syncGridFromTextButtonState();
478491
if (confirm('Do you want to Set Text From Grid?')) {
479492
this.renderTextFromGrid();
@@ -484,6 +497,7 @@ class ImportExportControls {
484497
}
485498

486499
this.textEditMode = 'preview';
500+
this._setPreviewTextDirty(false);
487501
this._syncGridFromTextButtonState();
488502
this.renderTextFromGrid();
489503
return this.textEditMode;
@@ -494,6 +508,7 @@ class ImportExportControls {
494508
const previewDataTable = this.exporter.getGridAsGenericDataTable(this.previewRowLimit);
495509
const textToRender = this.exporter.getDataTableAs(type, previewDataTable);
496510
this.exportControls.setTextFromString(textToRender);
511+
this._setPreviewTextDirty(false);
497512
}
498513

499514
_renderPreviewTextFromInput(type, textToImport) {
@@ -505,6 +520,7 @@ class ImportExportControls {
505520
const limitedDataTable = this._limitDataTableRows(dataTable, this.previewRowLimit);
506521
const textToRender = this.exporter.getDataTableAs(type, limitedDataTable);
507522
this.exportControls.setTextFromString(textToRender);
523+
this._setPreviewTextDirty(false);
508524
}
509525

510526
async _previewThenImportToGrid(type, textToImport) {
@@ -548,11 +564,34 @@ class ImportExportControls {
548564
}
549565

550566
_syncGridFromTextButtonState() {
567+
this._bindPreviewTextInputIfAvailable();
551568
const importButton = document.querySelector('#setgridfromtextbutton');
552569
if (!importButton) {
553570
return;
554571
}
555-
importButton.disabled = this.isPreviewTextMode();
572+
importButton.disabled = this.isPreviewTextMode() ? !this.previewTextDirty : false;
573+
}
574+
575+
_bindPreviewTextInputIfAvailable() {
576+
if (this._hasBoundPreviewTextInput) {
577+
return;
578+
}
579+
const textArea = document.getElementById('markdownarea');
580+
if (!textArea) {
581+
return;
582+
}
583+
textArea.addEventListener('input', () => {
584+
if (!this.isPreviewTextMode()) {
585+
return;
586+
}
587+
this._setPreviewTextDirty(true);
588+
});
589+
this._hasBoundPreviewTextInput = true;
590+
}
591+
592+
_setPreviewTextDirty(isDirty) {
593+
this.previewTextDirty = isDirty === true;
594+
this._syncGridFromTextButtonState();
556595
}
557596

558597
_configureOptionsPreviewSplitter(editArea, optionsParent, splitter, textArea) {

packages/core-ui/src/tests/generator/data-generator-page.test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ describe('DataGeneratorPage', () => {
156156
expect(spec).toBe('A\nword.noun()\nB\nx');
157157
});
158158

159+
test('schemaRowsToSpec omits fully blank rows', () => {
160+
const spec = schemaRowsToSpec([{ name: '', sourceType: 'regex', value: '' }]);
161+
expect(spec).toBe('');
162+
});
163+
159164
test('uses curated alphabetical faker command list in schema editor', () => {
160165
const page = new DataGeneratorPage({
161166
parentElement: document.getElementById('app'),
@@ -788,4 +793,84 @@ describe('DataGeneratorPage', () => {
788793
expect(page.schemaRows[0].value).toBe('[A-Z]{4}');
789794
expect(page.schemaRows[0].sourceType).toBe('regex');
790795
});
796+
797+
test('edit as text shows empty textarea for untouched blank schema', () => {
798+
const page = new DataGeneratorPage({
799+
parentElement: document.getElementById('app'),
800+
documentObj: document,
801+
alertFn,
802+
faker: { word: { noun: () => 'x' } },
803+
RandExp: function RandExp() {},
804+
TabulatorCtor: FakeTabulator,
805+
GridExtensionClass: FakeGridExtension,
806+
ExporterClass: FakeExporter,
807+
DownloadClass: FakeDownload,
808+
TestDataGeneratorClass: FakeTestDataGenerator,
809+
});
810+
page.init();
811+
812+
const toggle = document.getElementById('schemaModeToggleButton');
813+
toggle.click();
814+
815+
const textArea = document.getElementById('generatorSchemaText');
816+
expect(textArea.value).toBe('');
817+
});
818+
819+
test('can generate directly from text mode without toggling back to schema mode', async () => {
820+
const page = new DataGeneratorPage({
821+
parentElement: document.getElementById('app'),
822+
documentObj: document,
823+
alertFn,
824+
faker: { word: { noun: () => 'x' } },
825+
RandExp: function RandExp() {},
826+
TabulatorCtor: FakeTabulator,
827+
GridExtensionClass: FakeGridExtension,
828+
ExporterClass: FakeExporter,
829+
DownloadClass: FakeDownload,
830+
TestDataGeneratorClass: FakeTestDataGenerator,
831+
});
832+
page.init();
833+
834+
const toggle = document.getElementById('schemaModeToggleButton');
835+
toggle.click();
836+
837+
const textArea = document.getElementById('generatorSchemaText');
838+
textArea.value = 'City\nLondon';
839+
document.getElementById('generateRowsCount').value = '2';
840+
841+
await page.generateDataFile();
842+
843+
expect(alertFn).not.toHaveBeenCalled();
844+
expect(FakeDownload.lastDownload).toEqual({
845+
filename: 'generated-data.csv',
846+
text: 'csv:async:2',
847+
});
848+
});
849+
850+
test('empty text mode schema keeps zero rows and shows add-row validation', async () => {
851+
const page = new DataGeneratorPage({
852+
parentElement: document.getElementById('app'),
853+
documentObj: document,
854+
alertFn,
855+
faker: { word: { noun: () => 'x' } },
856+
RandExp: function RandExp() {},
857+
TabulatorCtor: FakeTabulator,
858+
GridExtensionClass: FakeGridExtension,
859+
ExporterClass: FakeExporter,
860+
DownloadClass: FakeDownload,
861+
TestDataGeneratorClass: FakeTestDataGenerator,
862+
});
863+
page.init();
864+
865+
const toggle = document.getElementById('schemaModeToggleButton');
866+
toggle.click();
867+
868+
const textArea = document.getElementById('generatorSchemaText');
869+
textArea.value = '';
870+
871+
await page.generateDataFile();
872+
873+
expect(alertFn).toHaveBeenCalledWith('Add at least one schema row.');
874+
expect(page.schemaRows).toEqual([]);
875+
});
791876
});

0 commit comments

Comments
 (0)