Skip to content

Cover two-way binding (@bind-) in the interop contracts - #315

Merged
damyanpetev merged 4 commits into
masterfrom
dpetev/two-way-handler-unit-tests-tabs-fix
Aug 6, 2026
Merged

Cover two-way binding (@bind-) in the interop contracts#315
damyanpetev merged 4 commits into
masterfrom
dpetev/two-way-handler-unit-tests-tabs-fix

Conversation

@damyanpetev

@damyanpetev damyanpetev commented Aug 5, 2026

Copy link
Copy Markdown
Member

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.SelectedChanged comparing an EventCallback<bool> against EventCallback<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: 17 X/XChanged pairs, zero contract specs, and one hand-written test for IgbChip. The integration sweep drops every bind member by reflection, so nothing covered them.

What changed

  • New .Bind contract section. One line per pair, arranged with bUnit's own ps.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 .Prop spec (or rendered-attribute fact) already pins.
  • All 17 pairs covered — 17 .Bind specs across 15 suites (the IgbCheckboxBase pair is pinned on both CheckboxTests and SwitchTests). Payloads reuse each driving event's existing .Event spec.
  • IgbTab.Selected is the one exception: IgbTab has no events and registers nothing, so its pair is driven entirely by IgbTabs.Change. It's covered inside that event's spec, whose arrange binds both children's SelectedChanged and asserts each receives the right value.
  • Fixed the two mismatched guards, Tab.cs and Tabs.cs, two lines. Every generated occurrence was already type-correct.
  • Dates now pin the inbound conversion. Decoding converts to local time; a spec states the UTC instant and the runner compares against its local rendering and asserts Kind — which is what makes it hold in every timezone, including a UTC CI box. Unspecified expectations throw, and AssertReturn applies 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):

  • Bug fix
  • New functionality
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactoring (no functional changes)
  • Documentation
  • Demos
  • CI/CD
  • Tests
  • Changelog

Component(s) / Area(s) Affected:

Tabs, Components with two-way bindable props

How Has This Been Tested?

  • Unit tests
  • Manual testing
  • Automated e2e tests

Test Configuration:

  • .NET version:
  • Hosting model:
  • Browser(s):
  • OS:

Checklist:

  • All relevant tags have been applied to this PR
  • This PR includes unit tests covering all the new code
  • This PR includes API docs for newly added methods/properties
  • This PR includes general feature table updates in the root README.MD
  • This PR includes CHANGELOG.MD updates for newly added functionality
  • This PR contains breaking changes
  • This PR includes behavioral changes and the feature specification has been updated with them
  • Accessibility (ARIA, keyboard navigation, focus management) has been verified

@damyanpetev damyanpetev added 🐛 bug Something isn't working 🧪 ci: tests tabs labels Aug 5, 2026
@damyanpetev
damyanpetev marked this pull request as ready for review August 5, 2026 16:10
Copilot AI review requested due to automatic review settings August 5, 2026 16:10
@damyanpetev
damyanpetev enabled auto-merge August 5, 2026 16:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 a VerifyBindContract() runner to validate inbound two-way binding behavior end-to-end.
  • Adds bind contract coverage for the X/XChanged pairs across affected component test suites (including the Tabs/Tab special case driven by IgbTabs.Change).
  • Fixes incorrect empty-callback guards in Tab.cs and Tabs.cs for SelectedChanged (wrong EventCallback<T>.Empty type).

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 tabSelection to null per run, these assertions should fail if the callbacks were never invoked (rather than treating null as 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]);

Comment thread tests/IgniteUI.Blazor.Tests/ComponentWithContractTestBase.cs
Comment thread tests/IgniteUI.Blazor.Tests/TabsTests.cs Outdated
@damyanpetev
damyanpetev force-pushed the dpetev/two-way-handler-unit-tests-tabs-fix branch from 3f385fe to 36d1375 Compare August 5, 2026 16:44
@damyanpetev
damyanpetev requested a review from Copilot August 5, 2026 16:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 rejects DateTimeKind.Unspecified and will accept Kind=Local, which can make specs timezone-dependent. Consider enforcing Kind=Utc here.
        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

  • tabSelection is a bool?[] that starts at null to 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. Assert HasValue first, then check .Value so 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
damyanpetev requested a review from MayaKirova August 5, 2026 16:51
@damyanpetev
damyanpetev force-pushed the dpetev/two-way-handler-unit-tests-tabs-fix branch from 36d1375 to c1c5036 Compare August 5, 2026 17:15
MayaKirova
MayaKirova previously approved these changes Aug 6, 2026
@damyanpetev
damyanpetev force-pushed the dpetev/two-way-handler-unit-tests-tabs-fix branch from c1c5036 to 8232913 Compare August 6, 2026 11:23
@MayaKirova
MayaKirova self-requested a review August 6, 2026 11:25
@damyanpetev
damyanpetev merged commit 193cbe2 into master Aug 6, 2026
7 checks passed
@damyanpetev
damyanpetev deleted the dpetev/two-way-handler-unit-tests-tabs-fix branch August 6, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 bug Something isn't working tabs 🧪 ci: tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants