Context
src/util/Function.mjs (230 lines, 9 exported functions) carries zero unit coverage while its siblings just gained it: Neo.util.Array (#15124), Neo.util.String (#15118), Neo.util.Json.extract (#15117). It is a high-reuse module — buffer/debounce/throttle back every rate-limited handler in the framework, and createSequence/intercept/bindAppend are core to the observable + method-wrapping machinery — so an untested regression here is broad-blast and silent.
The Problem
The module is entirely testable in isolation (zero imports; only the Neo.isNumber/Neo.isString/Neo.emptyFn globals the unit harness already provides), yet has no spec — so the timer semantics (leading-edge debounce, trailing throttle, buffer cancellation) and the method-wrapping lifecycle (createSequence → unSequence restore-original) are unverified. These are exactly the behaviors most prone to a subtle regression: an off-by-one in a timer branch, or a wrapper that fails to restore the original method.
The Architectural Reality
src/util/Function.mjs exports: bindAppend, buffer, createInterceptor, createSequence, debounce, intercept, resolveCallback, throttle, unSequence. Timer functions (buffer/debounce/throttle) use setTimeout/Date.now — deterministically testable by stubbing them (the test/playwright/unit/app/devindex/GitHubService.spec.mjs retry tests are the in-repo precedent for setTimeout/Date.now stubbing). createSequence/unSequence use private Symbols (sequencedFns, originalMethod) — witnessed via observable behavior (call-order, original-method restoration), never by reaching the Symbols. Spec lands at test/playwright/unit/util/Function.spec.mjs, the sibling of Array.spec.mjs / String.spec.mjs / Json.spec.mjs.
The Fix
One spec: test/playwright/unit/util/Function.spec.mjs covering each exported function's contract — the pure ones by direct assertion (bindAppend arg-append; createInterceptor/intercept value-transform + prevent-on-false; resolveCallback string / up.-parent-chain resolution; createSequence/unSequence call-order + restore) and the timer ones with stubbed setTimeout/Date.now (leading-edge debounce fires immediately then coalesces; throttle rate-limits + trailing-fires; buffer coalesces + .cancel()/.isPending). Uses the test/playwright/setup.mjs harness like the sibling util specs.
Acceptance Criteria
Out of Scope
- Any change to
src/util/Function.mjs itself (coverage only; if a test surfaces a real defect, its fix is a separate ticket).
- The other
src/util coverage gaps (Date, Matrix, Performance, KeyNavigation, …) — each its own leaf per the #15117/#15118/#15124 standalone precedent.
Related
Sibling precedents: #15117 (Json.extract), #15118 (String), #15124 (Array). Target: src/util/Function.mjs.
Origin Session ID: 3e5f61a5-35d0-4f3d-8805-54f63bebed70
Retrieval Hint: "Neo.util.Function unit coverage debounce throttle buffer createSequence intercept spec setTimeout stub"
Context
src/util/Function.mjs(230 lines, 9 exported functions) carries zero unit coverage while its siblings just gained it:Neo.util.Array(#15124),Neo.util.String(#15118),Neo.util.Json.extract(#15117). It is a high-reuse module —buffer/debounce/throttleback every rate-limited handler in the framework, andcreateSequence/intercept/bindAppendare core to the observable + method-wrapping machinery — so an untested regression here is broad-blast and silent.The Problem
The module is entirely testable in isolation (zero imports; only the
Neo.isNumber/Neo.isString/Neo.emptyFnglobals the unit harness already provides), yet has no spec — so the timer semantics (leading-edge debounce, trailing throttle, buffer cancellation) and the method-wrapping lifecycle (createSequence→unSequencerestore-original) are unverified. These are exactly the behaviors most prone to a subtle regression: an off-by-one in a timer branch, or a wrapper that fails to restore the original method.The Architectural Reality
src/util/Function.mjsexports:bindAppend,buffer,createInterceptor,createSequence,debounce,intercept,resolveCallback,throttle,unSequence. Timer functions (buffer/debounce/throttle) usesetTimeout/Date.now— deterministically testable by stubbing them (thetest/playwright/unit/app/devindex/GitHubService.spec.mjsretry tests are the in-repo precedent forsetTimeout/Date.nowstubbing).createSequence/unSequenceuse privateSymbols (sequencedFns,originalMethod) — witnessed via observable behavior (call-order, original-method restoration), never by reaching the Symbols. Spec lands attest/playwright/unit/util/Function.spec.mjs, the sibling ofArray.spec.mjs/String.spec.mjs/Json.spec.mjs.The Fix
One spec:
test/playwright/unit/util/Function.spec.mjscovering each exported function's contract — the pure ones by direct assertion (bindAppendarg-append;createInterceptor/interceptvalue-transform + prevent-on-false;resolveCallbackstring /up.-parent-chain resolution;createSequence/unSequencecall-order + restore) and the timer ones with stubbedsetTimeout/Date.now(leading-edge debounce fires immediately then coalesces; throttle rate-limits + trailing-fires; buffer coalesces +.cancel()/.isPending). Uses thetest/playwright/setup.mjsharness like the sibling util specs.Acceptance Criteria
test/playwright/unit/util/Function.spec.mjsexists and covers all 9 exported functions.setTimeout/Date.now), never real waits.createSequence/unSequenceverified via observable behavior (call order + original-method restoration), not by reaching privateSymbols.UNIT_TEST_MODE=true),--workers=1and parallel.Out of Scope
src/util/Function.mjsitself (coverage only; if a test surfaces a real defect, its fix is a separate ticket).src/utilcoverage gaps (Date,Matrix,Performance,KeyNavigation, …) — each its own leaf per the #15117/#15118/#15124 standalone precedent.Related
Sibling precedents: #15117 (Json.extract), #15118 (String), #15124 (Array). Target:
src/util/Function.mjs.Origin Session ID: 3e5f61a5-35d0-4f3d-8805-54f63bebed70
Retrieval Hint: "Neo.util.Function unit coverage debounce throttle buffer createSequence intercept spec setTimeout stub"