Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
Fix Shell Navigation for Hierarchally registered Global Routes (#13330)
Browse files Browse the repository at this point in the history
fixes #13328 fixes #11237

* Fix Shell Navigation for Hierarchally registered Global Routes

* - fix added paths

* - fix relative hierarchical routing

* - remove extra comment

* - remove comments

* - generalize shell setter

* - additional test

* Update BaseShellItem.cs

* - fix routes pushed with longer uris

* - improve matching

* - clean up comments

* - fix multiple back navigation with hierarchies

* - fix root routes

* - fix absolute routes
  • Loading branch information
PureWeen committed Jan 15, 2021
1 parent e1ae4e1 commit de4d0bc
Show file tree
Hide file tree
Showing 8 changed files with 867 additions and 289 deletions.
8 changes: 8 additions & 0 deletions Xamarin.Forms.Core.UnitTests/ShellTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ public List<List<Element>> GenerateTestFlyoutItems()

public TestShell()
{
Routing.RegisterRoute(nameof(TestPage1), typeof(TestPage1));
Routing.RegisterRoute(nameof(TestPage2), typeof(TestPage2));
Routing.RegisterRoute(nameof(TestPage3), typeof(TestPage3));

this.Navigated += (_, __) => NavigatedCount++;
this.Navigating += (_, __) => NavigatingCount++;
}
Expand Down Expand Up @@ -458,5 +462,9 @@ public string Text
}
}
}

public class TestPage1 : ContentPage { }
public class TestPage2 : ContentPage { }
public class TestPage3 : ContentPage { }
}
}
181 changes: 181 additions & 0 deletions Xamarin.Forms.Core.UnitTests/ShellUriHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,186 @@ public async Task GlobalNavigateTwice()
Assert.AreEqual("//rootlevelcontent1/details", shell.CurrentState.Location.ToString());
}

[Test]
public async Task GlobalRoutesRegisteredHierarchicallyNavigateCorrectly()
{
Routing.RegisterRoute("first", typeof(TestPage1));
Routing.RegisterRoute("first/second", typeof(TestPage2));
Routing.RegisterRoute("first/second/third", typeof(TestPage3));
var shell = new TestShell(
CreateShellItem(shellContentRoute: "MainPage")
);

await shell.GoToAsync("//MainPage/first/second");

Assert.AreEqual(typeof(TestPage1), shell.Navigation.NavigationStack[1].GetType());
Assert.AreEqual(typeof(TestPage2), shell.Navigation.NavigationStack[2].GetType());

await shell.GoToAsync("//MainPage/first/second/third");

Assert.AreEqual(typeof(TestPage1), shell.Navigation.NavigationStack[1].GetType());
Assert.AreEqual(typeof(TestPage2), shell.Navigation.NavigationStack[2].GetType());
Assert.AreEqual(typeof(TestPage3), shell.Navigation.NavigationStack[3].GetType());
}

[Test]
public async Task GlobalRoutesRegisteredHierarchicallyNavigateCorrectlyVariation()
{
Routing.RegisterRoute("monkeys/monkeyDetails", typeof(TestPage1));
Routing.RegisterRoute("monkeyDetails/monkeygenome", typeof(TestPage2));
var shell = new TestShell(
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals2"),
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals")
);

await shell.GoToAsync("//animals/monkeys/monkeyDetails?id=123");
await shell.GoToAsync("monkeygenome");
Assert.AreEqual("//animals/monkeys/monkeyDetails/monkeygenome", shell.CurrentState.Location.ToString());
}

[Test]
public async Task GlobalRoutesRegisteredHierarchicallyWithDoublePop()
{
Routing.RegisterRoute("monkeys/monkeyDetails", typeof(TestPage1));
Routing.RegisterRoute("monkeyDetails/monkeygenome", typeof(TestPage2));
var shell = new TestShell(
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals2"),
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals")
);

await shell.GoToAsync("//animals/monkeys/monkeyDetails?id=123");
await shell.GoToAsync("monkeygenome");
await shell.GoToAsync("../..");
Assert.AreEqual("//animals/monkeys", shell.CurrentState.Location.ToString());
}

