Skip to content

Commit 45c8a9d

Browse files
csharpfritzCopilot
andcommitted
fix: postback target ID mismatch + wait for JS bootstrap in tests
- WebFormsPageBase: _postBackTargetId now uses GetType().Name to match ResolveControlId() (was appending _GetHashCode() causing lookup miss) - PostBackTests: wait for __doPostBack function before clicking (race fix) - PostBackTests: use Assertions.Expect().ToContainTextAsync() instead of fixed 2-second delays for more reliable CI assertions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b71b82a commit 45c8a9d

9 files changed

Lines changed: 129 additions & 50 deletions

File tree

.ai-team/agents/bishop/history.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,21 @@ Added `ClientScriptTransform.cs` (Order 850) to the code-behind pipeline. Handle
214214
- **Tests:** All 349 tests pass (same count as before), updated 20 unit test assertions + TC33 expected output file.
215215
- **Regex approach:** Single `PageOrThisPrefixRegex` with lookahead handles all three shim-compatible methods in one pass. Much simpler than the old per-pattern regexes with inline script extraction.
216216

217+
### ClientScriptTransform: Phase 2 — PostBack + ScriptManager Shim-Preserving (Bishop)
218+
219+
- **Date:** 2026-07-31
220+
- **What changed:** `ClientScriptTransform.cs` (Order 850) now preserves ALL ClientScript/ScriptManager patterns for shim use. No more TODO markers.
221+
- **Phase 2 patterns (newly shim-preserved):**
222+
- `Page.ClientScript.GetPostBackEventReference(...)``ClientScript.GetPostBackEventReference(...)` (prefix stripped)
223+
- `this.ClientScript.GetPostBackEventReference(...)``ClientScript.GetPostBackEventReference(...)` (prefix stripped)
224+
- `ScriptManager.GetCurrent(Page)``ScriptManager.GetCurrent(this)` (Page→this substitution)
225+
- `ScriptManager.GetCurrent(this.Page)``ScriptManager.GetCurrent(this)` (this.Page→this substitution)
226+
- `ScriptManager.GetCurrent(this)` → preserved as-is (already correct)
227+
- **Regex changes:**
228+
- `PageOrThisPrefixRegex` lookahead expanded to include `GetPostBackEventReference`
229+
- Removed `GetPostBackEventRefRegex` and `ScriptManagerGetCurrentRegex` (TODO-emitting regexes)
230+
- Added `ScriptManagerGetCurrentPageRegex` for Page→this substitution
231+
- **Shim comment:** Now conditionally mentions `ScriptManagerShim` when ScriptManager patterns detected (dual-shim comment)
232+
- **Tests:** 353 total (was 349) — 4 new test cases for Phase 2 patterns, updated 6 existing assertions
233+
- **Key design decision:** `hasScriptManagerCall` flag tracks ScriptManager presence separately from `hasShimCall` to conditionally generate the dual-shim comment
234+

.squad/agents/beast/history.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
- **Stack:** C#, Blazor, .NET, ASP.NET Web Forms, bUnit, xUnit, MkDocs, Playwright
66
- **Created:** 2026-02-10
77

8-
8+
99
📌 Team update (2026-08-XX): ClientScriptShim documentation delivery — Updated ClientScriptMigrationGuide.md with prominent new section positioning ClientScriptShim as zero-rewrite path (+2,100 lines total), including supported methods table, before/after example, "How It Works" technical explanation, and migration approach comparison (ClientScriptShim vs. manual IJSRuntime vs. JS modules). Updated BWFC022.md, BWFC023.md, BWFC024.md analyzer docs with cross-references to new ClientScriptShim guidance. mkdocs.yml verified (guide already in nav). Strategy: Lead with easiest path first (ClientScriptShim), then modern alternatives for teams ready to modernize. Enables rapid migration for large Web Forms codebases. — decided by Beast
1010

1111
📌 Team update (2026-07-30): ClientScript Migration Support PRD delivered 9-section product requirements document (dev-docs/prd-clientscript-migration-support.md, 38K) covering analyzer improvements (BWFC022/023/024), CLI transforms (startup scripts, includes), safe automation boundaries, TODO guidance, documentation (ClientScriptMigrationGuide.md), testing (8 test cases), and 3-phase roadmap (P1: analyzers + transforms + docs, P2: samples, P3: runtime helpers). Based on Forge CLI Gap Analysis 1.2 (HIGH impact gap). Establishes BWFC position: prefer IJSRuntime over ClientScript shim; emit clear TODO for postback patterns; DO NOT emulate __doPostBack. Ready for implementation planning. decided by Beast

.squad/agents/colossus/history.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,13 @@ All 3 tests passing. Test suite now has 18 test cases (was 15). Pass rate 78% (1
290290
- Removal of : System.Web.UI.Page base class from partial classes
291291
- URL cleanup transform only applies to Response.Redirect() call arguments, not to arbitrary string literals containing URLs
292292
- To verify exact output: copy input files to temp directory, run bwfc-migrate.ps1 on directory (not individual file), examine generated .razor/.razor.cs files
293+
294+
## PostBack Demo Integration Tests (Phase 2)
295+
296+
**Date:** 2026-07-23
297+
**Task:** Write Playwright integration tests for `/postback-demo` page (Phase 2 of ClientScript migration).
298+
**File created:** `samples/AfterBlazorServerSide.Tests/Migration/PostBackTests.cs`
299+
**Tests:** 3 tests — PostBack button trigger, PostBack hyperlink click, ScriptManager startup script registration.
300+
**Status:** Compiles clean (0 errors). Demo page not yet created by Jubilee — tests will pass once the page exists.
301+
**Patterns used:** Same `[Collection(nameof(PlaywrightCollection))]` + `PlaywrightFixture` pattern as all other migration tests. 30s navigation timeout, 10s element wait, 2s post-action settle for CI reliability. `try/finally` with `page.CloseAsync()`.
302+
**Commit:** `0c75aba2` on `feature/clientscript-phase2`

.squad/agents/cyclops/history.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,3 +941,24 @@ foreach (var warning in warnings) {
941941
- Includes loaded via dynamic `<script>` element creation in eval
942942
- Auto-flush happens every render (not just firstRender) so scripts registered in event handlers work correctly
943943
- Build: 0 errors across net8.0/net9.0/net10.0
944+
945+
### ClientScript Phase 2: PostBack + ScriptManager Runtime (2026-07-17)
946+
947+
**Summary:** Replaced NotSupportedException stubs with working PostBack/Callback implementations. Created full JS interop bridge for __doPostBack and ScriptManager.GetCurrent() pattern.
948+
949+
**Files Created:**
950+
- PostBackEventArgs.cs, ScriptManagerShim.cs, bwfc-postback.js
951+
952+
**Files Modified:**
953+
- ClientScriptShim.cs: 3 methods now return working JS strings; added ResolveControlId
954+
- WebFormsPageBase.cs: IAsyncDisposable, ClientScript, PostBack event, JSInvokable handlers, OnAfterRenderAsync registration
955+
- ServiceCollectionExtensions.cs: ScriptManagerShim DI registration
956+
- ClientScriptShimTests.cs: Updated tests for new behavior + null edge cases
957+
958+
**Technical Decisions:**
959+
- Inline JS bootstrap avoids race conditions with external script loading
960+
- ResolveControlId prefers BaseWebFormsComponent.ID, falls back to type name
961+
- ScriptManagerShim: both DI and static GetCurrent() factory
962+
- PostBack target ID: TypeName_HashCode for per-instance uniqueness
963+
964+
**Build:** 0 errors, 32 tests pass x 3 TFMs

.squad/agents/rogue/history.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,3 +490,17 @@ Conventions discovered: SessionShim uses Shouldly assertions + xUnit `[Fact]` (m
490490
- System.CommandLine tests work by reconstructing the command tree locally rather than trying to invoke Program.Main — this decouples from Bishop's refactoring of Program.cs.
491491
- Test case count: 21 TC* cases (TC01-TC21), of which 8 have code-behind pairs (TC13-TC16, TC18-TC21). Total 29 input files + 29 expected files = 58 TestData files.
492492

493+
### Phase 2 PostBack + ScriptManager Shim Tests (2026-07)
494+
495+
**Wrote 35 new unit tests across 3 files for Phase 2 shims.** All 70 targeted tests pass (including pre-existing Phase 1 tests) across net8.0/net9.0/net10.0. Zero regressions (55 pre-existing failures in ViewState/RequestShim unrelated).
496+
497+
**Files modified/created:**
498+
- `ClientScriptShimTests.cs` (modified) — 15 new tests: GetPostBackEventReference escaping (single quotes, backslashes), output format, null/empty args, control type resolution; GetPostBackClientHyperlink consistency with EventReference, null control, escaping; GetCallbackEventReference escaping (single quotes, backslashes in context), null context/argument defaults, output format with all params.
499+
- `ScriptManagerShimTests.cs` (created) — 12 tests: Constructor null throws ArgumentNull, valid construction; GetCurrent error cases (plain object, null, integer, error message content); GetCurrent success via reflection-injected ClientScriptShim on mock BaseWebFormsComponent; Delegation tests for RegisterStartupScript (3 overloads), RegisterClientScriptBlock, RegisterClientScriptInclude.
500+
- `PostBackEventArgsTests.cs` (created) — 11 tests: Constructor sets EventTarget/EventArgument, null target/argument allowed, both null, empty strings; Immutability via reflection (CanWrite=false); Inherits from System.EventArgs; Round-trip with special characters, Unicode, long strings.
501+
502+
**Key patterns used:**
503+
- Reflection to inject `_clientScript`/`_clientScriptResolved` on `Mock<BaseWebFormsComponent>` for `GetCurrent` success test (ClientScript property is non-virtual, can't be mocked directly).
504+
- Fully-qualified `BlazorWebFormsComponents.BaseWebFormsComponent` required — test project has a `BaseWebFormsComponent/` folder creating namespace ambiguity. Same for `System.EventArgs` vs `EventArgs/` folder.
505+
- `dotnet test --filter` uses `|` for OR (not `OR` keyword) in vstest filter expressions.
506+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Decision: CLI Transform Preserves All ClientScript/ScriptManager Patterns (Phase 2)
2+
3+
**Date:** 2026-07-31
4+
**Author:** Bishop (Migration Tooling Dev)
5+
**Status:** Implemented
6+
7+
## Context
8+
9+
ClientScriptTransform Phase 1 emitted TODO markers for `GetPostBackEventReference` and `ScriptManager.GetCurrent()` because no runtime shim existed. Phase 2 shims (ClientScriptShim + ScriptManagerShim) now handle these patterns.
10+
11+
## Decision
12+
13+
The CLI transform now preserves ALL six ClientScript/ScriptManager patterns instead of commenting out two of them. Zero TODO markers remain — shims handle everything at runtime.
14+
15+
- `GetPostBackEventReference` → prefix-stripped, preserved for ClientScriptShim
16+
- `ScriptManager.GetCurrent(Page)` → converted to `ScriptManager.GetCurrent(this)` for ScriptManagerShim
17+
- Shim dependency comment conditionally mentions ScriptManagerShim when ScriptManager patterns detected
18+
19+
## Impact
20+
21+
- All agents: migrated code no longer has `// TODO: Replace __doPostBack` or `// TODO: ScriptManager.GetCurrent() has no Blazor equivalent` — these calls just work via shims
22+
- Layer 2 agents: less manual fixup needed for postback and ScriptManager patterns
23+
- Test count: 349 → 353
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Decision: PostBack Shim Runtime Strategy
2+
3+
**Date:** 2026-07-17
4+
**Author:** Cyclops
5+
**Status:** Implemented
6+
7+
## Context
8+
9+
ClientScriptShim Phase 1 left `GetPostBackEventReference()`, `GetPostBackClientHyperlink()`, and `GetCallbackEventReference()` as `NotSupportedException` stubs. Phase 2 needed to make these return working JavaScript strings — zero rewrite, same API.
10+
11+
## Decision
12+
13+
1. **Inline JS bootstrap** — WebFormsPageBase.OnAfterRenderAsync injects `__doPostBack` and registration functions via `eval()` on firstRender. This avoids race conditions with external script loading while keeping `bwfc-postback.js` available as an optional static asset.
14+
15+
2. **ResolveControlId priority** — Checks `BaseWebFormsComponent.ID` first (the developer-assigned HTML ID), then falls back to `GetType().Name`. This matches Web Forms' ClientID behavior.
16+
17+
3. **ScriptManagerShim dual-path** — Supports both DI injection (`services.AddScoped<ScriptManagerShim>`) and static `GetCurrent(page)` factory. Migrated code using `ScriptManager.GetCurrent(Page)` compiles unchanged.
18+
19+
4. **PostBack target ID format**`TypeName_HashCode` ensures per-instance uniqueness when multiple page components exist.
20+
21+
## Impact
22+
23+
- Tests updated: 3 tests changed from throw-verification to return-value-verification, plus 3 new null-handling tests
24+
- No breaking changes to existing consumers
25+
- WebFormsPageBase now implements IAsyncDisposable (new interface on the class)

samples/AfterBlazorServerSide.Tests/Migration/PostBackTests.cs

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,17 @@ public async Task PostBack_Button_TriggersPostBackEvent()
3434
Timeout = 30000
3535
});
3636

37-
// Wait for Blazor interactive circuit to be ready
38-
var button = page.Locator("#postback-button");
39-
await button.WaitForAsync(new LocatorWaitForOptions
40-
{
41-
State = WaitForSelectorState.Attached,
42-
Timeout = 10000
43-
});
37+
// Wait for __doPostBack to be bootstrapped by OnAfterRenderAsync
38+
await page.WaitForFunctionAsync("typeof window.__doPostBack === 'function'",
39+
null, new PageWaitForFunctionOptions { Timeout = 10000 });
4440

41+
var button = page.Locator("#postback-button");
4542
await button.ClickAsync();
4643

47-
// Wait for the result to appear after the server round-trip
44+
// Wait for Blazor to process the postback and re-render
4845
var result = page.Locator("#postback-result");
49-
await result.WaitForAsync(new LocatorWaitForOptions
50-
{
51-
State = WaitForSelectorState.Attached,
52-
Timeout = 10000
53-
});
54-
55-
// Allow time for Blazor to process the postback and re-render
56-
await page.WaitForTimeoutAsync(2000);
57-
58-
var resultText = await result.TextContentAsync();
59-
Assert.NotNull(resultText);
60-
Assert.Contains("PostBack received!", resultText!);
46+
await Assertions.Expect(result).ToContainTextAsync("PostBack received!",
47+
new LocatorAssertionsToContainTextOptions { Timeout = 10000 });
6148
}
6249
finally
6350
{
@@ -82,30 +69,17 @@ public async Task PostBackHyperlink_Click_TriggersPostBackEvent()
8269
Timeout = 30000
8370
});
8471

