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 @@
-
+
+
+ expand_more
+
+
+
+
+
+
+
-
- expand_more
-
-
-
-
-
-
- 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.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ row.error }}
-
-
-
-
-
-
- No settings are defined yet for this funding source.
-
-
-
-
-
-
-
+
+ Skipped by wallet config
+
-
+
@@ -1396,209 +1274,32 @@
-
-
- expand_more
-
-
+
+
+
+ expand_more
+
+
-
-
-
-
-
-
- Function Mocks
-
-
-
-
-
-
- {{ currentFunctionMockRows.length }} mocks
-
-
-
-
-
-
- Reusable mocks for this function and the selected funding source.
-
-
- Edit the mock fields directly. Changes update
- {{ currentFunctionBaseMockPath }}
- immediately.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.error }}
+
+
+
+
+
+
+ No settings are defined yet for this funding source.
+
+
+
+
+
+
+
+
+ Select a funding source first.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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) {