Skip to content

test(fjl): #124 assert $-prefixed methods are actually curried - #133

Open
elycruz wants to merge 1 commit into
mainfrom
124-curry-tests
Open

test(fjl): #124 assert $-prefixed methods are actually curried#133
elycruz wants to merge 1 commit into
mainfrom
124-curry-tests

Conversation

@elycruz

@elycruz elycruz commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Adds a shared currying assertion helper and a data-driven suite that asserts every $-prefixed export in fjl is actually curried. The target list is derived from the package's public export surface at runtime (not hand-written), so curried methods added later are picked up automatically and fail the suite until they get argument fixtures.

Closes #124

Work-unit id: 124-curry-tests

How the helper works

packages/fjl/tests/curry-helpers.ts exports assertCurriedCase(curried, uncurried, case, reusablePartial). fjl uses idiomatic currying (f(a)(b)(c)) rather than the deprecated curry*/argument-tuple form, so the assertions are written against that. Per argument case it asserts:

  1. Partial application returns a function — every application before the last one yields a function.
  2. Full application agrees with the un-curried sibling$f(a)(b)(c) deep-equals f(a, b, c).
  3. Partials are re-usable — a partially applied method completed twice (with fresh tails) yields the same result both times, i.e. currying returns a fresh function rather than a consumed/stateful one.

Fixture shape (CurryCase / CurrySpec):

  • args is an argument factory, not a literal list, so methods that mutate their arguments (push, pushN, defineProp, …) get clean args for every one of the ~5 invocations an assertion makes.
  • groups describes the application split, e.g. [1, 2]f(a)(b, c) — needed for the variadic-tail methods ($append, $complement, $zipN, $zipWithN, $toShortest, $pushN, $range, $objComplement, $bind) and the deeper ones ($defineProp[1, 1, 2], $errorIfNotType[1, 1, 1, 2]).
  • finalize maps a result into something comparable — for methods returning functions ($bind, $trampoline), generators ($iterate), or communicating by side effect ($forEach).
  • sibling / uncurried handle the methods whose un-curried counterpart isn't simply the $-name minus its $ ($normalizeStepnormalizeStepOrThrow; $getErrorIfNotType(s)Thrower, whose un-curried equivalent needs assembling).
  • knownFailure registers the case via it.failing, so a real bug is pinned rather than papered over — and the suite goes red if the method is ever fixed without removing the note.
  • notCurried documents a $-prefixed method that verifiably isn't curried.

packages/fjl/tests/test-currying.ts holds the registry and the driver. Two meta-tests keep registry and export surface in sync:

  • "covers every $-prefixed export" — fails listing any uncovered $ export.
  • "contains no entries for non-existent exports" — fails on stale fixtures.

Coverage

118 of 118 curried methods asserted:

  • 109 / 109 $-prefixed top-level exports of packages/fjl/src (list/, list/utils/, object/, string/, boolean/, number/, function/, errorThrowing/, _platform/).
  • 9 / 9 $-prefixed, flipped-and-curried Object statics on the fjl.native export ($assign, $create, $defineProperties, $defineProperty, $getOwnPropertyDescriptor, $groupBy, $hasOwn, $is, $setPrototypeOf) — same convention, same helper.

(The ~112 $ identifiers greppable in src include $zip4/zip4, which list/index.ts never re-exports, $concat, which is shadowed by list/concat.ts and reachable only as $append, and the $UnionBy/$defineProperties type/doc mentions.)

Methods that failed the currying assertion

1. $assignDeep — real behavioural bug (registered it.failing, not fixed here)

$assignDeep = (obj0: any) => (...objs: any[]) => assignDeep(obj0, objs)
//                                                              ^^^^ should be `...objs`

assignDeep's signature is (obj0, ...objs), so the curried version merges the array objs into obj0:

assignDeep({a: 1}, {b: 2}, {c: 3})  // => {a: 1, b: 2, c: 3}     (correct)
$assignDeep({a: 1})({b: 2}, {c: 3}) // => {0: {b: 2}, 1: {c: 3}, a: 1}  (wrong)

2. $objUnion — same bug (alias)

object/setTheory.ts defines $objUnion = $assignDeep, so it inherits the defect:

objUnion({a: 1}, {b: 2})  // => {a: 1, b: 2}
$objUnion({a: 1})({b: 2}) // => {0: {b: 2}, a: 1}

What I did: per the work-unit brief, no library behaviour was changed (a one-character fix here would collide with concurrent work on 32-type-system-cleanup / 121-generators). Both are registered with it.failing and a knownFailure reason, and src/object/assignDeep.ts carries a @todo known-bug doc note. These two need their own ticket; when fixed, the it.failing registrations must be dropped (the suite will go red otherwise, by design).

3. $trampoline — not curried; documentation corrected

$trampoline = (fn, fnName?) => trampoline(fn, fnName)

It takes the same argument tuple as trampoline, i.e. it's an alias, not an idiomatically curried sibling. $trampoline(fn) reads curried only because fnName is optional — it does not return a function awaiting fnName. Nothing documented it as curried, so per acceptance criterion 3 the documentation was corrected: a doc-block now states it explicitly, and the suite pins the non-curried arity via notCurried.

Other documentation corrections (no behaviour changes)

  • src/_platform/object/index.ts — the native doc-block told users to import {defineProperties, $defineProperties} from 'fjl'; those aren't top-level exports, they live on native. Example corrected.

Test layout

Follows the dominant existing convention (packages/fjl/tests/**, test-*.ts) — no new convention, and no attempt at the co-location migration (#77 / #125).

Testing evidence

Baseline (origin/main):

Test Suites: 133 passed, 133 total
Tests:       1066 passed, 1066 total

This branch (pnpm test):

Test Suites: 134 passed, 134 total
Tests:       1437 passed, 1437 total

+1 suite, +371 tests, all green. pnpm build exits 0. eslint and tsc-files --noEmit are clean on all five changed/added files (repo-wide pnpm lint failures are pre-existing on main and untouched). No git hooks were bypassed.

🤖 Generated with Claude Code

…curried

Adds a shared, data-driven currying assertion helper plus a suite that
derives its target list from the package's public export surface at
runtime, so curried methods added later are covered automatically.

- `tests/curry-helpers.ts` - `assertCurriedCase` asserts, per argument
  case, that (1) every application before the last returns a function,
  (2) the fully applied curried method agrees with its un-curried
  sibling, and (3) a partially applied method is re-usable.  Argument
  fixtures are factories, so methods that mutate their arguments get
  fresh args per invocation.
- `tests/test-currying.ts` - fixture registry for all 109 `$`-prefixed
  exports (+ the 9 `$`-prefixed `Object` statics on `fjl.native`), and
  meta-tests keeping the registry and the export surface in sync.

Doc corrections (no behaviour changes):
- `$trampoline` documented as *not* idiomatically curried (it takes the
  same `(fn, fnName?)` tuple as `trampoline`).
- `$assignDeep` (and its alias `$objUnion`) annotated with a known-bug
  `@todo`: rest-args are forwarded as a single array, so
  `$assignDeep(a)(b, c)` merges `[b, c]` into `a`.  Pinned with
  `it.failing` rather than fixed here - needs its own ticket.
- `_platform/object` doc-block: `$defineProperties` et al. live on
  `native`, they are not top-level `fjl` exports.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v2.0 - Add tests asserting curried methods are actually curried

1 participant