Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
192 changes: 192 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue30535.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 30535, "[Windows] RefreshView IsRefreshing property not working while binding", PlatformAffected.All)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💡 Suggestion — PlatformAffected.All overstates the fix scope

The only modified production file is RefreshViewHandler.Windows.cs. The [Issue] attribute would be more accurate as PlatformAffected.UWP (the closest Windows-targeted value in the enum). This is metadata-only and does not affect test execution, but helps future triage correctly identify the platform scope:

[Issue(IssueTracker.Github, 30535,
    "[Windows] RefreshView IsRefreshing property not working while binding",
    PlatformAffected.UWP)]

public class Issue30535 : NavigationPage
{
public Issue30535()
: base(new Issue30535MainPage())
{
}
}

class Issue30535MainPage : ContentPage
{
Issue30535MainViewModel _viewModel;

public Issue30535MainPage()
: this(new Issue30535MainViewModel())
{
}

public Issue30535MainPage(Issue30535MainViewModel viewModel)
{
_viewModel = viewModel;
BindingContext = _viewModel;
Title = "Main";

var optionsToolbarItem = new ToolbarItem
{
Text = "Options",
AutomationId = "Options"
};
optionsToolbarItem.Clicked += OnNavigateToControlPageClicked;
ToolbarItems.Add(optionsToolbarItem);

var infoLabel = new Label
{
Text = "Pull down to refresh this content",
FontSize = 18,
HorizontalOptions = LayoutOptions.Center
};

var isRefreshingLabel = new Label
{
FontSize = 16,
HorizontalOptions = LayoutOptions.Center,
TextColor = Colors.Blue
};
isRefreshingLabel.SetBinding(Label.TextProperty, new Binding(nameof(Issue30535MainViewModel.IsRefreshing), stringFormat: "IsRefreshing: {0}"));

var boxContent = new BoxView
{
Color = Colors.Green,
HeightRequest = 100,
WidthRequest = 200,
HorizontalOptions = LayoutOptions.Center,
AutomationId = "BoxContent"
};

var stackLayout = new StackLayout
{
Spacing = 20,
Padding = 20,
Children =
{
infoLabel,
isRefreshingLabel,
boxContent,
}
};

var scrollView = new ScrollView
{
Content = stackLayout
};

var refreshView = new RefreshView
{
Content = scrollView,
RefreshColor = Colors.Red
};
refreshView.SetBinding(RefreshView.IsRefreshingProperty, nameof(Issue30535MainViewModel.IsRefreshing), mode: BindingMode.TwoWay);

Content = refreshView;
}

async void OnNavigateToControlPageClicked(object sender, EventArgs e)
{
BindingContext = _viewModel = new Issue30535MainViewModel();
await Navigation.PushAsync(new RefreshControlPage(_viewModel));
}
}

public class RefreshControlPage : ContentPage
{
readonly Issue30535MainViewModel _viewModel;

public RefreshControlPage(Issue30535MainViewModel viewModel)
{
_viewModel = viewModel;
BindingContext = _viewModel;
Title = "Options";

var applyToolbarItem = new ToolbarItem
{
Text = "Apply",
AutomationId = "Apply"
};
applyToolbarItem.Clicked += OnBackClicked;
ToolbarItems.Add(applyToolbarItem);

var currentValueLabel = new Label
{
FontSize = 18,
HorizontalOptions = LayoutOptions.Center,
TextColor = Colors.Blue
};
currentValueLabel.SetBinding(Label.TextProperty, new Binding(nameof(Issue30535MainViewModel.IsRefreshing), mode: BindingMode.TwoWay, stringFormat: "Current IsRefreshing: {0}"));

var setTrueButton = new Button
{
Text = "Set IsRefreshing = True",
BackgroundColor = Colors.Green,
AutomationId = "SetIsRefreshingTrue",
TextColor = Colors.White
};
setTrueButton.Clicked += OnSetTrueClicked;

var setFalseButton = new Button

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💡 Suggestion — setFalseButton missing AutomationId

The "Set IsRefreshing = False" button is key to the two-way binding scenario (resetting the refresh state from a bound property), but without an AutomationId it cannot be tapped from UI tests. Adding one now avoids a follow-up change if a reset-direction test is ever added:

var setFalseButton = new Button
{
    Text = "Set IsRefreshing = False",
    AutomationId = "SetIsRefreshingFalse",
    BackgroundColor = Colors.Red,
    TextColor = Colors.White
};

{
Text = "Set IsRefreshing = False",
BackgroundColor = Colors.Red,
TextColor = Colors.White
};
setFalseButton.Clicked += OnSetFalseClicked;

Content = new VerticalStackLayout
{
Spacing = 20,
Padding = 20,
VerticalOptions = LayoutOptions.Center,
Children =
{
currentValueLabel,
setTrueButton,
setFalseButton
}
};
}

void OnSetTrueClicked(object sender, EventArgs e)
{
_viewModel.IsRefreshing = true;
}

void OnSetFalseClicked(object sender, EventArgs e)
{
_viewModel.IsRefreshing = false;
}

async void OnBackClicked(object sender, EventArgs e)
{
await Navigation.PopAsync();
}
}

public class Issue30535MainViewModel : INotifyPropertyChanged
{
bool _isRefreshing;

public bool IsRefreshing
{
get => _isRefreshing;
set
{
if (_isRefreshing == value)
return;

_isRefreshing = value;
OnPropertyChanged();
}
}

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue30535 : _IssuesUITest
{
public override string Issue => "[Windows] RefreshView IsRefreshing property not working while binding";

public Issue30535(TestDevice device) : base(device)
{
}

[Test]
[Category(UITestCategories.RefreshView)]
public void RefreshViewSetIsRefreshingValueinNewPageAndVerifyStatus()
{
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
App.WaitForElement("Options");
App.Tap("Options");
App.WaitForElement("SetIsRefreshingTrue");
App.Tap("SetIsRefreshingTrue");
App.WaitForElement("Apply");
App.Tap("Apply");
VerifyScreenshot();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Warning — Race condition: VerifyScreenshot() called immediately after async navigation

App.Tap("Apply") triggers Navigation.PopAsync() — an async operation. The fix mechanism requires: (1) navigation back completes, (2) RefreshContainer.Loaded fires, (3) DispatcherQueue.TryEnqueue(UpdateIsRefreshing) executes. Without a stable wait, the screenshot can land during the slide-back animation before the dispatched callback has run — causing the test to pass on the unfixed code too (no spinner visible yet).

Add a wait for a stable main-page element:

App.Tap("Apply");
App.WaitForElement("BoxContent");   // wait for main page fully loaded
VerifyScreenshot();

Or use the retry overload:

VerifyScreenshot(retryDelay: TimeSpan.FromSeconds(2));

}
Comment thread
devanathan-vaithiyanathan marked this conversation as resolved.
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ void OnLoaded(object sender, RoutedEventArgs e)
if (sender is not RefreshContainer refreshControl)
return;

refreshControl.Loaded -= OnLoaded;

// If the virtual view requested a refresh, we need to trigger it now that the control
// is loaded. This needs to be done on a dispatch as the control may need to be laid
// out or the template applied first.
Expand Down
Loading