85-
// Wait for Blazor interactive circuit to be ready
86-
var link = page.Locator("#postback-link");
87-
await link.WaitForAsync(new LocatorWaitForOptions
88-
{
89-
State = WaitForSelectorState.Attached,
90-
Timeout = 10000
91-
});
72+
// Wait for __doPostBack to be bootstrapped by OnAfterRenderAsync
73+
await page.WaitForFunctionAsync("typeof window.__doPostBack === 'function'",
74+
null, new PageWaitForFunctionOptions { Timeout = 10000 });
9275

76+
var link = page.Locator("#postback-link");
9377
await link.ClickAsync();
9478

95-
// Wait for the result to appear after the server round-trip
79+
// Wait for Blazor to process the postback and re-render
9680
var result = page.Locator("#hyperlink-result");
97-
await result.WaitForAsync(new LocatorWaitForOptions
98-
{
99-
State = WaitForSelectorState.Attached,
100-
Timeout = 10000
101-
});
102-
103-
// Allow time for Blazor to process the postback and re-render
104-
await page.WaitForTimeoutAsync(2000);
105-
106-
var resultText = await result.TextContentAsync();
107-
Assert.NotNull(resultText);
108-
Assert.Contains("PostBack received!", resultText!);
81+
await Assertions.Expect(result).ToContainTextAsync("PostBack received!",
82+
new LocatorAssertionsToContainTextOptions { Timeout = 10000 });
10983
}
11084
finally
11185
{
@@ -132,14 +106,8 @@ public async Task ScriptManager_GetCurrent_RegistersStartupScript()
132106

133107
// Wait for the startup script to execute and populate the target
134108
var target = page.Locator("#scriptmanager-target");
135-
await target.WaitForAsync(new LocatorWaitForOptions
136-
{
137-
State = WaitForSelectorState.Attached,
138-
Timeout = 10000
139-
});
140-
141-
// Allow time for OnAfterRenderAsync to fire and JS to execute
142-
await page.WaitForTimeoutAsync(2000);
109+
await Assertions.Expect(target).Not.ToHaveTextAsync(string.Empty,
110+
new LocatorAssertionsToHaveTextOptions { Timeout = 10000 });
143111

144112
var targetText = await target.TextContentAsync();
145113
Assert.NotNull(targetText);

src/BlazorWebFormsComponents/WebFormsPageBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
325325

326326
if (firstRender)
327327
{
328-
_postBackTargetId = $"{GetType().Name}_{GetHashCode()}";
328+
_postBackTargetId = GetType().Name;
329329
_postBackRef = DotNetObjectReference.Create(this);
330330

331331
// Bootstrap __doPostBack and registration functions

0 commit comments

Comments
 (0)