Cover two-way binding (@bind-) in the interop contracts - #315
Merged
Conversation
damyanpetev
marked this pull request as ready for review
August 5, 2026 16:10
damyanpetev
enabled auto-merge
August 5, 2026 16:12
Contributor
There was a problem hiding this comment.
Pull request overview
Adds first-class interop contract coverage for Blazor two-way binding (@bind-) across the component wrappers, and fixes the IgbTab/IgbTabs two-way binding guard bug that previously made the unbind branch dead.
Changes:
- Introduces a new
.Bind(...)contract spec type plus aVerifyBindContract()runner to validate inbound two-way binding behavior end-to-end. - Adds bind contract coverage for the
X/XChangedpairs across affected component test suites (including the Tabs/Tab special case driven byIgbTabs.Change). - Fixes incorrect empty-callback guards in
Tab.csandTabs.csforSelectedChanged(wrongEventCallback<T>.Emptytype).
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/IgniteUI.Blazor.Tests/TextareaTests.cs | Adds .Bind(Value/ValueChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/TabsTests.cs | Extends Change event contract to validate child-tab @bind-Selected propagation. |
| tests/IgniteUI.Blazor.Tests/SwitchTests.cs | Adds .Bind(Checked/CheckedChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/SliderTests.cs | Adds .Bind(Value/ValueChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/SelectTests.cs | Adds .Bind(Value/ValueChanged) coverage for value projection from selected item. |
| tests/IgniteUI.Blazor.Tests/RatingTests.cs | Adds .Bind(Value/ValueChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/RadioTests.cs | Adds .Bind coverage for both IgbRadio and IgbRadioGroup, plus bind runner facts. |
| tests/IgniteUI.Blazor.Tests/MaskInputTests.cs | Adds .Bind(Value/ValueChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/Interop/ComponentContract.cs | Adds BindContractSpec and .Bind(...) DSL entry point to the contract model. |
| tests/IgniteUI.Blazor.Tests/InputTests.cs | Adds .Bind(Value/ValueChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/DateTimeInputTests.cs | Adds .Bind(Value/ValueChanged) coverage for inbound date conversion expectations. |
| tests/IgniteUI.Blazor.Tests/DateRangePickerTests.cs | Adds .Bind(Value/ValueChanged) coverage for complex value payloads (with custom assertion). |
| tests/IgniteUI.Blazor.Tests/DatePickerTests.cs | Adds .Bind(Value/ValueChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/ComponentWithContractTestBase.cs | Adds VerifyBindContract(), bind section fact enforcement, and date-assertion enhancements. |
| tests/IgniteUI.Blazor.Tests/ComboTests.cs | Adds .Bind(Value/ValueChanged) coverage for uuid-ref payloads with arranged data. |
| tests/IgniteUI.Blazor.Tests/ChipTests.cs | Adds .Bind(Selected/SelectedChanged) coverage driven by Select event. |
| tests/IgniteUI.Blazor.Tests/CheckboxTests.cs | Adds .Bind(Checked/CheckedChanged) coverage and bind runner fact. |
| tests/IgniteUI.Blazor.Tests/CalendarTests.cs | Adds .Bind(Value/ValueChanged) and .Bind(Values/ValuesChanged) including date collection handling. |
| src/componentsBase/WebInputs/Tabs.cs | Fixes SelectedChanged empty-guard type (string→bool) in IgbTabs change handler. |
| src/componentsBase/WebInputs/Tab.cs | Fixes SelectedChanged empty-guard type (string→bool) in IgbTab setter. |
| skills/igniteui-blazor-lite-testing/SKILL.md | Updates testing skill docs to include @bind- interop contract coverage. |
| skills/igniteui-blazor-lite-testing/references/interop-contracts.md | Extends authoring guide with .Bind and date decoding rules/expectations. |
Suppressed comments (1)
tests/IgniteUI.Blazor.Tests/TabsTests.cs:39
- After resetting
tabSelectiontonullper run, these assertions should fail if the callbacks were never invoked (rather than treatingnullas an acceptable value). Using null-coalescing makes the intent explicit and avoids a false pass when nothing wrote the holders.
Assert.True(args.Detail.Selected);
Assert.False(cut.Instance.ActualTabsCollection[0].Selected);
Assert.False(tabSelection[0]);
Assert.True(tabSelection[1]);
damyanpetev
force-pushed
the
dpetev/two-way-handler-unit-tests-tabs-fix
branch
from
August 5, 2026 16:44
3f385fe to
36d1375
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/IgniteUI.Blazor.Tests/ComponentWithContractTestBase.cs:624
AssertDecodedDate’s doc comment and the authoring guide require expectations to be stated as a UTC instant so comparisons are stable across timezones, but the helper currently only rejectsDateTimeKind.Unspecifiedand will acceptKind=Local, which can make specs timezone-dependent. Consider enforcingKind=Utchere.
if (expectedInstant.Kind == DateTimeKind.Unspecified)
{
throw new XunitException(
$"expected date {expectedInstant:o} has Kind=Unspecified — state the instant explicitly " +
"(DateTimeKind.Utc), since ToLocalTime and ToUniversalTime read an unspecified kind in " +
"opposite directions and would shift it silently");
}
tests/IgniteUI.Blazor.Tests/TabsTests.cs:43
tabSelectionis abool?[]that starts atnullto detect whether the child callbacks fired, but the current assertions (Assert.False(tabSelection[0])/Assert.True(tabSelection[1])) don’t make the non-null intent explicit and can be ambiguous about whether a callback actually ran. AssertHasValuefirst, then check.Valueso the test fails clearly when the callback was never invoked.
Assert.False(cut.Instance.ActualTabsCollection[0].Selected);
Assert.False(tabSelection[0]);
Assert.True(tabSelection[1]);
damyanpetev
force-pushed
the
dpetev/two-way-handler-unit-tests-tabs-fix
branch
from
August 5, 2026 17:15
36d1375 to
c1c5036
Compare
MayaKirova
previously approved these changes
Aug 6, 2026
damyanpetev
force-pushed
the
dpetev/two-way-handler-unit-tests-tabs-fix
branch
from
August 6, 2026 11:23
c1c5036 to
8232913
Compare
MayaKirova
self-requested a review
August 6, 2026 11:25
MayaKirova
approved these changes
Aug 6, 2026
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.
Description
Cover two-way binding (
@bind-) in the interop contracts and fix Tab's two-way issue (plus a date comparison update to be resilient outside UTC env) split in separate commits. Tabs turned out to be uncoverable by the two-way setup unf.Motivation / Context
Reviewing the XML docs (#286) turned up
IgbTab.SelectedChangedcomparing anEventCallback<bool>againstEventCallback<string>.Empty— always false, so its unbind branch is dead. The bug survived because the two-way-binding surface had no behavior coverage at all: 17X/XChangedpairs, zero contract specs, and one hand-written test forIgbChip. The integration sweep drops every bind member by reflection, so nothing covered them.What changed
.Bindcontract section. One line per pair, arranged with bUnit's ownps.Bind(real@bind-X). Each spec pins four things: the driving event's registration crossed the wire, the callback member kept the callback it was handed, the dispatch pushed the decoded value to the binding, and the component's property adopted it. Inbound only — the client already holds the value, and how the property serializes outbound is what its.Propspec (or rendered-attribute fact) already pins..Bindspecs across 15 suites (theIgbCheckboxBasepair is pinned on bothCheckboxTestsandSwitchTests). Payloads reuse each driving event's existing.Eventspec.IgbTab.Selectedis the one exception:IgbTabhas no events and registers nothing, so its pair is driven entirely byIgbTabs.Change. It's covered inside that event's spec, whose arrange binds both children'sSelectedChangedand asserts each receives the right value.Tab.csandTabs.cs, two lines. Every generated occurrence was already type-correct.Kind— which is what makes it hold in every timezone, including a UTC CI box.Unspecifiedexpectations throw, andAssertReturnapplies the same rule element-wise to date collections.Testing
919 tests, 897 passing, 22 skipped on net8.0/net9.0/net10.0 (was 903 — +16 bind facts). No new build warnings.
Type of Change (check all that apply):
Component(s) / Area(s) Affected:
Tabs, Components with two-way bindable props
How Has This Been Tested?
Test Configuration:
Checklist:
README.MDCHANGELOG.MDupdates for newly added functionality