Show a range-handling pattern in the function-editing UI

A user function parameter already worked as a range when passed
straight into a range-capable call (SUM/AVG/MIN/MAX/another user
function) -- expandUserFunctions splices the caller's RangeNode in
unchanged, so it behaves exactly like a normal formula (and #VALUE!
outside a function-argument position, also exactly like normal). Added
2 tests confirming this (incl. recalculation on a change inside the
passed range) and a hint under the Formel field in
CustomFunctionsModal.vue showing the pattern (SPANNE(bereich) =
MAX(bereich) - MIN(bereich)), since nothing surfaced that this was
possible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-02 15:59:56 +02:00
co-authored by Claude Sonnet 5
parent 1046b22e96
commit fdde4ed5a7
3 changed files with 47 additions and 4 deletions
+7
View File
@@ -146,6 +146,13 @@ function close() {
placeholder="z. B. (revenue - cost) / revenue"
/>
</label>
<p class="hint">
Ein Parameter kann auch ein <strong>Bereich</strong> sein, solange er
direkt an eine Funktion wie <code>SUM</code>/<code>AVG</code>/
<code>MIN</code>/<code>MAX</code> übergeben wird — z. B.
<code>SPANNE(bereich) = MAX(bereich) - MIN(bereich)</code>, aufgerufen
als <code>=SPANNE(Umsatz!B2:E6)</code>.
</p>
<p v-if="formError" class="form-error">⚠ {{ formError }}</p>
</template>
@@ -199,6 +199,34 @@ describe('Workbook integration: user-defined functions end-to-end', () => {
expect((val(wb, 's-umsatz', 'B9') as FormulaError).code).toBe('#DIV/0!');
});
it('a parameter can be a range, as long as it lands in a function-call argument position', () => {
// SPANNE(bereich) = MAX(bereich) - MIN(bereich) -- "bereich" (ParamNode)
// gets substituted with the caller's RangeNode, splicing a normal
// MAX(Umsatz!B2:E2) / MIN(Umsatz!B2:E2) call into the expanded AST.
const umsatz = sheet('s-umsatz', 'Umsatz', {
B2: '1200', C2: '1450', D2: '1600', E2: '1720',
B9: '=SPANNE(B2:E2)',
});
const wb = new Workbook(
[umsatz],
[def({ id: 'f1', name: 'SPANNE', params: ['bereich'], body: 'MAX(bereich) - MIN(bereich)' })],
);
expect(val(wb, 's-umsatz', 'B9')).toBe(1720 - 1200);
// recalculates when a cell *inside* the range argument changes
wb.setCell('s-umsatz', 1, 1, '1000'); // B2: 1200 -> 1000
expect(val(wb, 's-umsatz', 'B9')).toBe(1720 - 1000);
});
it('a range parameter used outside a function-call argument position is #VALUE!, same as a bare range in an ordinary formula', () => {
const umsatz = sheet('s-umsatz', 'Umsatz', {
B2: '1200', C2: '1450',
B9: '=BAD(B2:C2)',
});
const wb = new Workbook([umsatz], [def({ id: 'f1', name: 'BAD', params: ['bereich'], body: 'bereich + 1' })]);
expect((val(wb, 's-umsatz', 'B9') as FormulaError).code).toBe('#VALUE!');
});
it('debugOrder places a function-body-only precedent before its dependent', () => {
const umsatz = sheet('s-umsatz', 'Umsatz', {
B2: '1200', E1: '3',