This commit is contained in:
Vlad Stan
2026-03-26 15:09:40 +02:00
parent 8649129156
commit 33fef98d9e
+247 -26
View File
@@ -327,6 +327,18 @@
color: var(--studio-muted); color: var(--studio-muted);
} }
.fixture-select-icon {
font-family: 'Material Icons';
font-feature-settings: 'liga';
font-style: normal;
font-weight: 400;
font-size: 1.2rem;
line-height: 1;
color: var(--studio-muted);
user-select: none;
pointer-events: none;
}
.native-input, .native-input,
.native-textarea { .native-textarea {
width: 100%; width: 100%;
@@ -485,6 +497,7 @@
.test-detail-grid { .test-detail-grid {
display: grid; display: grid;
gap: 12px; gap: 12px;
grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.detail-panel { .detail-panel {
@@ -494,6 +507,14 @@
background: rgba(255, 255, 255, 0.72); background: rgba(255, 255, 255, 0.72);
} }
.test-json-editor {
min-height: 220px;
}
.test-mock-editor {
min-height: 240px;
}
.detail-label { .detail-label {
color: var(--studio-muted); color: var(--studio-muted);
font-size: 0.74rem; font-size: 0.74rem;
@@ -784,6 +805,19 @@
.test-type-tabs .q-tab { .test-type-tabs .q-tab {
flex: 1 1 0; flex: 1 1 0;
min-width: 0; min-width: 0;
border-radius: 12px;
color: var(--q-primary);
}
.test-type-tabs .q-tab--active {
background: var(--q-primary);
color: white;
}
.test-type-tabs .q-tab--active,
.test-type-tabs .q-tab--active .q-tab__label,
.test-type-tabs .q-tab--active .text-weight-bold {
color: white !important;
} }
.test-type-panels { .test-type-panels {
@@ -984,6 +1018,7 @@
} }
.mock-source-grid, .mock-source-grid,
.test-detail-grid,
.settings-row-grid, .settings-row-grid,
.settings-table-grid { .settings-table-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -1153,9 +1188,13 @@
dense dense
options-dense options-dense
behavior="menu" behavior="menu"
dropdown-icon="expand_more" hide-dropdown-icon
class="fixture-select" class="fixture-select"
></q-select> >
<template v-slot:append>
<span class="fixture-select-icon" aria-hidden="true">expand_more</span>
</template>
</q-select>
<div v-if="currentFundingSource" class="q-mt-md"> <div v-if="currentFundingSource" class="q-mt-md">
<div v-if="currentFundingSource.skip" class="pill-row q-mb-md"> <div v-if="currentFundingSource.skip" class="pill-row q-mb-md">
@@ -1326,9 +1365,13 @@
dense dense
options-dense options-dense
behavior="menu" behavior="menu"
dropdown-icon="expand_more" hide-dropdown-icon
class="fixture-select" class="fixture-select"
></q-select> >
<template v-slot:append>
<span class="fixture-select-icon" aria-hidden="true">expand_more</span>
</template>
</q-select>
<details <details
v-if="currentFunction && currentFundingSource" v-if="currentFunction && currentFundingSource"
@@ -1553,7 +1596,6 @@
<q-tab-panels <q-tab-panels
v-if="currentSelectedTestTypeGroup" v-if="currentSelectedTestTypeGroup"
v-model="currentSelectedTestTypeKey" v-model="currentSelectedTestTypeKey"
animated
class="test-type-panels" class="test-type-panels"
> >
<q-tab-panel <q-tab-panel
@@ -1608,25 +1650,46 @@
<div class="test-detail-grid"> <div class="test-detail-grid">
<div class="detail-panel"> <div class="detail-panel">
<json-panel <label
label="Call Params" class="field-label"
:value="testEntry.call_params || {}" :for="`test-call-params-${group.key}-${testEntry.index}-${entryIndex}`"
max-height="220px" >
></json-panel> Call Params
</label>
<textarea
:id="`test-call-params-${group.key}-${testEntry.index}-${entryIndex}`"
v-model="testEntry.callParamsText"
class="native-textarea test-json-editor"
spellcheck="false"
@blur="persistTestCallParams(testEntry)"
></textarea>
<div v-if="testEntry.callParamsError" class="settings-error q-mt-sm">
{{ testEntry.callParamsError }}
</div>
</div> </div>
<div class="detail-panel"> <div class="detail-panel">
<json-panel <label
:label="testEntry.expectedLabel" class="field-label"
:value="testEntry.expectedBlock" :for="`test-expected-${group.key}-${testEntry.index}-${entryIndex}`"
empty-label="No expected result block" >
max-height="220px" {{ testEntry.expectedLabel }}
></json-panel> </label>
<textarea
:id="`test-expected-${group.key}-${testEntry.index}-${entryIndex}`"
v-model="testEntry.expectedText"
class="native-textarea test-json-editor"
spellcheck="false"
@blur="persistTestExpected(testEntry)"
></textarea>
<div v-if="testEntry.expectedError" class="settings-error q-mt-sm">
{{ testEntry.expectedError }}
</div>
</div> </div>
</div> </div>
<div class="mock-list q-mt-lg" v-if="testEntry.rawMockSets.length"> <div class="mock-list q-mt-lg" v-if="testEntry.rawMockSets.length">
<div <div
v-for="mockSet in testEntry.rawMockSets" v-for="(mockSet, mockSetIndex) in testEntry.rawMockSets"
:key="mockSet.path" :key="mockSet.path"
class="mock-card" class="mock-card"
> >
@@ -1639,12 +1702,22 @@
</q-chip> </q-chip>
</div> </div>
<json-panel <label
class="q-mt-md" class="field-label q-mt-md"
label="Test Mock Definition" :for="`test-mock-definition-${group.key}-${testEntry.index}-${mockSetIndex}`"
:value="mockSet.value" >
max-height="320px" Test Mock Definition
></json-panel> </label>
<textarea
:id="`test-mock-definition-${group.key}-${testEntry.index}-${mockSetIndex}`"
v-model="mockSet.valueText"
class="native-textarea test-mock-editor"
spellcheck="false"
@blur="persistTestMockDefinition(testEntry, mockSet)"
></textarea>
<div v-if="mockSet.error" class="settings-error q-mt-sm">
{{ mockSet.error }}
</div>
</div> </div>
</div> </div>
@@ -2347,6 +2420,15 @@
const rawWalletMockValue = hasMockEntry const rawWalletMockValue = hasMockEntry
? test.raw.mocks[this.currentFundingSource.name] ? test.raw.mocks[this.currentFundingSource.name]
: undefined : undefined
const hasExpectField = Object.prototype.hasOwnProperty.call(
test.raw || {},
'expect'
)
const expectedField = hasExpectField
? 'expect'
: Object.prototype.hasOwnProperty.call(test.raw || {}, 'expect_error')
? 'expect_error'
: 'expect'
const rawWalletPath = hasMockEntry const rawWalletPath = hasMockEntry
? this.testWalletPath( ? this.testWalletPath(
this.currentFunction.name, this.currentFunction.name,
@@ -2363,10 +2445,13 @@
label: Array.isArray(rawWalletMockValue) label: Array.isArray(rawWalletMockValue)
? `Mock set ${index + 1}` ? `Mock set ${index + 1}`
: 'Mock set', : 'Mock set',
setIndex: index,
path: Array.isArray(rawWalletMockValue) path: Array.isArray(rawWalletMockValue)
? `${rawWalletPath}[${index}]` ? `${rawWalletPath}[${index}]`
: rawWalletPath, : rawWalletPath,
value: deepClone(mockSet) value: deepClone(mockSet),
valueText: safeStringify(mockSet),
error: ''
})) }))
const variants = testsForFundingSource( const variants = testsForFundingSource(
this.currentFundingSource, this.currentFundingSource,
@@ -2416,8 +2501,20 @@
variantCount: variants.length, variantCount: variants.length,
status, status,
note, note,
expectedBlock: test.expect ?? test.expect_error, callParamsText: safeStringify(test.call_params || {}),
expectedLabel: test.expect ? 'Expect' : 'Expect Error' callParamsError: '',
expectedField,
expectedBlock:
expectedField === 'expect'
? (test.expect === undefined ? null : test.expect)
: (test.expect_error === undefined ? null : test.expect_error),
expectedText: safeStringify(
expectedField === 'expect'
? (test.expect === undefined ? null : test.expect)
: (test.expect_error === undefined ? null : test.expect_error)
),
expectedError: '',
expectedLabel: expectedField === 'expect' ? 'Expect' : 'Expect Error'
} }
}) })
}, },
@@ -2763,6 +2860,130 @@
return mock return mock
}, },
parseEditableTestJson(text, label) {
const parsed = parseJsonText(text)
if (!parsed.ok) {
throw new Error(`${label}: ${parsed.error}`)
}
return parsed.data
},
persistTestCallParams(testEntry) {
if (!this.currentFunctionName) {
return
}
testEntry.callParamsError = ''
let nextValue
try {
nextValue = this.parseEditableTestJson(
testEntry.callParamsText,
'Call Params'
)
} catch (error) {
testEntry.callParamsError =
error instanceof Error ? error.message : String(error)
this.notify('Could not save call params.', 'negative', testEntry.callParamsError)
return
}
this.applyDatasetMutation(
this.selectedDatasetKey,
data => {
data.functions[this.currentFunctionName].tests[testEntry.index].call_params =
nextValue
},
{
preserveSettingsDrafts: true,
preserveFunctionMockDrafts: true
}
)
},
persistTestExpected(testEntry) {
if (!this.currentFunctionName) {
return
}
testEntry.expectedError = ''
let nextValue
try {
nextValue = this.parseEditableTestJson(
testEntry.expectedText,
testEntry.expectedLabel
)
} catch (error) {
testEntry.expectedError =
error instanceof Error ? error.message : String(error)
this.notify(
`Could not save ${testEntry.expectedLabel.toLowerCase()}.`,
'negative',
testEntry.expectedError
)
return
}
this.applyDatasetMutation(
this.selectedDatasetKey,
data => {
const rawTest = data.functions[this.currentFunctionName].tests[testEntry.index]
rawTest[testEntry.expectedField || 'expect'] = nextValue
},
{
preserveSettingsDrafts: true,
preserveFunctionMockDrafts: true
}
)
},
persistTestMockDefinition(testEntry, mockSet) {
if (!this.currentFunctionName || !this.currentFundingSourceName) {
return
}
mockSet.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
}
this.applyDatasetMutation(
this.selectedDatasetKey,
data => {
const rawTest = data.functions[this.currentFunctionName].tests[testEntry.index]
if (!rawTest.mocks) {
rawTest.mocks = {}
}
const walletMockValue = rawTest.mocks[this.currentFundingSourceName]
if (Array.isArray(walletMockValue)) {
walletMockValue[mockSet.setIndex] = nextValue
} else {
rawTest.mocks[this.currentFundingSourceName] = nextValue
}
},
{
preserveSettingsDrafts: true,
preserveFunctionMockDrafts: true
}
)
},
applyDatasetMutation(datasetKey, mutator, options = {}) { applyDatasetMutation(datasetKey, mutator, options = {}) {
const dataset = this.datasets[datasetKey] const dataset = this.datasets[datasetKey]
if (!dataset.data) { if (!dataset.data) {