Skip to content

Commit 61638f5

Browse files
author
Fortinbra
committed
fix(client): resolve build and test failures for features 051-053
- Fix Razor parser failure with escaped quotes in ReportCanvas interpolated strings - Fix @layout directive conflict in CustomReportBuilder - Fix AreaChart string parameter bindings missing @ prefix - Add missing Domain.Reports global using in Infrastructure and Application - Fix missing IBudgetApiService stub methods in test classes - Fix ThemeService IAsyncDisposable handling with IAsyncLifetime in bUnit tests - Add feature doc 053.1 cataloging all issues and fixes - Update CHANGELOG for v3.15.0 Refs: #53.1
1 parent 4a0edaa commit 61638f5

File tree

17 files changed

+290
-23
lines changed

17 files changed

+290
-23
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ All notable changes to Budget Experiment.
44

55
## [Unreleased]
66

7+
## [3.15.0] - 2026-02-14
8+
79
### Features
810

911
- **client:** LineChart component for trend visualization with multi-series support, axes, grid, and reference lines
@@ -27,6 +29,16 @@ All notable changes to Budget Experiment.
2729
- **client:** BarChart click events for drill-down
2830
- **client:** Component showcase page for charts and exports
2931

32+
### Bug Fixes
33+
34+
- **client:** Fix Razor parser failure with escaped quotes in ReportCanvas interpolated strings
35+
- **client:** Fix `@layout` directive conflict in CustomReportBuilder by renaming variable
36+
- **client:** Fix AreaChart string parameter bindings missing `@` prefix
37+
- **client:** Fix ExportDownloadService async disposal pattern
38+
- **infra:** Add missing `Domain.Reports` global using in Infrastructure and Application
39+
- **api:** Add missing XML doc param tag in ExportController
40+
- **api:** Fix invalid `ChatActionType.Unknown` enum reference in tests
41+
3042
### Testing
3143

3244
- **client:** bUnit tests for LineChart (empty state, paths, points, multi-series, ARIA)
@@ -43,12 +55,15 @@ All notable changes to Budget Experiment.
4355
- **e2e:** Report export flow test for CSV download
4456
- **e2e:** Custom report builder tests for drag/select and save/reload
4557
- **e2e:** Accessibility coverage expanded to report pages
58+
- **client:** Fix missing IBudgetApiService stub methods and service registrations in test classes
59+
- **client:** Fix ThemeService IAsyncDisposable handling with IAsyncLifetime in bUnit tests
4660

4761
### Documentation
4862

4963
- **docs:** Update Feature 053 spec for custom report builder layout, endpoints, and Phase 7 status
5064
- **docs:** Feature 053 updates for grid layout, presets, tests, and deferred items
5165
- **docs:** Chart component standards added to component guidelines
66+
- **docs:** Feature 053.1 — build and test fix catalog for features 051–053
5267
- **readme:** Reports and component showcase documentation updates
5368