[Test]
public async Task GlobalRoutesRegisteredHierarchicallyWithDoubleSplash()
{
Routing.RegisterRoute("//animals/monkeys/monkeyDetails", typeof(TestPage1));
var shell = new TestShell(
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals")
);

await shell.GoToAsync("//animals/monkeys/monkeyDetails?id=123");
Assert.AreEqual("//animals/monkeys/monkeyDetails", shell.CurrentState.Location.ToString());
}


[Test]
public async Task RemovePageWithNestedRoutes()
{
Routing.RegisterRoute("monkeys/monkeyDetails", typeof(TestPage1));
Routing.RegisterRoute("monkeyDetails/monkeygenome", typeof(TestPage2));
var shell = new TestShell(
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals")
);

await shell.GoToAsync("//animals/monkeys/monkeyDetails");
await shell.GoToAsync("monkeygenome");
shell.Navigation.RemovePage(shell.Navigation.NavigationStack[1]);
await shell.Navigation.PopAsync();
}

[Test]
public async Task GlobalRoutesRegisteredHierarchicallyNavigateCorrectlyWithAdditionalItems()
{
Routing.RegisterRoute("monkeys/monkeyDetails", typeof(TestPage1));
Routing.RegisterRoute("monkeyDetails/monkeygenome", typeof(TestPage2));
var shell = new TestShell(
CreateShellItem(shellContentRoute: "cats", shellSectionRoute:"domestic", shellItemRoute: "animals")
);

shell.Items[0].Items.Add(CreateShellContent(shellContentRoute: "monkeys"));
shell.Items[0].Items.Add(CreateShellContent(shellContentRoute: "elephants"));
shell.Items[0].Items.Add(CreateShellContent(shellContentRoute: "bears"));
shell.Items[0].Items[0].Items.Add(CreateShellContent(shellContentRoute: "dogs"));
shell.Items.Add(CreateShellContent(shellContentRoute: "about"));
await shell.GoToAsync("//animals/monkeys/monkeyDetails?id=123");
await shell.GoToAsync("monkeygenome");
Assert.AreEqual("//animals/monkeys/monkeyDetails/monkeygenome", shell.CurrentState.Location.ToString());
}

[Test]
public async Task GoBackFromRouteWithMultiplePaths()
{
Routing.RegisterRoute("monkeys/monkeyDetails", typeof(TestPage1));

var shell = new TestShell(
CreateShellItem()
);

await shell.GoToAsync("monkeys/monkeyDetails");
await shell.GoToAsync("monkeys/monkeyDetails");
await shell.Navigation.PopAsync();
await shell.Navigation.PopAsync();
}


[Test]
public async Task GoBackFromRouteWithMultiplePathsHierarchical()
{
Routing.RegisterRoute("monkeys/monkeyDetails", typeof(TestPage1));
Routing.RegisterRoute("monkeyDetails/monkeygenome", typeof(TestPage2));

var shell = new TestShell(
CreateShellItem()
);

await shell.GoToAsync("monkeys/monkeyDetails");
await shell.GoToAsync("monkeygenome");
await shell.Navigation.PopAsync();
await shell.Navigation.PopAsync();
}

[Test]
public void NodeWalkingBasic()
{
var shell = new TestShell(
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals2"),
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals")
);

ShellUriHandler.NodeLocation nodeLocation = new ShellUriHandler.NodeLocation();
nodeLocation.SetNode(shell);

nodeLocation = nodeLocation.WalkToNextNode();
Assert.AreEqual(nodeLocation.Content, shell.Items[0].Items[0].Items[0]);

nodeLocation = nodeLocation.WalkToNextNode();
Assert.AreEqual(nodeLocation.Content, shell.Items[1].Items[0].Items[0]);
}


