diff --git a/tests/wallets/index.html b/tests/wallets/index.html index 439b6f26e..d50e6bccd 100644 --- a/tests/wallets/index.html +++ b/tests/wallets/index.html @@ -515,6 +515,43 @@ min-height: 240px; } + .test-mock-binding { + margin-top: 12px; + padding: 12px; + border-radius: 16px; + border: 1px solid rgba(25, 54, 61, 0.08); + background: rgba(255, 255, 255, 0.7); + } + + .test-mock-binding:first-of-type { + margin-top: 14px; + } + + .test-mock-binding-header { + display: flex; + gap: 12px; + align-items: flex-start; + justify-content: space-between; + } + + .test-mock-binding-body { + flex: 1 1 auto; + min-width: 0; + } + + .test-mock-item-list { + margin-top: 12px; + display: grid; + gap: 12px; + } + + .test-mock-item-card { + padding: 12px; + border-radius: 14px; + border: 1px solid rgba(25, 54, 61, 0.08); + background: rgba(25, 54, 61, 0.03); + } + .detail-label { color: var(--studio-muted); font-size: 0.74rem; @@ -1024,6 +1061,10 @@ grid-template-columns: 1fr; } + .test-mock-binding-header { + flex-direction: column; + } + .settings-table-header { display: none; } @@ -1698,23 +1739,127 @@
{{ mockSet.label }}
- {{ Object.keys(mockSet.value || {}).length }} mock names + {{ mockSet.bindings.length }} function mocks - - + + + + +
+
+
+ + + + +
+ +
+ +
+
+ +
+
+
+
+

Test Mock Definition {{ itemIndex + 1 }}

