Prove WinForms two-way binding end-to-end with a dedicated test suite (#94)#127
Merged
Conversation
New StrongTypes.WinForms.Tests suite mirroring the WPF one: two-way Binding resolves the on-type [TypeConverter]s through TypeDescriptor with zero registration for NonEmptyString, Email, and the numeric wrappers; invalid input keeps the last valid source value and surfaces via BindingComplete. The earlier failed experiment was indeed a harness lifecycle problem: bindings activate only on a shown host Form, which the suite pins explicitly. Also pins the WinForms culture default (FormatInfo ?? CurrentCulture - the opposite of WPF). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CoverageLines: 4818 / 6386 (75.4%) Branches: 2514 / 3288 (76.5%) Files changed in this PRNo coverage-instrumented files changed in this PR. StrongTypes — lines 89.1% (1946/2183), branches 87.8% (1245/1418)Booleans — lines 100.0% (14/14), branches 84.6% (22/26)
Collections — lines 90.2% (276/306), branches 85.1% (114/134)
ComponentModel — lines 89.5% (17/19), branches 68.8% (11/16)
Digits — lines 82.7% (86/104), branches 93.8% (30/32)
Emails — lines 97.4% (74/76), branches 95.7% (44/46)
Enums — lines 100.0% (58/58), branches 95.5% (21/22)
Exceptions — lines 78.9% (15/19), branches 50.0% (7/14)
Intervals — lines 96.5% (382/396), branches 95.5% (231/242)
Maybe — lines 92.1% (151/164), branches 83.9% (94/112)
Nullables — lines 100.0% (12/12), branches 83.3% (20/24)
Numbers — lines 100.0% (84/84), branches 94.4% (17/18)
Result — lines 98.5% (333/338), branches 95.6% (417/436)
Strings — lines 84.7% (133/157), branches 84.4% (54/64)
generated — lines 71.3% (311/436), branches 70.3% (163/232)
StrongTypes.Analyzers — lines 92.4% (514/556), branches 83.8% (238/284)(root) — lines 92.4% (514/556), branches 83.8% (238/284)
StrongTypes.Api — lines 97.3% (391/402), branches 86.3% (88/102)(root) — lines 100.0% (11/11), branches n/a (0/0)
Controllers — lines 97.0% (226/233), branches 86.3% (88/102)
Data — lines 100.0% (102/102), branches n/a (0/0)
Entities — lines 85.2% (23/27), branches n/a (0/0)
Models — lines 100.0% (29/29), branches n/a (0/0)
StrongTypes.AspNetCore — lines 89.3% (151/169), branches 84.5% (71/84)(root) — lines 89.3% (151/169), branches 84.5% (71/84)
StrongTypes.AspNetCore.TestApi — lines 91.5% (43/47), branches 92.5% (37/40)(root) — lines 100.0% (6/6), branches n/a (0/0)
Controllers — lines 90.2% (37/41), branches 92.5% (37/40)
StrongTypes.Configuration — lines 97.5% (79/81), branches 83.3% (40/48)(root) — lines 97.5% (79/81), branches 83.3% (40/48)
StrongTypes.Configuration.Tests.NullableDisabled — lines 100.0% (3/3), branches n/a (0/0)(root) — lines 100.0% (3/3), branches n/a (0/0)
StrongTypes.EfCore — lines 95.4% (227/238), branches 81.2% (78/96)(root) — lines 95.4% (227/238), branches 81.2% (78/96)
StrongTypes.FsCheck — lines 81.9% (127/155), branches 100.0% (2/2)(root) — lines 81.9% (127/155), branches 100.0% (2/2)
StrongTypes.OpenApi.Core — lines 91.4% (447/489), branches 83.4% (297/356)(root) — lines 91.9% (262/285), branches 86.1% (167/194)
Inlining — lines 90.7% (185/204), branches 80.2% (130/162)
StrongTypes.OpenApi.Microsoft — lines 38.1% (450/1180), branches 41.2% (177/430)(root) — lines 94.6% (246/260), branches 74.6% (97/130)
Binding — lines 98.1% (103/105), branches 78.6% (55/70)
Collections — lines 100.0% (19/19), branches 100.0% (8/8)
Digits — lines 100.0% (11/11), branches 100.0% (2/2)
Emails — lines 100.0% (11/11), branches 100.0% (2/2)
Inlining — lines 100.0% (7/7), branches 50.0% (1/2)
Intervals — lines 100.0% (24/24), branches 100.0% (6/6)
Maybe — lines 100.0% (12/12), branches 100.0% (2/2)
Numbers — lines 100.0% (8/8), branches 100.0% (2/2)
Strings — lines 100.0% (9/9), branches 100.0% (2/2)
obj/Debug/net10.0/Microsoft.AspNetCore.OpenApi.SourceGenerators/Microsoft.AspNetCore.OpenApi.SourceGenerators.XmlCommentGenerator — lines 0.0% (0/714), branches 0.0% (0/204)
StrongTypes.OpenApi.Swashbuckle — lines 94.7% (266/281), branches 79.1% (174/220)(root) — lines 98.7% (77/78), branches 93.4% (71/76)
Binding — lines 88.3% (106/120), branches 64.3% (72/112)
Collections — lines 100.0% (6/6), branches 100.0% (4/4)
Digits — lines 100.0% (10/10), branches 100.0% (4/4)
Emails — lines 100.0% (10/10), branches 100.0% (4/4)
Inlining — lines 100.0% (2/2), branches n/a (0/0)
Intervals — lines 100.0% (26/26), branches 100.0% (8/8)
Maybe — lines 100.0% (13/13), branches 75.0% (3/4)
Numbers — lines 100.0% (8/8), branches 100.0% (4/4)
Strings — lines 100.0% (8/8), branches 100.0% (4/4)
StrongTypes.OpenApi.TestApi.Microsoft — lines 41.0% (163/398), branches 31.6% (65/206)(root) — lines 100.0% (14/14), branches 100.0% (2/2)
obj/Debug/net10.0/Microsoft.AspNetCore.OpenApi.SourceGenerators/Microsoft.AspNetCore.OpenApi.SourceGenerators.XmlCommentGenerator — lines 38.8% (149/384), branches 30.9% (63/204)
StrongTypes.OpenApi.TestApi.Shared — lines 0.0% (0/193), branches n/a (0/0)(root) — lines 0.0% (0/193), branches n/a (0/0)
StrongTypes.OpenApi.TestApi.Swashbuckle — lines 100.0% (11/11), branches 100.0% (2/2)(root) — lines 100.0% (11/11), branches 100.0% (2/2)
|
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #127 +/- ##
=======================================
Coverage 85.09% 85.09%
=======================================
Files 172 172
Lines 4852 4852
Branches 992 992
=======================================
Hits 4129 4129
Misses 494 494
Partials 229 229
🚀 New features to boost your workflow:
|
KaliCZ
commented
Jul 17, 2026
KaliCZ
left a comment
Owner
Author
There was a problem hiding this comment.
Which reminds me - how about tests for Maui too? And can you make sure that our WPF suite is also as complete as this?
| v2 change. If "cleared means null" matters, bind through a `string?` view-model | ||
| property and convert in the view-model. | ||
|
|
||
| ## WinForms |
Owner
Author
There was a problem hiding this comment.
So perhaps this should be desktop.md instead of wpf.md?
Maybe desktop (wpf/winform/maui)?
| …where `Name` is a view-model property of type `NonEmptyString`. `ValidatesOnExceptions=True` is the load-bearing piece: it turns the `ArgumentException` a strong type throws on invalid input into a `ValidationError`, driving WPF's standard red-border template. Without it the binding swallows the failure silently. | ||
|
|
||
| The same `TypeDescriptor` mechanism backs WinForms and designers. MAUI and Avalonia aren't covered yet — see [issue #94](https://github.com/KaliCZ/StrongTypes/issues/94). | ||
| The same `TypeDescriptor` mechanism backs WinForms — verified end-to-end by the `StrongTypes.WinForms.Tests` binding suite (there the culture default is `Binding.FormatInfo ?? CultureInfo.CurrentCulture`, and invalid input surfaces via `Binding.BindingComplete` instead of a validation error) — and designers. MAUI and Avalonia aren't covered yet — see [issue #94](https://github.com/KaliCZ/StrongTypes/issues/94). |
Owner
Author
There was a problem hiding this comment.
Nobody cares here. This is a user facing documentation. Just change the docs to list the technologies that are supported and that's it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rename Skill/references/wpf.md to desktop.md and restructure it around the shared TypeDescriptor mechanism with WPF and WinForms sections; update the SKILL.md and configuration.md links. - Readme lists supported technologies plainly instead of test-suite internals. - Parity between the two desktop suites: WPF gains the recovery-after- invalid-input case; WinForms gains Digit coverage, non-numeric input, and nullable value display. - Correct the MAUI status: empirically (headless Microsoft.Maui.Controls 10.0.80) display binds work for every strong type, but MAUI's target-to-source conversion never consults TypeConverters, so typed input silently never reaches the source for any strong type - an explicit IValueConverter is required regardless of converter shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every invalid-input test now also asserts the failure is observable (BindingComplete with Exception state, WinForms' analogue of the WPF validation error), the culture write-back theory gains the invalid and negative rows the WPF suite pins, and the FormatInfo display theory round-trips the displayed text back to the source via WriteValue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Why
Closes out the WinForms section of #94. The issue's remaining ask there was verification, not plumbing: confirm the on-type
[TypeConverter]s are picked up end-to-end by WinFormsBindingwith a hosted, handle-created control, and ship a WinForms test project as the proof.What
New
StrongTypes.WinForms.Testssuite (30 tests), mirroring the WPF one — referencesStrongTypesand nothing else:NonEmptyString,Email, andPositive<int>/Positive<decimal>: display, source→control, control→source with conversion throughTypeDescriptor.0) leaves the source at its last valid value and surfaces throughBinding.BindingCompletewithBindingCompleteState.Exception— WinForms' analogue of WPF'sValidatesOnExceptions.Unhosted_BindingStaysDormantlocks this, and theHostedFormhelper shows the form off-screen so CI never flashes a window.FormatInfo ?? CultureInfo.CurrentCulture— the opposite default from WPF, which ignores the host culture.CultureBindingTestsruns the explicit-FormatInfocases under four host cultures (host ignored, wrong-culture separators swallowed as grouping — same trap as WPF) and pins the host-culture default.Wiring
test-winforms.yml— dedicatedwindows-latestworkflow, mirroringtest-wpf.yml./WinForms/folder; the Ubuntu build compiles the project viaEnableWindowsTargeting,dotnet testskips it off-Windows (same mechanism as the WPF suite).testing.md— new "WinForms binding tests" section (activation requirement, culture default, representative-types rule).Skill/references/wpf.md— WinForms promoted from "should work, tested only for WPF" to verified, with the two WinForms-specific notes (activation, culture); readme WPF section updated the same way.After this, what's left on #94 is MAUI (the numeric-wrapper converter gap —
StrongTypeConverterhas no parameterless ctor for MAUI's reflective instantiation — plus verification for the string-like types), and the WinUI/Avalonia investigations.🤖 Generated with Claude Code