test: cover ResolveLabels, export i18n completeness and the view-mode guards - #1223
Merged
Merged
Conversation
… 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
There was a problem hiding this comment.
🟢 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
ResolveLabelsend-to-end via real export output (file name) and docx header content. - Add/extend Jest unit tests covering view-mode guard behavior,
Content-Dispositionfilename 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.
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.
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 coverageIt 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 anullDbContext 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
Exportcall, and the header text read out of a generatedWordprocessingDocument. 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:
WorkflowState != Removedguards are separateWhereclauses 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.boardIds.Count == 1can produce the fallback — a "takeboardIds[0]" implementation fails it.IsNullOrWhiteSpacecheck specifically: without it the label isnull, the part is dropped entirely, and the file name loses a segment rather than readingAlle.Stated gap: the two-line hand-off inside
Exportthat 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 tosoffice, 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
JsonStringLocalizerreturns the raw key on a miss, so a locale omitted in a future PR gives that customer a column headedCompanyand a title readingComplianceOverviewTitlein 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.Formatjust 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/GetStringWithFormatacross the export services, remembering thatInvalidExportRequestarrives through a non-literalFail(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.resetToOverview()while that child is still mounted — and asserts the call count does not move.resetToOverview()callssetMode('overview')beforerequestFetch(), so by the time the trigger lands the mode already matches and that child's guard passes;drillIntoPropertyemits 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.mode !== 'details'passed all three original tests, since'details'was the only foreign mode used.The
loading-flag tests stub with a never-settlingSubjecton purpose — with a synchronous stub the subscribe callback clears the flag in the same tick and the test could not fail.Two smaller ones
parseContentDispositionFileNamewas exercised exactly once, by its happy path, inside a slow Playwright shard. Now 16 cases including RFC-5987 precedence, malformed percent-encoding (decodeURIComponentthrows; thetry/catchdegrades correctly — asserted as intended), and the escaped-quote form.The
[caseId]binding from #1159 needed an explicit stub component:NO_ERRORS_SCHEMAsilently 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
rxFilterguards 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 (BuildFileNamenever emits an empty name), so current behaviour is pinned with a comment rather than "fixed".localization.jsonhas four locale codes that can never resolve (pl-Pl,uk-UK,Uk-UA, bareen), 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
TestBaseSetupfixture (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