From 3d919aa1a8a2bc3c6e1056b6961cb1ce25097862 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 27 Mar 2026 12:33:19 +0200 Subject: [PATCH] feat: UI changs --- tests/wallets/index.html | 407 +++++++++++++++++++++++++++++++++------ 1 file changed, 346 insertions(+), 61 deletions(-) diff --git a/tests/wallets/index.html b/tests/wallets/index.html index 7c2579693..13af21c79 100644 --- a/tests/wallets/index.html +++ b/tests/wallets/index.html @@ -1389,7 +1389,7 @@ v-model="testEntry.callParamsText" class="native-textarea test-json-editor" spellcheck="false" - @blur="persistTestCallParams(testEntry)" + @input="persistTestCallParams(testEntry, { silent: true })" >
{{ testEntry.callParamsError }} @@ -1407,7 +1407,7 @@ v-model="testEntry.expectedText" class="native-textarea test-json-editor" spellcheck="false" - @blur="persistTestExpected(testEntry)" + @input="persistTestExpected(testEntry, { silent: true })" >
{{ testEntry.expectedError }} @@ -1472,7 +1472,7 @@ class="native-input" type="text" spellcheck="false" - @blur="persistTestMockSet(testEntry, mockSet)" + @input="persistTestMockSet(testEntry, mockSet, { silent: true })" />
@@ -1554,7 +1554,7 @@ type="text" placeholder="Optional label" spellcheck="false" - @blur="persistTestMockSet(testEntry, mockSet)" + @input="persistTestMockSet(testEntry, mockSet, { silent: true })" />
@@ -1572,7 +1572,7 @@ type="text" placeholder="json" spellcheck="false" - @blur="persistTestMockSet(testEntry, mockSet)" + @input="persistTestMockSet(testEntry, mockSet, { silent: true })" /> @@ -1588,7 +1588,7 @@ v-model="item.requestBodyText" class="native-textarea test-mock-item-json" spellcheck="false" - @blur="persistTestMockSet(testEntry, mockSet)" + @input="persistTestMockSet(testEntry, mockSet, { silent: true })" >
{{ item.requestBodyError }} @@ -1609,7 +1609,7 @@ type="text" placeholder="json" spellcheck="false" - @blur="persistTestMockSet(testEntry, mockSet)" + @input="persistTestMockSet(testEntry, mockSet, { silent: true })" />
@@ -1625,7 +1625,7 @@ v-model="item.responseText" class="native-textarea test-mock-item-json" spellcheck="false" - @blur="persistTestMockSet(testEntry, mockSet)" + @input="persistTestMockSet(testEntry, mockSet, { silent: true })" >
{{ item.responseError }} @@ -1784,7 +1784,7 @@ type="text" placeholder="setting_key" spellcheck="false" - @blur="persistFundingSourceSettings()" + @input="persistFundingSourceSettings({ silent: true })" />
@@ -1823,7 +1823,7 @@ v-model="row.valueText" class="native-textarea" spellcheck="false" - @blur="persistFundingSourceSettings()" + @input="persistFundingSourceSettings({ silent: true })" > @@ -1939,7 +1939,7 @@ type="text" placeholder="mock_name" spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" />
Mock Name: Stable mock identifier used inside this function's fixture map. @@ -1963,7 +1963,7 @@ type="text" placeholder="json" spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" />
Request Type: Optional request payload format such as @@ -1979,7 +1979,7 @@ type="text" placeholder="GET" spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" />
Method: HTTP verb or RPC method name used by this reusable mock. @@ -1994,7 +1994,7 @@ type="text" placeholder="/path" spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" />
Path: Request path or URI matcher for REST mocks. @@ -2009,7 +2009,7 @@ class="native-textarea" placeholder='{"Authorization":"Bearer ..."}' spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" >
Headers JSON: JSON object of request headers that must match. @@ -2023,7 +2023,7 @@ class="native-textarea" placeholder='{"foo":"bar"}' spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" >
Query Params JSON: JSON object of expected query-string parameters. @@ -2039,7 +2039,7 @@ type="text" placeholder="json" spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" />
Response Type: Optional response mode such as @@ -2057,7 +2057,7 @@ class="native-textarea" placeholder='{"field":"value"}' spellcheck="false" - @blur="persistFunctionMocks()" + @input="persistFunctionMocks({ silent: true })" >
Response Body: JSON payload, data object, or exception body returned by the mock. @@ -2198,6 +2198,7 @@ autocapitalize="off" autocomplete="off" autocorrect="off" + @input="syncEditorFromRawText(selectedDatasetKey, { silent: true })" > @@ -2231,6 +2232,7 @@ testFile: 'tests/wallets/test_rpc_wallets.py' } ] + const STORAGE_PREFIX = 'lnbits-wallet-fixture-studio' const JsonPanel = { name: 'JsonPanel', @@ -3064,19 +3066,39 @@ this.ensureSelections(this.selectedDatasetKey) this.ensureSettingsDraftForCurrent() this.ensureFunctionMockDraftForCurrent() + this.persistUiState() }, currentFundingSourceName() { this.ensureSettingsDraftForCurrent() + this.persistUiState() }, currentTestFundingSourceName() { this.ensureFunctionMockDraftForCurrent() + this.persistUiState() }, currentFunctionName() { this.ensureFunctionMockDraftForCurrent() + this.persistUiState() + }, + currentSelectedTestTypeKey() { + this.persistUiState() } }, mounted() { - this.loadRelative(this.selectedDatasetKey) + this.restoreUiState() + + let restoredAny = this.restoreActiveDatasetFromStorage() + if (!restoredAny) { + for (const def of DATASET_DEFS) { + restoredAny = this.restoreDatasetFromStorage(def.key) || restoredAny + } + } + + if (!this.datasets[this.selectedDatasetKey].analysis) { + this.loadRelative(this.selectedDatasetKey) + } else if (!restoredAny) { + this.loadRelative(this.selectedDatasetKey) + } window.addEventListener('keydown', this.handleGlobalKeydown) }, @@ -3084,6 +3106,224 @@ window.removeEventListener('keydown', this.handleGlobalKeydown) }, methods: { + storageKey(datasetKey) { + return `${STORAGE_PREFIX}:dataset:${datasetKey}` + }, + activeDatasetStorageKey() { + return `${STORAGE_PREFIX}:active-dataset` + }, + uiStateStorageKey() { + return `${STORAGE_PREFIX}:ui-state` + }, + canUseLocalStorage() { + try { + return typeof window !== 'undefined' && Boolean(window.localStorage) + } catch (error) { + return false + } + }, + persistDatasetToStorage(datasetKey) { + if (!this.canUseLocalStorage()) { + return + } + + const dataset = this.datasets[datasetKey] + + if (!dataset?.rawText) { + window.localStorage.removeItem(this.storageKey(datasetKey)) + if (datasetKey === this.selectedDatasetKey) { + window.localStorage.removeItem(this.activeDatasetStorageKey()) + } + return + } + + const payload = JSON.stringify({ + rawText: dataset.rawText, + snapshotText: dataset.snapshotText, + sourceLabel: dataset.sourceLabel || '' + }) + + window.localStorage.setItem(this.storageKey(datasetKey), payload) + + if (datasetKey === this.selectedDatasetKey) { + window.localStorage.setItem( + this.activeDatasetStorageKey(), + JSON.stringify({ + datasetKey, + rawText: dataset.rawText, + snapshotText: dataset.snapshotText, + sourceLabel: dataset.sourceLabel || '' + }) + ) + } + }, + restoreActiveDatasetFromStorage() { + if (!this.canUseLocalStorage()) { + return false + } + + const storedValue = window.localStorage.getItem(this.activeDatasetStorageKey()) + if (!storedValue) { + return false + } + + const parsedStoredValue = parseJsonText(storedValue) + if (!parsedStoredValue.ok || !isPlainObject(parsedStoredValue.data)) { + window.localStorage.removeItem(this.activeDatasetStorageKey()) + return false + } + + const { datasetKey } = parsedStoredValue.data + + if ( + typeof datasetKey !== 'string' || + !DATASET_DEFS.some(def => def.key === datasetKey) + ) { + window.localStorage.removeItem(this.activeDatasetStorageKey()) + return false + } + + const rawText = String(parsedStoredValue.data.rawText || '').trim() + const snapshotText = + typeof parsedStoredValue.data.snapshotText === 'string' + ? parsedStoredValue.data.snapshotText + : rawText + + if (!rawText) { + window.localStorage.removeItem(this.activeDatasetStorageKey()) + return false + } + + const parsedDataset = parseJsonText(rawText) + if (!parsedDataset.ok) { + window.localStorage.removeItem(this.activeDatasetStorageKey()) + return false + } + + this.selectedDatasetKey = datasetKey + this.commitDataset(datasetKey, parsedDataset.data, { + rawText, + snapshotText, + sourceLabel: + parsedStoredValue.data.sourceLabel || + `Local storage: ${this.datasets[datasetKey].fileName}`, + fileHandle: null, + loadError: '' + }) + + return true + }, + restoreDatasetFromStorage(datasetKey) { + if (!this.canUseLocalStorage()) { + return false + } + + const storedValue = window.localStorage.getItem(this.storageKey(datasetKey)) + if (!storedValue) { + return false + } + + const parsedStoredValue = parseJsonText(storedValue) + if (!parsedStoredValue.ok || !isPlainObject(parsedStoredValue.data)) { + window.localStorage.removeItem(this.storageKey(datasetKey)) + return false + } + + const rawText = String(parsedStoredValue.data.rawText || '').trim() + const snapshotText = + typeof parsedStoredValue.data.snapshotText === 'string' + ? parsedStoredValue.data.snapshotText + : rawText + + if (!rawText) { + window.localStorage.removeItem(this.storageKey(datasetKey)) + return false + } + + const parsedDataset = parseJsonText(rawText) + if (!parsedDataset.ok) { + window.localStorage.removeItem(this.storageKey(datasetKey)) + return false + } + + this.commitDataset(datasetKey, parsedDataset.data, { + rawText, + snapshotText, + sourceLabel: + parsedStoredValue.data.sourceLabel || + `Local storage: ${this.datasets[datasetKey].fileName}`, + fileHandle: null, + loadError: '' + }) + + return true + }, + persistUiState() { + if (!this.canUseLocalStorage()) { + return + } + + window.localStorage.setItem( + this.uiStateStorageKey(), + JSON.stringify({ + selectedDatasetKey: this.selectedDatasetKey, + selectedFunctionByDataset: this.selectedFunctionByDataset, + selectedFundingSourceByDataset: this.selectedFundingSourceByDataset, + selectedTestFundingSourceByDataset: + this.selectedTestFundingSourceByDataset, + selectedTestTypeByScope: this.selectedTestTypeByScope + }) + ) + }, + restoreUiState() { + if (!this.canUseLocalStorage()) { + return + } + + const storedValue = window.localStorage.getItem(this.uiStateStorageKey()) + if (!storedValue) { + return + } + + const parsedStoredValue = parseJsonText(storedValue) + if (!parsedStoredValue.ok || !isPlainObject(parsedStoredValue.data)) { + window.localStorage.removeItem(this.uiStateStorageKey()) + return + } + + const state = parsedStoredValue.data + + if ( + typeof state.selectedDatasetKey === 'string' && + DATASET_DEFS.some(def => def.key === state.selectedDatasetKey) + ) { + this.selectedDatasetKey = state.selectedDatasetKey + } + + if (isPlainObject(state.selectedFunctionByDataset)) { + Object.assign(this.selectedFunctionByDataset, state.selectedFunctionByDataset) + } + + if (isPlainObject(state.selectedFundingSourceByDataset)) { + Object.assign( + this.selectedFundingSourceByDataset, + state.selectedFundingSourceByDataset + ) + } + + if (isPlainObject(state.selectedTestFundingSourceByDataset)) { + Object.assign( + this.selectedTestFundingSourceByDataset, + state.selectedTestFundingSourceByDataset + ) + } + + if (isPlainObject(state.selectedTestTypeByScope)) { + this.selectedTestTypeByScope = { + ...state.selectedTestTypeByScope + } + } + }, notify(message, type = 'info', caption = '') { Quasar.Notify.create({ message, @@ -3416,7 +3656,7 @@ return parsed.data }, - persistTestCallParams(testEntry) { + persistTestCallParams(testEntry, options = {}) { if (!this.currentFunctionName) { return } @@ -3432,7 +3672,13 @@ } catch (error) { testEntry.callParamsError = error instanceof Error ? error.message : String(error) - this.notify('Could not save call params.', 'negative', testEntry.callParamsError) + if (!options.silent) { + this.notify( + 'Could not save call params.', + 'negative', + testEntry.callParamsError + ) + } return } @@ -3448,7 +3694,7 @@ } ) }, - persistTestExpected(testEntry) { + persistTestExpected(testEntry, options = {}) { if (!this.currentFunctionName) { return } @@ -3464,11 +3710,13 @@ } catch (error) { testEntry.expectedError = error instanceof Error ? error.message : String(error) - this.notify( - `Could not save ${testEntry.expectedLabel.toLowerCase()}.`, - 'negative', - testEntry.expectedError - ) + if (!options.silent) { + this.notify( + `Could not save ${testEntry.expectedLabel.toLowerCase()}.`, + 'negative', + testEntry.expectedError + ) + } return } @@ -3484,7 +3732,7 @@ } ) }, - persistTestMockSet(testEntry, mockSet) { + persistTestMockSet(testEntry, mockSet, options = {}) { if (!this.currentFunctionName || !this.currentTestFundingSourceName) { return } @@ -3506,11 +3754,13 @@ if (!mockName) { if ((binding.items || []).length) { mockSet.error = 'Select a function mock before editing its overrides.' - this.notify( - 'Could not save test mock definition.', - 'negative', - mockSet.error - ) + if (!options.silent) { + this.notify( + 'Could not save test mock definition.', + 'negative', + mockSet.error + ) + } return } @@ -3519,11 +3769,13 @@ if (Object.prototype.hasOwnProperty.call(nextValue, mockName)) { mockSet.error = `Duplicate function mock selected: ${mockName}` - this.notify( - 'Could not save test mock definition.', - 'negative', - mockSet.error - ) + if (!options.silent) { + this.notify( + 'Could not save test mock definition.', + 'negative', + mockSet.error + ) + } return } @@ -3555,11 +3807,13 @@ item.requestBodyError = error instanceof Error ? error.message : String(error) mockSet.error = item.requestBodyError - this.notify( - 'Could not save test mock definition.', - 'negative', - mockSet.error - ) + if (!options.silent) { + this.notify( + 'Could not save test mock definition.', + 'negative', + mockSet.error + ) + } return } } @@ -3578,11 +3832,13 @@ item.responseError = error instanceof Error ? error.message : String(error) mockSet.error = item.responseError - this.notify( - 'Could not save test mock definition.', - 'negative', - mockSet.error - ) + if (!options.silent) { + this.notify( + 'Could not save test mock definition.', + 'negative', + mockSet.error + ) + } return } } @@ -3675,7 +3931,7 @@ rows.splice(rowIndex, 1) this.persistFundingSourceSettings() }, - persistFundingSourceSettings() { + persistFundingSourceSettings(options = {}) { if (!this.currentFundingSourceName) { return } @@ -3699,11 +3955,13 @@ } catch (error) { row.error = error instanceof Error ? error.message : String(error) - this.notify( - `Could not save setting "${key || 'new setting'}".`, - 'negative', - row.error - ) + if (!options.silent) { + this.notify( + `Could not save setting "${key || 'new setting'}".`, + 'negative', + row.error + ) + } return } } @@ -3736,7 +3994,7 @@ rows.splice(rowIndex, 1) this.persistFunctionMocks() }, - persistFunctionMocks() { + persistFunctionMocks(options = {}) { if (!this.currentFunctionName || !this.currentTestFundingSourceName) { return } @@ -3760,11 +4018,13 @@ } catch (error) { row.error = error instanceof Error ? error.message : String(error) - this.notify( - `Could not save mock "${name || 'new mock'}".`, - 'negative', - row.error - ) + if (!options.silent) { + this.notify( + `Could not save mock "${name || 'new mock'}".`, + 'negative', + row.error + ) + } return } } @@ -3890,6 +4150,31 @@ ) } } + + this.persistDatasetToStorage(datasetKey) + this.persistUiState() + }, + syncEditorFromRawText(datasetKey, options = {}) { + const dataset = this.datasets[datasetKey] + const parsed = parseJsonText(dataset.rawText) + + if (!parsed.ok) { + dataset.parseError = `Invalid JSON in ${dataset.fileName}: ${parsed.error}` + if (!options.silent) { + this.notify('Could not apply the editor contents.', 'negative') + } + return false + } + + this.commitDataset(datasetKey, parsed.data, { + rawText: dataset.rawText, + snapshotText: dataset.snapshotText, + fileHandle: dataset.fileHandle, + sourceLabel: dataset.sourceLabel, + loadError: dataset.loadError + }) + + return true }, validateEditor(datasetKey) { const dataset = this.datasets[datasetKey]