From 0b8b530ca6ee08537d202ccd4ea7417339018d52 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 26 Mar 2026 19:13:06 +0200 Subject: [PATCH] feat: nicer UI --- tests/wallets/index.html | 1074 +++++++++++++++++++------------------- 1 file changed, 542 insertions(+), 532 deletions(-) diff --git a/tests/wallets/index.html b/tests/wallets/index.html index d50e6bccd..d641a3624 100644 --- a/tests/wallets/index.html +++ b/tests/wallets/index.html @@ -136,13 +136,6 @@ justify-content: flex-end; } - .dataset-tabs { - margin-top: 20px; - border-radius: 18px; - background: rgba(22, 32, 40, 0.04); - padding: 6px; - } - .stats-row { margin-top: 18px; display: grid; @@ -182,7 +175,7 @@ display: grid; gap: 18px; grid-template-columns: repeat(2, minmax(0, 1fr)); - align-items: start; + align-items: stretch; } .stack { @@ -202,6 +195,23 @@ padding: 18px 20px; } + .picker-card { + height: 100%; + } + + .picker-card .q-card__section--vert { + height: 100%; + padding: 16px 18px; + } + + .picker-card .section-title { + margin-bottom: 10px; + } + + .picker-card .section-caption { + font-size: 0.84rem; + } + .section-title { display: flex; gap: 10px; @@ -275,7 +285,7 @@ .native-select-label { display: block; - margin-bottom: 8px; + margin-bottom: 6px; color: var(--studio-muted); font-size: 0.74rem; text-transform: uppercase; @@ -304,7 +314,7 @@ } .fixture-select .q-field__control { - min-height: 50px; + min-height: 46px; border-radius: 10px; background: rgba(255, 255, 255, 0.92); } @@ -339,6 +349,34 @@ pointer-events: none; } + .picker-action-row { + display: flex; + gap: 12px; + align-items: flex-start; + } + + .picker-select { + flex: 1 1 auto; + min-width: 0; + } + + .editor-dialog-card { + width: calc(100vw - 32px); + max-width: calc(100vw - 32px); + border-radius: 24px; + } + + .dialog-header { + position: relative; + padding-right: 96px; + } + + .dialog-close-btn { + position: absolute; + top: 16px; + right: 16px; + } + .native-input, .native-textarea { width: 100%; @@ -1061,6 +1099,7 @@ grid-template-columns: 1fr; } + .picker-action-row, .test-mock-binding-header { flex-direction: column; } @@ -1105,8 +1144,7 @@ fixtures_rest.json and fixtures_rpc.json - with the same expansion logic the wallet tests use. The explorer shows which - funding source, test case, and merged mocks produce each generated wallet test. + with the same expansion logic the wallet tests use.

@@ -1114,26 +1152,8 @@ - - - - - -
-
{{ datasetDef.label }}
-
- {{ datasets[datasetDef.key].analysis ? datasets[datasetDef.key].analysis.summary.expandedTests : 0 }} - generated wallet tests -
-
-
-
-
Funding Sources
@@ -1205,7 +1202,7 @@
- +
@@ -1219,170 +1216,51 @@ - + + + + + +
+ +
- - - -
-
- - Skipped by wallet config - -
- -
- -
- -
-

- Settings to configure for the selected funding source. -

-
-
- -
- - {{ currentFundingSourceSettingRows.length }} rows - -
-
- -
-
- Edit, add, or remove key-value settings. Changes update the active fixture JSON immediately. -
- -
-
-
-
Key
-
Type
-
Value
-
Actions
-
- -
-
-
- -
- -
- -
- -
-
- -
- - -
- -
- -
-
- -
- {{ row.error }} -
-
-
-
- -
- No settings are defined yet for this funding source. -
- -
- -
-
-
+ + Skipped by wallet config +
- +
@@ -1396,209 +1274,32 @@ - - - +
+ + + -
- -
- -
-

- Function Mocks -

-
-
- -
- - {{ currentFunctionMockRows.length }} mocks - -
-
- -
-
- Reusable mocks for this function and the selected funding source. -
-
- Edit the mock fields directly. Changes update - {{ currentFunctionBaseMockPath }} - immediately. -
- -
-
-
-
-
Mock Name
-
URI
-
Method
-
Headers JSON
-
Query Params JSON
-
Request Type
-
Response Type
-
Response JSON
-
- -
-
-
- -
- -
-
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- {{ mockRow.error }} -
-
- Leave unused fields blank. JSON columns must contain valid JSON objects or values. -
-
-
-
-
- -
- No function-level mocks are defined yet for - {{ currentFundingSource.name }} - in - {{ currentFunction.name }}. -
- -
- -
-
-
- -
- Select a funding source and function to inspect reusable mocks. +
@@ -1795,15 +1496,6 @@
- -
- -
@@ -1884,6 +1576,351 @@
+ + + +
+