5469
## [3.14.1] - 2026-02-08
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Feature 053.1: Build & Test Fixes for Features 051–053
2+
> **Status:** Complete
3+
4+
## Overview
5+
6+
Review of features 051 (AI Assistant Calendar Context), 052 (Performance TTFP/Page Load), and 053 (Reporting & Data Portability) uncovered build-breaking and test-breaking issues. This document catalogs every issue found, its root cause, and the fix applied.
7+
8+
## Problem Statement
9+
10+
After features 051–053 were marked complete, the solution failed to build with **188 compilation errors** concentrated in the Client project. Beyond the build failures, **23 unit tests** also failed due to missing service registrations and interface method stubs.
11+
12+
### Current State (Before Fix)
13+
14+
- `dotnet build` produced 188 errors (all RZ1006/RZ1000 in `ReportCanvas.razor`).
15+
- 23 unit tests failed across `BudgetExperiment.Client.Tests`.
16+
- Features 051 and 052 were correctly implemented and fully tested.
17+
- Feature 053 code was structurally correct but contained Razor-parser-incompatible patterns.
18+
19+
### Target State (After Fix)
20+
21+
- Build succeeds with **0 errors, 0 warnings**.
22+
- All **1994 tests pass** (772 Domain + 491 Application + 431 Client + 300 Api), 1 skipped.
23+
24+
---
25+
26+
## Issues Found & Fixes Applied
27+
28+
### Issue 1: Razor Parser Failure — Escaped Quotes in Interpolated Strings (188 errors)
29+
30+
**File:** `src/BudgetExperiment.Client/Components/Reports/ReportCanvas.razor`
31+
32+
**Root Cause:** The Razor source generator cannot parse escaped quotes `\"` inside interpolated string expressions within `@code` blocks. Code like:
33+
34+
```csharp
35+
$"repeat({grid.GetColumns(\"lg\")}, 1fr)"
36+
```
37+
38+
causes the parser to lose track of string boundaries, fail to find the closing `}` for the `@code` block, and dump all class-level members into `BuildRenderTree` — producing cascading errors for every method, field, and property.
39+
40+
**Fix:** Extract the problematic calls into local variables before the interpolated string:
41+
42+
```csharp
43+
var lgCols = grid.GetColumns("lg");
44+
// then use:
45+
$"repeat({lgCols}, 1fr)"
46+
```
47+
48+
Applied to `GetGridStyle()` (3 calls) and `GetWidgetStyle()` (3 calls).
49+
50+
---
51+
52+
### Issue 2: Razor `@layout` Directive Conflict
53+
54+
**File:** `src/BudgetExperiment.Client/Pages/Reports/CustomReportBuilder.razor`
55+
56+
**Root Cause:** Using `@layout.Name` or `@layout.Id` in Razor markup is interpreted as the `@layout` directive rather than accessing the `layout` variable's properties.
57+
58+
**Fix:** Renamed the variable from `layout` to `savedLayout` throughout the file, and the `Layout` component parameter to `ReportLayout`.
59+
60+
---
61+
62+
### Issue 3: Missing Global Using for Domain.Reports Namespace
63+
64+
**Files:**
65+
- `src/BudgetExperiment.Infrastructure/GlobalUsings.cs`
66+
- `src/BudgetExperiment.Application/GlobalUsings.cs`
67+
68+
**Root Cause:** Feature 053 added `BudgetExperiment.Domain.Reports` namespace with `CustomReportLayout` and related types, but the global using was not added to Infrastructure or Application projects.
69+
70+
**Fix:** Added `global using BudgetExperiment.Domain.Reports;` to both files.
71+
72+
---
73+
74+
### Issue 4: Missing Interface Method Stubs in Test Classes
75+
76+
**Files:**
77+
- `tests/BudgetExperiment.Client.Tests/Pages/Reports/BudgetComparisonReportTests.cs`
78+
- `tests/BudgetExperiment.Client.Tests/Components/Reports/DaySummaryTests.cs`
79+
- `tests/BudgetExperiment.Client.Tests/Components/Reports/CalendarInsightsPanelTests.cs`
80+
81+
**Root Cause:** `IBudgetApiService` grew with 5 new custom report layout methods (`GetCustomReportLayoutsAsync`, `GetCustomReportLayoutAsync`, `CreateCustomReportLayoutAsync`, `UpdateCustomReportLayoutAsync`, `DeleteCustomReportLayoutAsync`), but the `StubBudgetApiService` classes in these 3 test files were not updated.
82+
83+
**Fix:** Added all 5 missing stub method implementations to each test file.
84+
85+
---
86+
87+
### Issue 5: Missing Service Registrations in Test Classes
88+
89+
**Files & Missing Services:**
90+
- `ReportWidgetTests.cs` — missing `ThemeService` + `IAsyncLifetime`
91+
- `WidgetConfigPanelTests.cs` — missing `ThemeService` + `IAsyncLifetime`
92+
- `BudgetComparisonReportTests.cs` — missing `IToastService` + `IExportDownloadService`
93+
- `ExportButtonTests.cs` — missing `using Microsoft.Extensions.DependencyInjection;`
94+
95+
**Root Cause:** New components depend on services (`ThemeService`, `IToastService`, `IExportDownloadService`) that weren't registered in the test DI container. Additionally, `ThemeService` implements `IAsyncDisposable` only, requiring test classes to implement `IAsyncLifetime` for proper cleanup.
96+
97+
**Fix:** Added service registrations in constructors. Added `IAsyncLifetime` with `DisposeAsync` delegation to `ReportWidgetTests` and `WidgetConfigPanelTests`. Added `StubExportDownloadService` inner class to `BudgetComparisonReportTests`.
98+
99+
---
100+
101+
### Issue 6: AreaChart String Parameter Binding
102+
103+
**File:** `src/BudgetExperiment.Client/Components/Charts/AreaChart.razor`
104+
105+
**Root Cause:** String-typed component parameters were passed without `@` prefix (e.g., `AriaLabel="AriaLabel"`), which Blazor treats as a literal string `"AriaLabel"` rather than binding to the `AriaLabel` property.
106+
107+
**Fix:** Changed to `AriaLabel="@AriaLabel"`, `XAxisLabel="@XAxisLabel"`, `YAxisLabel="@YAxisLabel"`, `Interpolation="@Interpolation"`.
108+
109+
---
110+
111+
### Issue 7: Missing XML Doc Param Tag
112+
113+
**File:** `src/BudgetExperiment.Api/Controllers/ExportController.cs`
114+
115+
**Root Cause:** Constructor had a `budgetProgressService` parameter without a corresponding `<param>` XML doc tag, causing an analyzer error.
116+
117+
**Fix:** Added `<param name="budgetProgressService">Budget progress service.</param>`.
118+
119+
---
120+
121+
### Issue 8: Invalid Enum Member Reference
122+
123+
**File:** `tests/BudgetExperiment.Api.Tests/ChatControllerTests.cs`
124+
125+
**Root Cause:** Test referenced `ChatActionType.Unknown` which doesn't exist in the enum.
126+
127+
**Fix:** Changed to `default(ChatActionType)`.
128+
129+
---
130+
131+
## Feature Verification Summary
132+
133+
| Feature | Status | Notes |
134+
|---------|--------|-------|
135+
| 051AI Assistant Calendar Context | Verified Complete | All code and tests in place |
136+
| 052Performance TTFP/Page Load | Verified Complete | All code and tests in place |
137+
| 053Reporting & Data Portability | Verified Complete | Build/test issues fixed in this doc |
138+
139+
---
140+
141+
## Testing Strategy
142+
143+
### Verification
144+
145+
- [x] Full solution build: 0 errors, 0 warnings
146+
- [x] Domain tests: 772 passed
147+
- [x] Application tests: 491 passed
148+
- [x] Client tests: 431 passed, 1 skipped
149+
- [x] Api tests: 300 passed
150+
- [x] Total: 1994 passed, 0 failed
151+
152+
---
153+
154+
## Changelog
155+
156+
| Date | Change | Author |
157+
|------|--------|--------|
158+
| 2025-06-25 | Initial draftcataloged all 8 issues and fixes | @copilot |