[Test]
public void NodeWalkingMultipleContent()
{
var shell = new TestShell(
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals1"),
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals2"),
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals3"),
CreateShellItem(shellContentRoute: "monkeys", shellItemRoute: "animals4")
);

var content = CreateShellContent();
shell.Items[1].Items[0].Items.Add(content);
shell.Items[2].Items[0].Items.Add(CreateShellContent());

// add a section with now content
shell.Items[0].Items.Add(new ShellSection());

ShellUriHandler.NodeLocation nodeLocation = new ShellUriHandler.NodeLocation();
nodeLocation.SetNode(content);

nodeLocation = nodeLocation.WalkToNextNode();
Assert.AreEqual(shell.Items[2].Items[0].Items[0], nodeLocation.Content);

nodeLocation = nodeLocation.WalkToNextNode();
Assert.AreEqual(shell.Items[2].Items[0].Items[1], nodeLocation.Content);

nodeLocation = nodeLocation.WalkToNextNode();
Assert.AreEqual(shell.Items[3].Items[0].Items[0], nodeLocation.Content);
}

[Test]
public async Task GlobalRegisterAbsoluteMatching()
Expand Down Expand Up @@ -356,6 +536,7 @@ public async Task RelativeNavigationToShellElementThrows()
Assert.That(async () => await shell.GoToAsync($"domestic"), Throws.Exception);
}


[Test]
public async Task RelativeNavigationWithRoute()
{
Expand Down
27 changes: 27 additions & 0 deletions Xamarin.Forms.Core/Shell/NavigationRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Diagnostics;

namespace Xamarin.Forms
{
[DebuggerDisplay("RequestDefinition = {Request}, StackRequest = {StackRequest}")]
internal class NavigationRequest
{
public enum WhatToDoWithTheStack
{
ReplaceIt,
PushToIt
}

public NavigationRequest(RequestDefinition definition, WhatToDoWithTheStack stackRequest, string query, string fragment)
{
StackRequest = stackRequest;
Query = query;
Fragment = fragment;
Request = definition;
}

public WhatToDoWithTheStack StackRequest { get; }
public string Query { get; }
public string Fragment { get; }
public RequestDefinition Request { get; }
}
}
50 changes: 50 additions & 0 deletions Xamarin.Forms.Core/Shell/RequestDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Xamarin.Forms
{
[DebuggerDisplay("Full = {FullUri}, Short = {ShortUri}")]
internal class RequestDefinition
{
public RequestDefinition(RouteRequestBuilder theWinningRoute, Shell shell)
{
Item = theWinningRoute.Item;
Section = theWinningRoute.Section ?? Item?.CurrentItem;
Content = theWinningRoute.Content ?? Section?.CurrentItem;
GlobalRoutes = theWinningRoute.GlobalRouteMatches;

List<String> builder = new List<string>();
if (Item?.Route != null)
builder.Add(Item.Route);

if (Section?.Route != null)
builder.Add(Section?.Route);

if (Content?.Route != null)
builder.Add(Content?.Route);

if (GlobalRoutes != null)
builder.AddRange(GlobalRoutes);

var uriPath = MakeUriString(builder);
var uri = ShellUriHandler.CreateUri(uriPath);
FullUri = ShellUriHandler.ConvertToStandardFormat(shell, uri);

}

string MakeUriString(List<string> segments)
{
if (segments[0].StartsWith("/", StringComparison.Ordinal) || segments[0].StartsWith("\\", StringComparison.Ordinal))
return String.Join("/", segments);

return $"//{String.Join("/", segments)}";
}

public Uri FullUri { get; }
public ShellItem Item { get; }
public ShellSection Section { get; }
public ShellContent Content { get; }
public List<string> GlobalRoutes { get; }
}
}
Loading

0 comments on commit de4d0bc

Please sign in to comment.