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
23 changes: 21 additions & 2 deletions src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,30 @@ IWindow CreateWindowForContent(IElement view)
return window;
}

protected Task CreateHandlerAndAddToWindow(IElement view, Func<Task> action)
{
return CreateHandlerAndAddToWindow<IWindowHandler>(CreateWindowForContent(view), handler =>
{
return action();
}, MauiContext, null);
}

protected Task CreateHandlerAndAddToWindow<THandler>(IElement view, Func<THandler, Task> action)
where THandler : class, IElementHandler
{
return CreateHandlerAndAddToWindow<THandler>(view, handler =>
{
return action(handler);
}, MauiContext, null);
}

protected Task CreateHandlerAndAddToWindow(IElement view, Action action)
{
return CreateHandlerAndAddToWindow<IWindowHandler>(CreateWindowForContent(view), handler =>
{
action();
return Task.CompletedTask;
});
}, MauiContext, null);
}

protected Task CreateHandlerAndAddToWindow<THandler>(IElement view, Action<THandler> action)
Expand All @@ -113,7 +130,7 @@ protected Task CreateHandlerAndAddToWindow<THandler>(IElement view, Action<THand
{
action(handler);
return Task.CompletedTask;
});
}, MauiContext, null);
}

static SemaphoreSlim _takeOverMainContentSempahore = new SemaphoreSlim(1);
Expand Down Expand Up @@ -175,6 +192,8 @@ await SetupWindowForTests<THandler>(window, async () =>

await OnLoadedAsync(content as VisualElement);

// Gives time for the measure/layout pass to settle
await Task.Yield();
if (view is VisualElement veBeingTested)
await OnLoadedAsync(veBeingTested);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(tabbedPage), asy

BottomNavigationView GetBottomNavigationView(TabbedViewHandler tabViewHandler)
{
var layout = (tabViewHandler.PlatformView as Android.Views.IViewParent).FindParent((view) => view is CoordinatorLayout)
var layout = tabViewHandler.PlatformView.FindParent((view) => view is CoordinatorLayout)
as CoordinatorLayout;

return layout.GetFirstChildOfType<BottomNavigationView>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Xunit.Abstractions;

namespace Microsoft.Maui.DeviceTests
{
public class FindVisualTreeElementInsideTestCase : IXunitSerializable
{

public string TestCaseName { get; private set; }

public FindVisualTreeElementInsideTestCase() { }

public FindVisualTreeElementInsideTestCase(string testCaseName)
{
TestCaseName = testCaseName;
}

public void Deserialize(IXunitSerializationInfo info)
{
TestCaseName = info.GetValue<string>(nameof(TestCaseName));
}

public void Serialize(IXunitSerializationInfo info)
{
info.AddValue(nameof(TestCaseName), TestCaseName, typeof(string));
}

public override string ToString()
{
return TestCaseName;
}

public (VisualElement rootView, VisualElement testView) CreateVisualElement()
{
switch (TestCaseName)
{
case "CollectionView":
{
var cv = new CollectionView();
NestingView view = new NestingView();
cv.ItemTemplate = new DataTemplate(() =>
{
return view;
});
cv.ItemsSource = new[] { 0 };
return (cv, view);
}
case "ContentView":
{
var contentView = new ContentView();
NestingView view = new NestingView();
contentView.ControlTemplate = new ControlTemplate(() =>
{
return view;
});
return (contentView, view);
}
}

throw new Exception(String.Concat(TestCaseName, " ", "Not Found"));
}
}

public class FindVisualTreeElementInsideTestCases : IEnumerable<object[]>
{
private readonly List<object[]> _data = new()
{
new object[] { new FindVisualTreeElementInsideTestCase("CollectionView") },
new object[] { new FindVisualTreeElementInsideTestCase("ContentView") }
};

public IEnumerator<object[]> GetEnumerator()
=> _data.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Handlers;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform;
using Xunit;
using System.Collections.Generic;
using ContentView = Microsoft.Maui.Controls.ContentView;
using Microsoft.Maui.Controls.Handlers.Items;
#if ANDROID || IOS || MACCATALYST
using ShellHandler = Microsoft.Maui.Controls.Handlers.Compatibility.ShellRenderer;
#endif

namespace Microsoft.Maui.DeviceTests
{
[Category(TestCategory.VisualElementTree)]
#if ANDROID || IOS || MACCATALYST
[Collection(ControlsHandlerTestBase.RunInNewWindowCollection)]
#endif
public partial class VisualElementTreeTests : ControlsHandlerTestBase
{
void SetupBuilder()
{
EnsureHandlerCreated(builder =>
{
builder.SetupShellHandlers();

builder.ConfigureMauiHandlers(handlers =>
{
#if IOS || MACCATALYST
handlers.AddHandler(typeof(Controls.NavigationPage), typeof(Controls.Handlers.Compatibility.NavigationRenderer));
#else
handlers.AddHandler(typeof(Controls.NavigationPage), typeof(NavigationViewHandler));
#endif
handlers.AddHandler<NestingView, NestingViewHandler>();
handlers.AddHandler<ContentView, ContentViewHandler>();
handlers.AddHandler<CollectionView, CollectionViewHandler>();
});
});
}

[Fact]
public async Task GetVisualTreeElements()
{
SetupBuilder();
var label = new Label() { Text = "Find Me" };
var page = new ContentPage() { Title = "Title Page" };
page.Content = new VerticalStackLayout()
{
label
};

var rootPage = await InvokeOnMainThreadAsync(() =>
new NavigationPage(page)
);

await CreateHandlerAndAddToWindow<IWindowHandler>(rootPage, async handler =>
{
await OnFrameSetToNotEmpty(label);
var locationOnScreen = label.GetLocationOnScreen().Value;
var labelFrame = label.Frame;
var window = rootPage.Window;

// Find label at the top left corner
var topLeft = new Graphics.Point(locationOnScreen.X + 1, locationOnScreen.Y + 1);

Assert.True(window.GetVisualTreeElements(topLeft).Contains(label), $"Unable to find label using top left coordinate: {topLeft} with label location: {label.GetBoundingBox()}");

// find label at the bottom right corner
var bottomRight = new Graphics.Point(
locationOnScreen.X + labelFrame.Width - 1,
locationOnScreen.Y + labelFrame.Height - 1);

Assert.True(window.GetVisualTreeElements(bottomRight).Contains(label), $"Unable to find label using bottom right coordinate: {bottomRight} with label location: {label.GetBoundingBox()}");

// Ensure that the point directly outside the bounds of the label doesn't
// return the label
Assert.DoesNotContain(label, window.GetVisualTreeElements(
locationOnScreen.X + labelFrame.Width + 1,
locationOnScreen.Y + labelFrame.Height + 1
));

});
}

[Fact]
public async Task FindPlatformViewInsideLayout()
{
SetupBuilder();
var button = new Button();
VerticalStackLayout views = new VerticalStackLayout()
{
new VerticalStackLayout()
{
button
}
};

await CreateHandlerAndAddToWindow(views, () =>
{
var platformView = button.ToPlatform();
var foundTreeElement = button.ToPlatform().GetVisualTreeElement();

Assert.Equal(button, foundTreeElement);
});
}

[Fact]
public async Task FindPlatformViewInsideScrollView()
{
SetupBuilder();
var button = new Button();
ScrollView view = new ScrollView()
{
Content = button
};

await CreateHandlerAndAddToWindow(view, () =>
{
var platformView = button.ToPlatform();
var foundTreeElement = button.ToPlatform().GetVisualTreeElement();

Assert.Equal(button, foundTreeElement);
});
}

[Fact]
public async Task FindPlatformViewViaDefaultContainer()
{
SetupBuilder();
var button = new Button();
NestingView view = new NestingView();
view.AddLogicalChild(button);

await CreateHandlerAndAddToWindow(view, () =>
{
var platformView = button.ToPlatform();
var foundTreeElement = button.ToPlatform().GetVisualTreeElement();

Assert.Equal(button, foundTreeElement);
});
}

[Fact]
public async Task FindVisualTreeElementWithArbitraryPlatformViewsAdded()
{
SetupBuilder();
var button = new Button();
NestingView view = new NestingView();

await CreateHandlerAndAddToWindow<NestingViewHandler>(view, (handler) =>
{
handler
.PlatformView
.AddChild()
.AddChild()
.AddChild()
.AddChild(button, view);

var platformView = button.ToPlatform();
var foundTreeElement = button.ToPlatform().GetVisualTreeElement();

Assert.Equal(button, foundTreeElement);
});
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task FindFirstMauiParentElement(bool searchAncestors)
{
SetupBuilder();
var viewToLocate = new NestingView();
NestingView view = new NestingView();

await CreateHandlerAndAddToWindow<NestingViewHandler>(view, (handler) =>
{
var nestedChild =
handler.PlatformView
.AddChild<NestingViewPlatformView>(viewToLocate, view)
.AddChild()
.AddChild()
.AddChild();

var foundTreeElement = nestedChild.GetVisualTreeElement(searchAncestors);

if (searchAncestors)
Assert.Equal(viewToLocate, foundTreeElement);
else
Assert.Null(foundTreeElement);
});
}

[Theory]
[ClassData(typeof(FindVisualTreeElementInsideTestCases))]
public async Task FindPlatformViewInsideView(FindVisualTreeElementInsideTestCase testCase)
{
SetupBuilder();

VisualElement rootView;
VisualElement viewToLocate;

(rootView, viewToLocate) = testCase.CreateVisualElement();
await CreateHandlerAndAddToWindow(rootView, () =>
{
var platformView = viewToLocate.ToPlatform();
var foundTreeElement = platformView.GetVisualTreeElement();
Assert.Equal(viewToLocate, foundTreeElement);
});
}
}
}
Loading