Closed adospace closed 3 months ago
I've added the timeout parameter to the IElementController extensions methods: be aware that you still need to provide the ITemplateHost to the method.
Thanks for adding, but not working like the other method. Here's my complete test. I'm ultimately trying to get all the way to ModelPage
and verify the text in the label on that page.
public class TestShellPage : Component
{
private MauiControls.Shell shell;
/// <inheritdoc />
public override VisualNode Render() =>
new Shell(x => shell = x!)
{
new ShellContent("Models")
.AutomationId("Models")
.Icon(FontImages.Feature)
.RenderContent(() => new ListPage().Shell(shell))
}
.AutomationId("MainShell");
}
public record Model(string Id, string Name);
public partial class ListPage : Component<ListPage.PageState>
{
[Prop("Shell")] protected MauiControls.Shell shellRef;
public override VisualNode Render() =>
new ContentPage
{
new CollectionView().AutomationId("list")
.ItemsSource(State.Items, Render)
};
protected override void OnMounted()
{
Routing.RegisterRoute<ModelPage>("model");
Task.Run(
async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
SetState(s => s.Items = new[] { new Model("m1", "model name 1") });
});
base.OnMounted();
}
private VisualNode Render(Model item) =>
new VStack(5)
{
new Label(item.Name).AutomationId(item.Id + "-name"),
new Label(item.Id).AutomationId(item.Id + "-id")
.FontSize(12)
.TextColor(Colors.Gray)
}.AutomationId(item.Id + "-stack")
.OnTapped(
async () => await shellRef.GoToAsync<ModelPage.Props2>(
"model",
props => props.Id = item.Id))
.Margin(5, 10);
public class PageState
{
public Model[] Items { get; set; } =
[
];
}
}
public class ModelPage : Component<ModelPage.PageState, ModelPage.Props2>
{
public override VisualNode Render() =>
new ContentPage("Model")
{
new Label(State.Item?.Name)
.AutomationId("name")
.VCenter()
.HCenter()
};
protected override void OnMountedOrPropsChanged()
{
Task.Run(
async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
SetState(s => s.Item = new Model("m1", "model name 1"));
});
base.OnMounted();
}
public class PageState
{
public Model? Item { get; set; }
}
public class Props2
{
public string Id { get; set; } = null!;
}
}
public class BugIntegrationTests
{
[Fact]
public async Task VerifyLabelOnModelPage()
{
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
using var serviceContext = new ServiceContext(provider);
var mainPage = TemplateHost.Create(new TestShellPage());
var shell = mainPage.Find<MauiControls.Shell>("MainShell");
var models = shell.Find<MauiControls.ShellContent>("Models");
shell.CurrentItem = models;
// first way
var name = await mainPage.Find<MauiControls.Label>("m1-name", TimeSpan.FromSeconds(30));
name.Text.Should()
.Be("model name 1");
// this way should also work
var name2 = await models.Find<MauiControls.Label>(mainPage, "m1-name", TimeSpan.FromSeconds(30));
name2.Text.Should()
.Be("model name 1");
// get and click on item in collection view
var stack = await mainPage.Find<MauiControls.VerticalStackLayout>("m1-stack", TimeSpan.FromSeconds(30));
// how to tap the stack??
// get ModelPage and get the label
// test the label of ModelPage
}
}
ok, a few things:
1) I usually make unit tests on single functions/single components, what you're doing looks like more of an integration test that may be more suitable for tools like Appium.
2) models.Find<>
above doesn't work because it relies on the MAUI .NET function IElementController.Descendant()
that doesn't navigate to views created with a template: so, it won't find the rendered page inside the ShellContent or the the list items inside the CollectionView.
To find those controls you need to use the ITemplateHost.Find<>()
provided by MauiReactor that instead visits all the nodes forcing the creation of the children.
3) Unfortunately the Tapped event can't be raised easily because MAUI team has made the SendTapped event internal:
https://github.com/dotnet/maui/blob/51cabfd4002e5b7d3617a5177251251f58ac73c1/src/Controls/src/Core/TapGestureRecognizer.cs#L56C33-L56C39 but I'm looking for a way to call it using reflection. I'll be back with a solution shortly
ok, this seems working fine:
public class TestBug220ShellPage : Component
{
private MauiControls.Shell? shell;
/// <inheritdoc />
public override VisualNode Render() =>
new Shell(x => shell = x!)
{
new ShellContent("Models")
.AutomationId("Models")
//.Icon(FontImages.Feature)
.RenderContent(() => new ListPage().Shell(shell))
}
.AutomationId("MainShell");
}
public record Model(string Id, string Name);
public partial class ListPage : Component<ListPage.PageState>
{
[Prop("Shell")] protected MauiControls.Shell? shellRef;
public override VisualNode Render() =>
new ContentPage
{
new CollectionView()
.AutomationId("list")
.ItemsSource(State.Items, Render)
}
.AutomationId("Models_page");
protected override void OnMounted()
{
Routing.RegisterRoute<TestBug220ModelPage>("model");
Task.Run(
async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
SetState(s => s.Items = new[] { new Model("m1", "model name 1") });
});
base.OnMounted();
}
private VisualNode Render(Model item) =>
new VStack(5)
{
new Label(item.Name).AutomationId(item.Id + "-name"),
new Label(item.Id).AutomationId(item.Id + "-id")
.FontSize(12)
.TextColor(Colors.Gray)
}.AutomationId(item.Id + "-stack")
.OnTapped(
async () => await shellRef!.GoToAsync<TestBug220ModelPage.Props2>(
"model",
props => props.Id = item.Id))
.Margin(5, 10);
public class PageState
{
public Model[] Items { get; set; } =
[
];
}
}
public class TestBug220ModelPage : Component<TestBug220ModelPage.PageState, TestBug220ModelPage.Props2>
{
public override VisualNode Render() =>
new ContentPage("Model")
{
new Label(State.Item?.Name)
.AutomationId(State.Item?.Id ?? string.Empty)
.VCenter()
.HCenter()
};
protected override void OnMountedOrPropsChanged()
{
Task.Run(
async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
SetState(s => s.Item = new Model("m2", "model name 2"));
});
base.OnMounted();
}
public class PageState
{
public Model? Item { get; set; }
}
public class Props2
{
public string Id { get; set; } = null!;
}
}
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
using var serviceContext = new ServiceContext(provider);
var mainPage = TemplateHost.Create(new TestBug220ShellPage());
var shell = mainPage.Find<MauiControls.Shell>("MainShell");
var models = shell.Find<MauiControls.ShellContent>("Models");
var modelsPage = mainPage.Find<MauiControls.ContentPage>("Models_page");
shell.CurrentItem = models;
// first way
var name = await mainPage.Find<MauiControls.Label>("m1-name", TimeSpan.FromSeconds(30));
name.ShouldNotBeNull();
name.Text.ShouldBe("model name 1");
// get and click on item in collection view
var stack = await mainPage.Find<MauiControls.VerticalStackLayout>("m1-stack", TimeSpan.FromSeconds(30));
stack.ShouldNotBeNull();
// how to tap the stack??
var tapGestureRecognizer = stack.GestureRecognizers.OfType<MauiControls.TapGestureRecognizer>().Single();
tapGestureRecognizer
.GetType()
.GetMethod("SendTapped", BindingFlags.Instance | BindingFlags.NonPublic)
.ShouldNotBeNull().Invoke(tapGestureRecognizer, new[] { stack, null });
// get ModelPage and get the label
var label = await mainPage.Find<MauiControls.Label>("m2", TimeSpan.FromSeconds(5));
label.ShouldNotBeNull();
// test the label of ModelPage
label.Text.ShouldBe("model name 2");
Originally posted by @powerdude in https://github.com/adospace/reactorui-maui/issues/216#issuecomment-1977255492