feat(working-capital): loan servicing actions, plus a Vitest runner and a Karma ratchet - #408
Merged
Aman-Mittal merged 2 commits intoAug 19, 2026
Conversation
…s and originators Four Working Capital endpoints existed on the backend but had no UI: delinquency actions, breach actions, near-breach actions and per-loan loan-originator attach/detach. The loan detail screen now carries write paths for all four, with three new guarded routes (UPDATE_WORKINGCAPITALLOAN) and their action forms. Also in this branch: - api: sync the generated client against apache/fineract:latest (schemaCount 1472 -> 1475). The interop loan-repayment command gained a request body, so interop-transfers now sends dateFormat/locale rather than calling with none. - guidance: the dashboard tour's second step targeted a bare `ul`, which matched the sidebar's nav list before the System Status card it describes; it now targets `.status-list`. The tour's Exit/Back/Next/Finish buttons were hardcoded English and are translated. - search: results were dismissed by the blur timeout racing the click, silently swallowing navigation on a slow render. Result items now preventDefault on mousedown so the searchbar never blurs on a result click. - e2e: new specs for working-capital actions, the dashboard tour, and RBAC dimensions rbac-backend-restricted-user does not reach (multi-permission routes, ALL_FUNCTIONS_READ, a second action-level gate, reload persistence). - e2e: `npm run seed:demo-data` populates a backend with a representative dataset for manual sanity testing, as its own `demo-seed` Playwright project so it never runs as a side effect of the `backend` project in CI.
Karma is deprecated and `@angular/build:karma` goes with it. `vitest@4` was already in devDependencies with nothing wired to it, which is the worst of both: two runners carried, only the deprecated one used. The suite cannot move in one commit — 219 specs, 886 `jasmine.*` sites — so this makes it movable in batches and stops it growing in the meantime. - The runners select by filename: `*.spec.ts` is Karma (legacy, shrinking), `*.test.ts` is Vitest (everything new). A migration is a rename plus a dialect swap. - `scripts/check-test-runner.mjs` records the remaining Karma specs in `karma-baseline.json` and fails CI when the set grows. Same ratchet as `eslint-suppressions.json`. `--write` refuses to record a baseline that grew, so the ratchet cannot be released by the command that documents it. - `scripts/codemod-jasmine-to-vitest.mjs` does the dialect and the rename, converting 187 of 215 remaining specs. It skips whole files using fakeAsync/tick/flush, `done()` callbacks or the object form of createSpyObj — those carry semantics, not spelling, and a half-converted spec that still compiles reads as finished in review. - `src/app/testing/mocks.ts` provides `SpyObj<T>`/`createSpyObj<T>()`, reproducing Jasmine's typing including its unsoundness, so the migration is not also a strict-null audit of 358 annotations. Two environment differences had to be solved before any Ionic spec could run: `@ionic/angular` reaches `@ionic/core/components` as a directory import that Node's ESM resolver rejects, so `vitest.config.ts` inlines the package; and jsdom has no `CSS.escape`, `Element.prototype.scrollTo` or usable `matchMedia`, stubbed in `src/testing/vitest-setup.ts`. 30 specs are migrated. Karma 921 + Vitest 270 = 1191, the same total as before, so nothing was lost in the move. Rationale and the open question about Vitest browser mode are in DOCS/adr/0004-vitest-migration.md.
Aman-Mittal
force-pushed
the
chore/vitest-migration-groundwork
branch
from
August 19, 2026 17:12
c9db3e0 to
07a1dd1
Compare
9 tasks
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes and closes #402, which contained the first commit only. Rebased and combined so there is one branch to review and merge.
Closes #396, closes #397, closes #398, closes #399, closes #400, closes #401 — the working-capital epic #395.
Closes #403 — the test-runner epic. Its sub-issues #404, #405, #406, #407, #409, #410 and #411 are the follow-up batches this enables, and stay open.
Commit 1 — Working Capital loan servicing
A Working Capital loan was read-only in this UI. Four capabilities the backend already exposes — delinquency actions, breach actions, near-breach actions, and per-loan loan-originator attach/detach — had no entry point, so recording a breach action meant leaving the back office and calling Fineract directly.
wc-loan-view, three new action forms, each on a lazily-loaded route guarded byUPDATE_WORKINGCAPITALLOAN(A Working Capital loan is read-only: delinquency, breach and near-breach actions and originators cannot be recorded #396).apache/fineract:latest(1472 → 1475 schemas). The sync gives the interop loan-repayment command a request body it previously had none of — every field optional, so the bodyless call still compiled while sending nothing. It now sendsdateFormatandlocale(Interop loan repayment is sent with no request body after the API spec adds one #397).targetSelector: 'ul', which resolves to the first<ul>in the document — the sidebar's nav list, not the System Status card its copy describes. Now targets.status-list, and the tour's own buttons are translated (The dashboard guidance tour highlights the sidebar instead of the System Status card it is describing #398).preventDefault()onmousedown(Clicking a global search result can do nothing — the blur timeout races the click and wins #399).ALL_FUNCTIONS_READagainst Fineract's real catalogue, a second action-level gate, and reload persistence (RBAC e2e never covers multi-permission routes, ALL_FUNCTIONS_READ, or whether a restricted session survives a reload #400).npm run seed:demo-datapopulates a fresh backend with a representative dataset, as its owndemo-seedPlaywright project so it never runs as a side effect of--project=backend(No repeatable way to populate a backend with demo data, so manual and release-candidate testing starts from an empty tenant #401).Commit 2 — Vitest runner and Karma ratchet
Karma is deprecated and
@angular/build:karmagoes with it.vitest@4was already indevDependencieswith nothing wired to it — two runners carried, only the deprecated one used.219 specs and 886
jasmine.*sites cannot move in one commit, so this makes the suite movable in batches and stops it growing in the meantime:*.spec.tsis Karma (legacy, shrinking),*.test.tsis Vitest (everything new). A migration is a rename plus a dialect swap.scripts/check-test-runner.mjsrecords the remaining Karma specs inkarma-baseline.jsonand fails CI when the set grows — the same ratchet aseslint-suppressions.json.--writerefuses to record a baseline that grew, so the ratchet cannot be released by the command that documents it.scripts/codemod-jasmine-to-vitest.mjsdoes the dialect and the rename, converting 187 of 215. It skips whole files usingfakeAsync/tick/flush,done()callbacks or the object form ofcreateSpyObj— those carry semantics, not spelling, and a half-converted spec that still compiles reads as finished in review.src/app/testing/mocks.tsprovidesSpyObj<T>/createSpyObj<T>(), reproducing Jasmine's typing including its unsoundness, so the migration is not also a strict-null audit of 358 annotations.Two environment differences had to be solved before any Ionic spec could run at all:
@ionic/angularreaches@ionic/core/componentsas a directory import, which Node's ESM resolver rejects. Karma's bundler resolved it; Vitest externalises the package and hands it to Node.vitest.config.tsinlines it.CSS.escape, noElement.prototype.scrollTo(Ionic'sion-segmentscrolls the active button into view during change detection) and no usablematchMedia. Stubbed insrc/testing/vitest-setup.ts, each with a comment saying what it does not do.30 specs migrated. Karma 921 + Vitest 270 = 1191 — the same total as before the split, so nothing was lost in the move.
Interaction with other open PRs — checked
.spec.ts, which the ratchet would otherwise fail. It predates this rule, so it is listed undergrandfatheredinkarma-baseline.jsonand admitted. Kept in a separate key fromspecsso the exception stays visible and finite, and so--writecannot quietly reclassify it.package.json; this branch adds two scripts there. Textual, trivial to resolve.Checklist
npm run lintclean —eslint-suppressions.jsonregenerated after the renames; 443 across 400 files, unchanged frommain, so violations followed the renames without any added or lostnpm run format:checkcleannpm run check:test-runnerpasses (192 remaining, 14% migrated)npm run i18n:check,check:route-permissions,typecheck:e2e,check:licenses,check-license.shall cleannpm test -- --watch=false— 921 passnpm run test:unit— 270 pass across 30 files, including Ionic component renderingReviewer notes
@angular/build:unit-testis marked[EXPERIMENTAL]upstream. It is nonetheless Angular's sanctioned path off Karma, and the alternative is staying on something already deprecated — that trade, and the open question about whether to move to Vitest browser mode instead of stubbing jsdom, are both written up inDOCS/adr/0004-vitest-migration.md.The e2e suites were not run locally; they need the Docker Fineract stack. CI covers them.