+
+ +
+ + +
+ {{ item.error }} +
+
+
+ +
+ No objects are defined in this mock array yet. +
+ +
+ +
+
+ +
+ +
+
{{ mockSet.error }}
@@ -2400,6 +2545,24 @@ this.currentFundingSourceName ) }, + currentFunctionMockNameOptions() { + const seen = new Set() + + return this.currentFunctionMockRows + .map(row => row.name.trim()) + .filter(name => { + if (!name || seen.has(name)) { + return false + } + + seen.add(name) + return true + }) + .map(name => ({ + label: name, + value: name + })) + }, currentFunctionTestsForFundingSource() { if (!this.currentFunction || !this.currentFundingSource) { return [] @@ -2436,9 +2599,10 @@ this.currentFundingSource.name ) : '' + const walletMockValueIsArray = Array.isArray(rawWalletMockValue) const rawMockSets = rawWalletMockValue === undefined ? [] - : (Array.isArray(rawWalletMockValue) + : (walletMockValueIsArray ? rawWalletMockValue : [rawWalletMockValue] ).map((mockSet, index) => ({ @@ -2446,11 +2610,27 @@ ? `Mock set ${index + 1}` : 'Mock set', setIndex: index, + walletMockValueIsArray, path: Array.isArray(rawWalletMockValue) ? `${rawWalletPath}[${index}]` : rawWalletPath, value: deepClone(mockSet), - valueText: safeStringify(mockSet), + metaFields: Object.fromEntries( + Object.entries(isPlainObject(mockSet) ? mockSet : {}).filter( + ([key]) => key === 'description' + ) + ), + bindings: Object.entries(isPlainObject(mockSet) ? mockSet : {}) + .filter(([key]) => key !== 'description') + .map(([key, value]) => ({ + selectedMockName: key, + itemTexts: (Array.isArray(value) ? value : [value]).map( + item => ({ + text: safeStringify(item), + error: '' + }) + ) + })), error: '' })) const variants = testsForFundingSource( @@ -2681,6 +2861,72 @@ error: '' } }, + createTestMockBindingDraft(mockName = '', items = []) { + const normalizedItems = Array.isArray(items) + ? items + : items === undefined + ? [] + : [items] + + return { + selectedMockName: mockName, + itemTexts: normalizedItems.map(item => ({ + text: safeStringify(item), + error: '' + })) + } + }, + testMockBindingOptions(selectedMockName = '') { + const names = this.currentFunctionMockNameOptions.map( + option => option.value + ) + + if (selectedMockName && !names.includes(selectedMockName)) { + names.unshift(selectedMockName) + } + + return names.map(name => ({ + label: name, + value: name + })) + }, + nextAvailableTestMockName(mockSet) { + const usedNames = new Set( + (mockSet.bindings || []) + .map(binding => (binding.selectedMockName || '').trim()) + .filter(Boolean) + ) + + const availableName = this.currentFunctionMockNameOptions.find( + option => !usedNames.has(option.value) + ) + + return availableName?.value || '' + }, + addTestMockBinding(testEntry, mockSet) { + mockSet.bindings.push( + this.createTestMockBindingDraft( + this.nextAvailableTestMockName(mockSet), + [] + ) + ) + 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({}), + error: '' + }) + this.persistTestMockSet(testEntry, mockSet) + }, + removeTestMockBindingItem(testEntry, mockSet, binding, itemIndex) { + binding.itemTexts.splice(itemIndex, 1) + this.persistTestMockSet(testEntry, mockSet) + }, getSettingsDraftRows(datasetKey, fundingSourceName) { const draftKey = this.makeSettingsDraftKey(datasetKey, fundingSourceName) return this.settingsDrafts[draftKey] || [] @@ -2937,28 +3183,73 @@ } ) }, - persistTestMockDefinition(testEntry, mockSet) { + persistTestMockSet(testEntry, mockSet) { if (!this.currentFunctionName || !this.currentFundingSourceName) { return } mockSet.error = '' + ;(mockSet.bindings || []).forEach(binding => { + ;(binding.itemTexts || []).forEach(item => { + item.error = '' + }) + }) - let nextValue - try { - nextValue = this.parseEditableTestJson( - mockSet.valueText, - 'Test Mock Definition' - ) - } catch (error) { - mockSet.error = - error instanceof Error ? error.message : String(error) - this.notify( - 'Could not save test mock definition.', - 'negative', - mockSet.error - ) - return + const nextValue = deepClone(mockSet.metaFields || {}) + + for (const binding of mockSet.bindings || []) { + const mockName = (binding.selectedMockName || '').trim() + + if (!mockName) { + if ((binding.itemTexts || []).length) { + mockSet.error = 'Select a function mock before editing its overrides.' + this.notify( + 'Could not save test mock definition.', + 'negative', + mockSet.error + ) + return + } + + continue + } + + 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 + ) + return + } + + const parsedItems = [] + + for (let itemIndex = 0; itemIndex < (binding.itemTexts || []).length; itemIndex += 1) { + const item = binding.itemTexts[itemIndex] + + try { + parsedItems.push( + this.parseEditableTestJson( + item.text, + `${mockName} item ${itemIndex + 1}` + ) + ) + } catch (error) { + item.error = + error instanceof Error ? error.message : String(error) + mockSet.error = item.error + this.notify( + 'Could not save test mock definition.', + 'negative', + mockSet.error + ) + return + } + } + + nextValue[mockName] = parsedItems } this.applyDatasetMutation( @@ -2972,8 +3263,13 @@ const walletMockValue = rawTest.mocks[this.currentFundingSourceName] - if (Array.isArray(walletMockValue)) { - walletMockValue[mockSet.setIndex] = nextValue + if (Array.isArray(walletMockValue) || mockSet.walletMockValueIsArray) { + if (!Array.isArray(rawTest.mocks[this.currentFundingSourceName])) { + rawTest.mocks[this.currentFundingSourceName] = [] + } + + rawTest.mocks[this.currentFundingSourceName][mockSet.setIndex] = + nextValue } else { rawTest.mocks[this.currentFundingSourceName] = nextValue }