src/BudgetExperiment.Api/Controllers/ExportController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public sealed class ExportController : ControllerBase
2828
/// Initializes a new instance of the <see cref="ExportController"/> class.
2929
/// </summary>
3030
/// <param name="reportService">Report service.</param>
31+
/// <param name="budgetProgressService">Budget progress service.</param>
3132
/// <param name="exportService">Export service.</param>
3233
public ExportController(
3334
IReportService reportService,

src/BudgetExperiment.Application/GlobalUsings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
global using BudgetExperiment.Domain.Reconciliation;
1414
global using BudgetExperiment.Domain.Recurring;
1515
global using BudgetExperiment.Domain.Repositories;
16+
global using BudgetExperiment.Domain.Reports;
1617
global using BudgetExperiment.Domain.Settings;
1718

1819
// Application namespaces

src/BudgetExperiment.Application/Reports/CustomReportLayoutService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using BudgetExperiment.Contracts.Dtos;
66
using BudgetExperiment.Domain;
7+
using BudgetExperiment.Domain.Reports;
78

89
namespace BudgetExperiment.Application.Reports;
910

src/BudgetExperiment.Client/Components/Charts/AreaChart.razor

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
ShowPoints="ShowPoints"
66
ShowArea="true"
77
UseGradientFill="UseGradientFill"
8-
Interpolation="Interpolation"
8+
Interpolation="@Interpolation"
99
ShowGrid="ShowGrid"
1010
ShowXAxis="ShowXAxis"
1111
ShowYAxis="ShowYAxis"
12-
XAxisLabel="XAxisLabel"
13-
YAxisLabel="YAxisLabel"
12+
XAxisLabel="@XAxisLabel"
13+
YAxisLabel="@YAxisLabel"
1414
MinY="MinY"
1515
MaxY="MaxY"
1616
ReferenceLines="ReferenceLines"
17-
AriaLabel="AriaLabel"
17+
AriaLabel="@AriaLabel"
1818
OnPointClick="OnPointClick" />

src/BudgetExperiment.Client/Components/Reports/ReportCanvas.razor

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</div>
1212

1313
<div class="report-canvas-grid" style="@GetGridStyle()" @ref="gridRef">
14-
@if (Layout?.Widgets.Count == 0)
14+
@if (ReportLayout?.Widgets.Count == 0)
1515
{
1616
<div class="report-canvas-empty">
1717
<Icon Name="plus" Size="18" />
@@ -20,7 +20,7 @@
2020
}
2121
else
2222
{
23-
@foreach (var widget in Layout!.Widgets)
23+
@foreach (var widget in ReportLayout!.Widgets)
2424
{
2525
var lg = GetLayout(widget, "lg");
2626
var md = GetLayout(widget, "md");
@@ -45,7 +45,7 @@
4545
/// Gets or sets the current layout definition.
4646
/// </summary>
4747
[Parameter]
48-
public CustomReportLayoutDefinition? Layout { get; set; }
48+
public CustomReportLayoutDefinition? ReportLayout { get; set; }
4949

5050
/// <summary>
5151
/// Gets or sets the drop callback.
@@ -110,10 +110,13 @@
110110

111111
private string GetGridStyle()
112112
{
113-
var grid = Layout?.Grid ?? ReportGridDefinition.CreateDefault();
114-
return $"--grid-columns-lg: {grid.GetColumns(\"lg\")}; " +
115-
$"--grid-columns-md: {grid.GetColumns(\"md\")}; " +
116-
$"--grid-columns-sm: {grid.GetColumns(\"sm\")}; " +
113+
var grid = ReportLayout?.Grid ?? ReportGridDefinition.CreateDefault();
114+
var lgCols = grid.GetColumns("lg");
115+
var mdCols = grid.GetColumns("md");
116+
var smCols = grid.GetColumns("sm");
117+
return $"--grid-columns-lg: {lgCols}; " +
118+
$"--grid-columns-md: {mdCols}; " +
119+
$"--grid-columns-sm: {smCols}; " +
117120
$"--grid-row-height: {grid.RowHeight}px; " +
118121
$"--grid-gap: {grid.Gap}px;";
119122
}
@@ -133,7 +136,7 @@
133136

134137
private ReportWidgetLayoutPosition GetLayout(ReportWidgetDefinition widget, string breakpoint)
135138
{
136-
var grid = Layout?.Grid ?? ReportGridDefinition.CreateDefault();
139+
var grid = ReportLayout?.Grid ?? ReportGridDefinition.CreateDefault();
137140
var columns = grid.GetColumns(breakpoint);
138141
if (widget.Layouts.TryGetValue(breakpoint, out var layout))
139142
{
@@ -197,8 +200,8 @@
197200
dotNetRef,
198201
new
199202
{
200-
rowHeight = Layout?.Grid.RowHeight ?? 24,
201-
gap = Layout?.Grid.Gap ?? 12,
203+
rowHeight = ReportLayout?.Grid.RowHeight ?? 24,
204+
gap = ReportLayout?.Grid.Gap ?? 12,
202205
breakpointMdMax = BreakpointMdMaxWidth,
203206
breakpointSmMax = BreakpointSmMaxWidth,
204207
});
@@ -215,7 +218,7 @@
215218
int width,
216219
int height)
217220
{
218-
if (Layout == null)
221+
if (ReportLayout == null)
219222
{
220223
return Task.CompletedTask;
221224
}
@@ -225,7 +228,7 @@
225228
return Task.CompletedTask;
226229
}
227230

228-
Layout.UpdateWidgetLayout(parsedId, breakpoint, new ReportWidgetLayoutPosition
231+
ReportLayout.UpdateWidgetLayout(parsedId, breakpoint, new ReportWidgetLayoutPosition
229232
{
230233
X = x,
231234
Y = y,

src/BudgetExperiment.Client/Pages/Reports/CustomReportBuilder.razor

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
<label for="layout-select">Layout</label>
2929
<select id="layout-select" @onchange="HandleLayoutChange" disabled="@isSaving">
3030
<option value="">Select layout</option>
31-
@foreach (var layout in layouts)
31+
@foreach (var savedLayout in layouts)
3232
{
33-
<option value="@layout.Id" selected="@(currentLayout?.Id == layout.Id)">@layout.Name</option>
33+
<option value="@savedLayout.Id" selected="@(currentLayout?.Id == savedLayout.Id)">@savedLayout.Name</option>
3434
}
3535
</select>
3636
</div>
@@ -57,7 +57,7 @@
5757

5858
<div class="builder-grid">
5959
<WidgetPalette Items="paletteItems" OnDragStart="HandleDragStart" />
60-
<ReportCanvas Layout="layoutDefinition"
60+
<ReportCanvas ReportLayout="layoutDefinition"
6161
SelectedWidgetId="selectedWidgetId"
6262
OnSelectWidget="HandleSelectWidget"
6363
OnDropWidget="HandleDrop"

src/BudgetExperiment.Client/Services/ExportDownloadService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public async Task<ExportDownloadResult> DownloadAsync(string url, CancellationTo
5555
return ExportDownloadResult.Fail("Download helper unavailable.");
5656
}
5757

58-
await using var streamRef = new DotNetStreamReference(new MemoryStream(bytes));
58+
using var streamRef = new DotNetStreamReference(new MemoryStream(bytes));
5959
await this.module.InvokeVoidAsync(
6060
"downloadFileFromStream",
6161
cancellationToken,

src/BudgetExperiment.Infrastructure/GlobalUsings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
global using BudgetExperiment.Domain.Reconciliation;
1414
global using BudgetExperiment.Domain.Recurring;
1515
global using BudgetExperiment.Domain.Repositories;
16+
global using BudgetExperiment.Domain.Reports;
1617
global using BudgetExperiment.Domain.Settings;
1718

1819
// Application namespaces

0 commit comments

Comments
 (0)