Skip to content

test: cover ResolveLabels, export i18n completeness and the view-mode guards - #1223

Merged
renemadsen merged 1 commit into
stablefrom
test/backfill-export-labels-and-view-guards
Sep 8, 2026
Merged

test: cover ResolveLabels, export i18n completeness and the view-mode guards#1223
renemadsen merged 1 commit into
stablefrom
test/backfill-export-labels-and-view-guards

Conversation

@renemadsen

Copy link
Copy Markdown
Member

Test-only, no production file touched. Second round of the regression-coverage backfill, closing the MEDIUM gaps from the audit of recently-merged fixes. Independent of #1222 — no shared file, no ordering constraint.

ResolveLabels — previously zero coverage

It resolves the property and calendar-board names used for both the exported document's page header (Ejendom: / Kalender: / Periode:) and the download's file name. It had no test because the sibling fixture deliberately passes a null DbContext and records the gap in its own comment — both lookups need a seeded database.

The new fixture asserts against real output: the file name through a real Export call, and the header text read out of a generated WordprocessingDocument. Seven cases — live property, soft-deleted property, unknown property, one board, soft-deleted board, two boards, no boards.

Three details that make them discriminate rather than merely pass:

  • The property and board WorkflowState != Removed guards are separate Where clauses in separate queries, so each soft-delete test names only its own subject (no property on the board request, no board on the property request). Removing one guard fails only its own test.
  • The two-boards case seeds both boards live, so only boardIds.Count == 1 can produce the fallback — a "take boardIds[0]" implementation fails it.
  • The unknown-property case pins the IsNullOrWhiteSpace check specifically: without it the label is null, the part is dropped entirely, and the file name loses a segment rather than reading Alle.

Stated gap: the two-line hand-off inside Export that assigns the labels onto the document is not directly observable — the header only exists on the Word/PDF arm, and the PDF arm shells out to soffice, which CI lacks. So this pins DB → label → file name, and label → header, but not the assignment between them. A cheaper closure was looked for and none exists without a production seam.

Export localisation completeness

JsonStringLocalizer returns the raw key on a miss, so a locale omitted in a future PR gives that customer a column headed Company and a title reading ComplianceOverviewTitle in their PDF. Nothing pinned this.

The new DB-free fixture checks all 36 keys the export path reads resolve in all 26 shipped locales, and that the one formatted key keeps its {0} (losing it is silent — string.Format just drops the count). It reproduces the localizer's actual resolution order (culture presence first, then key match), which matters because the file contains duplicate keys and those two rules are not the same rule. It also addresses the resource by the same name the localizer builds, so a rename or a dropped <EmbeddedResource> fails here too.

Everything passes today — this is a guard, not a fix.

The key list is a hand-maintained snapshot and the fixture says so, including how to re-derive it: grep GetString/GetStringWithFormat across the export services, remembering that InvalidExportRequest arrives through a non-literal Fail(key) call and a literal-only grep misses it.

