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
}