Funding Source Settings

+
+ Edit the settings for the selected funding source. Changes update the active fixture JSON immediately. +
+
+ +
+ + {{ currentFundingSourceSettingRows.length }} rows + +
+ + +
+ + + + +
+
+
+
+
Key
+
Type
+
Value
+
Actions
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+
+ +
+ {{ row.error }} +
+
+
+
+ +
+ No settings are defined yet for this funding source. +
+ +
+ +
+
+ +
+ Select a funding source first. +
+
+
+
+ + + + +
+

Function Mocks

+
+ Reusable mocks for the selected function and funding source. +
+
+ Changes update + {{ currentFunctionBaseMockPath }} + immediately. +
+
+ +
+ + {{ currentFunctionMockRows.length }} mocks + +
+ + +
+ + + + +
+
+
+
+
+
Mock Name
+
URI
+
Method
+
Headers JSON
+
Query Params JSON
+
Request Type
+
Response Type
+
Response JSON
+
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ {{ mockRow.error }} +
+
+ Leave unused fields blank. JSON columns must contain valid JSON objects or values. +
+
+
+
+
+ +
+ No function-level mocks are defined yet for + {{ currentFundingSource.name }} + in + {{ currentFunction.name }}. +
+ +
+ +
+
+ +
+ Select a funding source and function first. +
+
+
+
+ @@ -1987,26 +2024,11 @@ - -
@@ -2390,6 +2412,33 @@ } } + function inferDatasetKeyFromData(data) { + let rpcHint = false + + for (const fn of Object.values(data?.functions || {})) { + for (const walletMocks of Object.values(fn?.mocks || {})) { + for (const mockDefinition of Object.values(walletMocks || {})) { + if (!isPlainObject(mockDefinition)) { + continue + } + + if (Object.prototype.hasOwnProperty.call(mockDefinition, 'uri')) { + return 'rest' + } + + if ( + Object.prototype.hasOwnProperty.call(mockDefinition, 'request_type') || + Object.prototype.hasOwnProperty.call(mockDefinition, 'response_type') + ) { + rpcHint = true + } + } + } + } + + return rpcHint ? 'rpc' : '' + } + function settingTypeFromValue(value) { if (value === null) { return 'null' @@ -2442,10 +2491,11 @@ }) return { - datasetDefs: DATASET_DEFS, datasets, editorDialog: false, + fundingSourceSettingsDialog: false, functionMockDraftRowId: 0, + functionMocksDialog: false, functionMockDrafts, settingsDraftRowId: 0, settingsDrafts, @@ -2514,13 +2564,6 @@ ) || null ) }, - currentDatasetKindHelp() { - if (this.selectedDatasetKey === 'rest') { - return 'HTTP funding sources with request and response mocks consumed by pytest_httpserver.' - } - - return 'RPC funding sources with patched Python methods, nested return data, and spy assertions.' - }, editorDirty() { return this.currentDataset.rawText !== this.currentDataset.snapshotText }, @@ -2786,7 +2829,7 @@ } }, mounted() { - this.loadAllRelative() + this.loadRelative(this.selectedDatasetKey) window.addEventListener('keydown', this.handleGlobalKeydown) }, @@ -2912,10 +2955,6 @@ ) this.persistTestMockSet(testEntry, mockSet) }, - removeTestMockBinding(testEntry, mockSet, bindingIndex) { - mockSet.bindings.splice(bindingIndex, 1) - this.persistTestMockSet(testEntry, mockSet) - }, addTestMockBindingItem(testEntry, mockSet, binding) { binding.itemTexts.push({ text: safeStringify({}), @@ -3630,7 +3669,7 @@ } catch (error) { dataset.loadError = `Automatic relative loading failed for ${dataset.fileName}. ` + - `If you opened this page with file://, use “Load Both Files” or “Pick Fixtures Folder”.` + 'If you opened this page with file://, use “Load File” to pick it manually.' if (!dataset.analysis) { dataset.parseError = '' @@ -3639,16 +3678,31 @@ console.error(error) } }, - async loadAllRelative() { - await Promise.all(this.datasetDefs.map(def => this.loadRelative(def.key))) - }, - openBulkInput() { - this.$refs.bulkInput.value = '' - this.$refs.bulkInput.click() - }, - async openCurrentFile() { - const datasetKey = this.currentDataset.key + inferDatasetKey(fileName, data = null) { + const normalizedName = String(fileName || '').toLowerCase() + const exactMatch = DATASET_DEFS.find( + def => def.fileName.toLowerCase() === normalizedName + ) + if (exactMatch) { + return exactMatch.key + } + + if (normalizedName.includes('fixtures_rest') || normalizedName.includes('_rest')) { + return 'rest' + } + + if (normalizedName.includes('fixtures_rpc') || normalizedName.includes('_rpc')) { + return 'rpc' + } + + if (data) { + return inferDatasetKeyFromData(data) + } + + return '' + }, + async openFilePicker() { if (window.showOpenFilePicker) { try { const [handle] = await window.showOpenFilePicker({ @@ -3668,7 +3722,7 @@ } const file = await handle.getFile() - await this.loadDatasetFromFile(datasetKey, file, { + await this.loadChosenDatasetFile(file, { fileHandle: handle, sourceLabel: `File picker: ${file.name}` }) @@ -3680,46 +3734,37 @@ } } - const refName = `${datasetKey}Input` - this.$refs[refName].value = '' - this.$refs[refName].click() + this.$refs.loadFileInput.value = '' + this.$refs.loadFileInput.click() }, - async handleBulkFileSelection(event) { - const files = Array.from(event.target.files || []) - const byName = Object.fromEntries(files.map(file => [file.name, file])) - - for (const def of this.datasetDefs) { - if (byName[def.fileName]) { - await this.loadDatasetFromFile(def.key, byName[def.fileName], { - sourceLabel: `Manual file load: ${byName[def.fileName].name}` - }) - } - } - - const missing = this.datasetDefs - .filter(def => !byName[def.fileName]) - .map(def => def.fileName) - - if (missing.length) { - this.notify( - `Missing file selection for ${missing.join(', ')}.`, - 'warning' - ) - } - }, - async handleSingleFileSelection(datasetKey, event) { + async handleSingleFileSelection(event) { const file = event.target.files?.[0] if (!file) { return } - await this.loadDatasetFromFile(datasetKey, file, { + await this.loadChosenDatasetFile(file, { sourceLabel: `Manual file load: ${file.name}` }) }, - async loadDatasetFromFile(datasetKey, file, options = {}) { + async loadChosenDatasetFile(file, options = {}) { const text = await file.text() const parsed = parseJsonText(text) + const datasetKey = this.inferDatasetKey( + file.name, + parsed.ok ? parsed.data : null + ) + + if (!datasetKey) { + this.notify( + 'Could not determine whether that fixture file is REST or RPC.', + 'negative', + 'Use fixtures_rest.json or fixtures_rpc.json.' + ) + return + } + + this.selectedDatasetKey = datasetKey if (!parsed.ok) { this.datasets[datasetKey].parseError = `Invalid JSON in ${file.name}: ${parsed.error}` @@ -3731,47 +3776,12 @@ this.commitDataset(datasetKey, parsed.data, { rawText: formatted, snapshotText: formatted, - fileHandle: options.fileHandle, + fileHandle: options.fileHandle || null, sourceLabel: options.sourceLabel || `Loaded from ${file.name}`, loadError: '' }) this.notify(`Loaded ${file.name}.`, 'positive') }, - async pickFixturesDirectory() { - if (!window.showDirectoryPicker) { - this.notify( - 'Directory picker is not available in this browser. Use “Load Both Files” instead.', - 'warning' - ) - return - } - - try { - const directoryHandle = await window.showDirectoryPicker({ - mode: 'readwrite' - }) - - for (const def of this.datasetDefs) { - try { - const fileHandle = await directoryHandle.getFileHandle(def.fileName) - const file = await fileHandle.getFile() - await this.loadDatasetFromFile(def.key, file, { - fileHandle, - sourceLabel: `Directory handle: ${def.fileName}` - }) - } catch (error) { - console.error(error) - this.datasets[def.key].loadError = - `Could not find ${def.fileName} in the selected folder. Choose tests/wallets/fixtures/json.` - } - } - } catch (error) { - if (error && error.name !== 'AbortError') { - console.error(error) - this.notify('Could not open the selected folder.', 'negative') - } - } - }, async saveDataset(datasetKey) { const parsedData = this.validateEditor(datasetKey) if (!parsedData) {