The view-mode guards (#1185)

rxFilter(() => this.state.mode === '<own>') at the head of each child view's fetch pipeline was completely uncovered, with an asymmetric failure mode: deleting a guard is silent (one cancelled request plus a briefly-set loading flag), while mis-writing one is loud.

  • The Rapport spec drives the real documented gesture end to end — resetToOverview() while that child is still mounted — and asserts the call count does not move.
  • The Oversigt ones are contract tests and say so. resetToOverview() calls setMode('overview') before requestFetch(), so by the time the trigger lands the mode already matches and that child's guard passes; drillIntoProperty emits nothing. The Oversigt child is never the outgoing child, so no reachable gesture makes its guard drop a trigger. The header comment states this rather than implying the test reproduces something real.
  • A second foreign mode is exercised, because a reviewer constructed a surviving mutant: mode !== 'details' passed all three original tests, since 'details' was the only foreign mode used.

The loading-flag tests stub with a never-settling Subject on purpose — with a synchronous stub the subscribe callback clears the flag in the same tick and the test could not fail.

Two smaller ones

parseContentDispositionFileName was exercised exactly once, by its happy path, inside a slow Playwright shard. Now 16 cases including RFC-5987 precedence, malformed percent-encoding (decodeURIComponent throws; the try/catch degrades correctly — asserted as intended), and the escaped-quote form.

The [caseId] binding from #1159 needed an explicit stub component: NO_ERRORS_SCHEMA silently swallows unknown bindings, so without one the test would hide the very regression it exists for. These are the first tests in that suite to render the template at all.

Mutation-checked, not argued

All nine new Jest tests were verified empirically: the two rxFilter guards and the [caseId] binding were temporarily reverted, the suite run, all nine failed with the expected assertions, then the production files restored and confirmed byte-identical.

Final: 35 suites, 659 tests, green.

Two production findings, reported not fixed

  • filename="" returns the literal "" rather than the caller's fallback — the quoted branch correctly rejects the empty value, then the bare branch's ([^;]+) matches the quote characters. Unreachable from this server (BuildFileName never emits an empty name), so current behaviour is pinned with a comment rather than "fixed".
  • fix(i18n): localization.json has unreachable locale codes, five keys missing up to 24 locales, duplicate keys, and two untranslated error toasts #1221localization.json has four locale codes that can never resolve (pl-Pl, uk-UK, Uk-UA, bare en), five keys missing up to 24 locales, five duplicate keys, and two error messages (CaseNotFound, CaseCouldNotBeUpdated) with no entry at all. The new fixture scopes to export keys deliberately — widening it would make CI red for reasons unrelated to this work.

Cost

+1 TestBaseSetup fixture (one container); the localisation fixture is DB-free by design. CI runs the project unfiltered, so no allowlist change.

Not verified

The C# tests have not been executed — tests run in CI only; the project builds clean (0 Error(s)). The Jest tests were run locally and are green.

🤖 Generated with Claude Code

https://claude.ai/code/session_018qJL2WhHwhZ5CGZehZF2ro

… guards

Second round of regression-coverage backfill from the audit of recently-merged
fixes. Test-only; no production file is touched.

ResolveLabels (#1189) had no test at all: the sibling export fixture passes a
null DbContext on purpose and records the gap in its own comment. It resolves
the property and calendar-board names used for BOTH the exported document's
page header and the download's file name, so a regression silently labels every
export "Alle" — or, if the WorkflowState guard is dropped, names a soft-deleted
property. The new fixture asserts against real output: the file name through a
real Export call, and the header text read out of a generated docx. The
property and board guards live in separate queries and are pinned separately,
and the two-boards case is seeded with both boards live so a "take boardIds[0]"
implementation fails it.

Export localisation had nothing pinning it. JsonStringLocalizer returns the raw
key on a miss, so a locale omitted in a future PR gives that customer a column
headed "Company" in their PDF. The new DB-free fixture checks all 36 keys the
export path reads resolve in all 26 shipped locales, and that the one formatted
key keeps its {0}. Everything passes today — this is a guard, not a fix. The key
list is a hand-maintained snapshot and the fixture says so, including how to
re-derive it (one key arrives through a non-literal Fail(key) call and a
literal-only grep misses it).

The view-mode guards (#1185) were completely uncovered, with an asymmetric
failure mode: deleting one is silent — a cancelled request and a briefly-set
loading flag — while mis-writing one is loud. The Rapport spec drives the real
resetToOverview() gesture end to end. The Oversigt ones are contract tests and
say so: resetToOverview calls setMode('overview') before requestFetch, so that
child is never the outgoing one and no reachable gesture makes its guard drop a
trigger. A second foreign mode is exercised so a guard written as
`mode !== 'details'` cannot pass.

Also covers parseContentDispositionFileName, previously exercised only once by
its happy path inside a slow Playwright shard, and the [caseId] binding from
#1159 — which needs an explicit stub component, because NO_ERRORS_SCHEMA
silently swallows unknown bindings and would hide the very regression the test
is for.

All nine new Jest tests were mutation-checked: the two rxFilter guards and the
[caseId] binding were reverted, the suite was run, all nine failed with the
expected assertions, and the production files were restored and verified
byte-identical. Jest: 35 suites, 659 tests, green.

Adds one TestBaseSetup fixture (+1 container); the localisation fixture is
DB-free by design.

Found while writing these and filed rather than fixed: #1221 (localization.json
has unreachable locale codes, five keys missing up to 24 locales, duplicate
keys, and two untranslated error toasts).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qJL2WhHwhZ5CGZehZF2ro
Copilot AI lite review requested due to automatic review settings September 8, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are isolated to tests and add targeted, non-flaky regression coverage for previously uncovered, silently-regressing behaviors.

Pull request overview

This PR adds regression-focused test coverage across the backend compliance export and the frontend compliance/calendar UIs, without modifying production code. It backfills previously-uncovered behaviors that can silently regress (label resolution, i18n key completeness, view-mode request guards, filename parsing, and a critical template binding).

Changes:

  • Add a DB-free integration fixture that asserts all compliance-export i18n keys resolve across all shipped locales (including the formatted placeholder key).
  • Add a DB-backed integration fixture covering ResolveLabels end-to-end via real export output (file name) and docx header content.
  • Add/extend Jest unit tests covering view-mode guard behavior, Content-Disposition filename parsing edge cases, and the [caseId] template binding in the calendar complete modal.
File summaries
File Description
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn.Integration.Test/ExportLocalizationCompletenessTests.cs Guards compliance-export localization key presence and per-locale completeness (DB-free).
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn.Integration.Test/ComplianceExportLabelResolutionTests.cs Adds DB-backed coverage for export label resolution and its observable outputs (file name + docx header).
eform-client/src/app/plugins/modules/backend-configuration-pn/services/backend-configuration-pn-compliance-report.service.spec.ts Adds a thorough unit test matrix for parseContentDispositionFileName ordering/fallbacks and malformed inputs.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/compliance-report/components/compliance-report-view/compliance-report-view.component.spec.ts Adds tests pinning the mode === 'report' fetch guard and its effect on request/loading behavior.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/compliance-report/components/compliance-overview-view/compliance-overview-view.component.spec.ts Adds contract tests pinning the mode === 'overview' fetch guard (including a second foreign mode).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/calendar/modals/calendar-complete-event-modal/calendar-complete-event-modal.component.spec.ts Adds a real stub component to assert the [caseId] binding is present and null-safe.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@renemadsen
renemadsen merged commit 4854fca into stable Sep 8, 2026
36 of 37 checks passed
@renemadsen
renemadsen deleted the test/backfill-export-labels-and-view-guards branch September 8, 2026 16:42
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.

2 participants