Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide NavigationViewItem Chevron if Children is empty #2759

Conversation

MarkIvanDev
Copy link
Contributor

Attach a CollectionChanged to ItemsRepeater ItemsViewSource
whenever the MenuItems or MenuItemsSource changes.
Add check to MenuItemsSource count when checking for children.

Fixes #2743

Description

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Attach a CollectionChanged to ItemsRepeater ItemsViewSource
whenever the MenuItems or MenuItemsSource changes.
Add check to MenuItemsSource count when checking for children.

Fixes microsoft#2743
@ghost
Copy link

ghost commented Jun 26, 2020

CLA assistant check
All CLA requirements met.

@msft-github-bot msft-github-bot added the needs-triage Issue needs to be triaged by the area owners label Jun 26, 2020
@Felix-Dev
Copy link
Contributor

Felix-Dev commented Jun 26, 2020

I think we should a test here for the chevron visibility. You can find the NavigationView Interaction tests here. I'm thinking we could add two tests here - one for when NavigationView uses XAML Markup to set the child items (via NavigationView.MenuItems) and one test to handle the data binding case where the source can also be set to null additionally (NavigationView.MenuItemsSource).

You can find interaction test examples fo many of the WinUI-developed controls, for example TreeView if you need some help setting them up.

@marcelwgn
Copy link
Collaborator

marcelwgn commented Jun 26, 2020

At the end of the day you will need to walk the visual tree to get to the chevron presenter in your test. Maybe an API test would be sufficient for this? Generally API tests are faster and less likely to be unreliable. I think in this an API test would be simpler since it's quite easy to repro the issue with just code behind. If you want @MarkIvanDev I can help you get started with the test.

@Felix-Dev
Copy link
Contributor

Felix-Dev commented Jun 26, 2020

@chingucoding I suggested an interaction test because I used those for when I had to walk the visual tree and I was under the impression that using an interaction test instead of an API test seemed to be much better suited for those kind of tests. If we can, however, walk the visual tree rather easily in an API test as well, then yeah, that sounds better.

@marcelwgn
Copy link
Collaborator

The general pattern I have seen with interaction tests is the following:

  1. Tests navigates to page
  2. Tests invokes some action
  3. Tests asks for verification steps to run
  4. Test reads out result from verification through UIA

In a lot of cases this makes sense, however in this case I think we could skip this "UIA interop" by just creating a NavigationView(item) and running the visual tree walking inside the test. By that we skip this "read text X from page Y" that comes from having an interaction test.

@Felix-Dev
Copy link
Contributor

Felix-Dev commented Jun 26, 2020

Yeah, thinking about it further, not sure why I thought an interaction test here would be better suited than an API test. @MarkIvanDev you can find the API tests for the NavigationView here.

@StephenLPeters
Copy link
Contributor

In general if your test does not require interacting with the control, via touch, or mouse, or keyboard interaction, then you should attempt to test the functionality via an API test.

Copy link
Contributor

@StephenLPeters StephenLPeters left a comment

Choose a reason for hiding this comment

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

:shipit:

Copy link
Contributor

@StephenLPeters StephenLPeters left a comment

Choose a reason for hiding this comment

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

🕐

@StephenLPeters
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@StephenLPeters StephenLPeters added area-NavigationView NavView control team-Controls Issue for the Controls team and removed needs-triage Issue needs to be triaged by the area owners labels Jun 26, 2020
@MarkIvanDev
Copy link
Contributor Author

@chingucoding making an API test involves making a new TestMethod right? Can you help me set that up.

@Felix-Dev
Copy link
Contributor

Felix-Dev commented Jun 27, 2020

@MarkIvanDev In NavigationViewTests.cs you could add the following code:

[TestMethod]
public void VerifyExpandCollapseChevronVisibility()
{
    NavigationView navView = null;
    NavigationViewItem parentItem = null;
    ObservableCollection<string> children = null;

    RunOnUIThread.Execute(() =>
    {
        navView = new NavigationView();
        Content = navView;

        children = new ObservableCollection<string>();
        parentItem = new NavigationViewItem() { Content = "ParentItem", MenuItemsSource = children };

        navView.MenuItems.Add(parentItem);

        navView.Width = 1008; // forces the control into Expanded mode so that the menu renders
        Content.UpdateLayout();

        UIElement chevronUIElement = (UIElement)FindVisualChildByName(parentItem, "ExpandCollapseChevron");
        Verify.IsTrue(chevronUIElement.Visibility == Visibility.Collapsed, "chevron should have been collapsed as NavViewItem has no children");

        // Add a child to our NavigationView parentItem. This should make the chevron visible.
        children.Add("Child");
        Content.UpdateLayout();

        Verify.IsTrue(chevronUIElement.Visibility == Visibility.Visible, "chevron should have been visible as NavViewItem now has children");

        // Remove all children from our NavigationView parentItem. This should collapse the chevron
        children.Clear();
        Content.UpdateLayout();

        Verify.IsTrue(chevronUIElement.Visibility == Visibility.Collapsed, "chevron should have been collapsed as NavViewItem no longer has children");
    });
}

private static DependencyObject FindVisualChildByName(FrameworkElement parent, string name)
{
    if (parent.Name == name)
    {
        return parent;
    }

    int childrenCount = VisualTreeHelper.GetChildrenCount(parent);

    for (int i = 0; i < childrenCount; i++)
    {
        FrameworkElement childAsFE = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;

        if (childAsFE != null)
        {
            DependencyObject result = FindVisualChildByName(childAsFE, name);

            if (result != null)
            {
                return result;
            }
        }
    }

    return null;
}

VerifyExpandCollapseChevronVisibility() will be your test method to check if the visibility of the expand/collapse chevron of the NavigationViewItem is updated accordingly.

FindVisualChildByName() is just a helper method to walk the visual tree. Alternatively, you could just walk the tree step-by-step using the VisualTreeHelper.GetChild() method as seen in other NavigationView API tests, like here:

var menuItemLayoutRoot = VisualTreeHelper.GetChild(menuItem1, 0) as FrameworkElement;

This test method might not cover everything we want to check here so feel free to add more test cases or treat data bound menu items (NavigationViewItem.MenuItemsSource) different to XAML markup added items (NavigationViewItem.MenuItems). It should give you a good starting block though and a feeling how to write these API tests. It's also helpful to just check other API tests as often times, they can give you sample code how to write your API test (like walking the visual tree or updating the UI).

@marcelwgn
Copy link
Collaborator

Thanks @Felix-Dev for answering! Adding to @Felix-Dev s answer:

In some cases calling "Content.UpdateLayout()" might not have finished doing all changes while the next line of code is already being evaluated. For example, new items might not have updated correctly while the verification code is already running.

In those cases, you need to close the "RunUIThread.Execute" block and add "IdleSynchronizer.WaitForIdle();" to ensure all updates to the UI have been done.

@MarkIvanDev
Copy link
Contributor Author

@chingucoding @Felix-Dev I am having trouble restoring packages. I just performed a clean and then when I try to rebuild or even restore packages, there are packages that cannot be restored. I am dealing with this for the past few days so I can't get anything running.

@marcelwgn
Copy link
Collaborator

That's probably a stupid question on my side, but does building the solution work though? (BTW You don't need to build all projects, you only need a few of them).
What packages aren't able to restore? What is the error message from package restore?
Maybe deleting the "packages" folder and/or deleting the "BuildOutput" folder helps here, but not sure.

@StephenLPeters
Copy link
Contributor

Try deleting the BuildOutput folder in the root of the repository.

@MarkIvanDev
Copy link
Contributor Author

@chingucoding
As I understand, I only have to set the MUXControlsTestApp as the startup project and build/run that project right? I have already tried deleting the BuildOutput and packages folder to no success. Do I have to install specific Visual Studio components to be able to run this? I already have the .NET desktop development, Desktop Development with C++, Universal Windows Platform development workloads installed (as well as the all of the Windows 10 SDKS). I just asked this though I have already built the project successfully before but maybe I have some dependency missing in my environment just in case.

These are the errors when doing a package restore.

  (140):Failed to download package 'runtime.win-arm.Microsoft.NETCore.App.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm.microsoft.netcore.app/2.1.0/runtime.win-arm.microsoft.netcore.app.2.1.0.nupkg'.
  (164):Failed to download package 'runtime.win-x86.Microsoft.NETCore.App.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-x86.microsoft.netcore.app/2.1.0/runtime.win-x86.microsoft.netcore.app.2.1.0.nupkg'.
  (167):Failed to download package 'runtime.win-arm64.Microsoft.NETCore.DotNetAppHost.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm64.microsoft.netcore.dotnetapphost/2.1.0/runtime.win-arm64.microsoft.netcore.dotnetapphost.2.1.0.nupkg'.
  (169):Failed to download package 'runtime.win-arm64.Microsoft.NETCore.DotNetHostPolicy.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm64.microsoft.netcore.dotnethostpolicy/2.1.0/runtime.win-arm64.microsoft.netcore.dotnethostpolicy.2.1.0.nupkg'.
  (171):Failed to download package 'runtime.win-x86.Microsoft.NETCore.DotNetHostPolicy.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-x86.microsoft.netcore.dotnethostpolicy/2.1.0/runtime.win-x86.microsoft.netcore.dotnethostpolicy.2.1.0.nupkg'.
  (176):Failed to download package 'runtime.win-x64.Microsoft.NETCore.DotNetHostResolver.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-x64.microsoft.netcore.dotnethostresolver/2.1.0/runtime.win-x64.microsoft.netcore.dotnethostresolver.2.1.0.nupkg'.
  (192):Failed to download package 'runtime.win-arm64.Microsoft.NETCore.DotNetHostPolicy.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm64.microsoft.netcore.dotnethostpolicy/2.1.0/runtime.win-arm64.microsoft.netcore.dotnethostpolicy.2.1.0.nupkg'.
  (194):Failed to download package 'runtime.win-arm64.Microsoft.NETCore.App.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm64.microsoft.netcore.app/2.1.0/runtime.win-arm64.microsoft.netcore.app.2.1.0.nupkg'.
  (197):Failed to download package 'runtime.win-x86.Microsoft.NETCore.DotNetHostPolicy.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-x86.microsoft.netcore.dotnethostpolicy/2.1.0/runtime.win-x86.microsoft.netcore.dotnethostpolicy.2.1.0.nupkg'.
  (201):Failed to download package 'runtime.win-x64.Microsoft.NETCore.App.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-x64.microsoft.netcore.app/2.1.0/runtime.win-x64.microsoft.netcore.app.2.1.0.nupkg'.
  (204):Failed to download package 'runtime.win-x64.Microsoft.NETCore.DotNetHostResolver.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-x64.microsoft.netcore.dotnethostresolver/2.1.0/runtime.win-x64.microsoft.netcore.dotnethostresolver.2.1.0.nupkg'.
  (207):Failed to download package 'runtime.win-arm64.Microsoft.NETCore.DotNetHostResolver.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm64.microsoft.netcore.dotnethostresolver/2.1.0/runtime.win-arm64.microsoft.netcore.dotnethostresolver.2.1.0.nupkg'.
  (212):Failed to download package 'runtime.win-arm64.Microsoft.NETCore.App.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm64.microsoft.netcore.app/2.1.0/runtime.win-arm64.microsoft.netcore.app.2.1.0.nupkg'.
  (219):Failed to download package 'runtime.win-x64.Microsoft.NETCore.App.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-x64.microsoft.netcore.app/2.1.0/runtime.win-x64.microsoft.netcore.app.2.1.0.nupkg'.
  (222):Failed to download package 'runtime.win-arm64.Microsoft.NETCore.DotNetHostResolver.2.1.0' from 'https://dotnetfeed.blob.core.windows.net/dotnet-core/flatcontainer/runtime.win-arm64.microsoft.netcore.dotnethostresolver/2.1.0/runtime.win-arm64.microsoft.netcore.dotnethostresolver.2.1.0.nupkg'.

@MarkIvanDev
Copy link
Contributor Author

Finally, I was able to run the test. I tried to do everything for the second time and it ran at last.

image

What will be the next step?

@marcelwgn
Copy link
Collaborator

Those errors you got seems more like network issues as it fails to download the packages. But seems that you are able to test your changes now so, yay!

Next steps are:

  • Run CI (something someone from the team has to start)
  • review this PR (which can anybody do, and I'll try do too)
  • Address potential code review feedback

If there are no requested changes during code review, there is nothing else you need to do here.

Thank you for contributing this fix!

@@ -144,9 +144,16 @@ void NavigationViewItem::UpdateRepeaterItemsSource()
}
return MenuItems().as<winrt::IInspectable>();
}();
m_itemsSourceViewChanged.revoke();
Copy link
Collaborator

@marcelwgn marcelwgn Jun 30, 2020

Choose a reason for hiding this comment

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

#Fixed I think you need to revoke this listener in UnhookEventsAndClearFields too:

void NavigationViewItem::UnhookEventsAndClearFields()
{
UnhookInputEvents();
m_flyoutClosingRevoker.revoke();
m_splitViewIsPaneOpenChangedRevoker.revoke();
m_splitViewDisplayModeChangedRevoker.revoke();
m_splitViewCompactPaneLengthChangedRevoker.revoke();
m_repeaterElementPreparedRevoker.revoke();
m_repeaterElementClearingRevoker.revoke();
m_isEnabledChangedRevoker.revoke();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do I also need to rename the variable into m_itemsSourceViewCollectionChangedRevoker to be inline with the other revokers?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good point, yes that would be great!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just pushed a commit doing this.

navView.Width = 1008; // forces the control into Expanded mode so that the menu renders
Content.UpdateLayout();

UIElement chevronUIElement = (UIElement)FindVisualChildByName(parentItem, "ExpandCollapseChevron");
Copy link
Contributor

Choose a reason for hiding this comment

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

We can use VisualTreeUtils.FindVisualChildByName() here instead of having to provide an own implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also removed the FindVisualChildByName method.

Copy link
Collaborator

@marcelwgn marcelwgn left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@@ -759,5 +759,70 @@ public void VerifyNavigationViewItemInFooterDoesNotCrash()
Verify.IsTrue(true);
});
}

[TestMethod]
public void VerifyExpandCollapseChevronVisibility()
Copy link
Contributor

@Felix-Dev Felix-Dev Jun 30, 2020

Choose a reason for hiding this comment

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

  • For now we check if the chevron is collapsed when the bound data collection is empty. We should probably also check in this test if the chevron visibility is updated accordingly when the NavigationViewItem.MenuItemsSource is set to null. (I.e. you could add an item again to the bound collection after our final visibility check after clearing the collection, then set parentItem.MenuItemsSource = null; and verify again if the chevron is collapsed.)

  • Right now we only use the NavigationViewItem.MenuItemsSource API to verify the correctness of the chevron visibility. For completeness, we could also add testing if the chevron visibility is updated correctly when using the NavigationViewItem.MenuItems API instead. Testing with this API would simply add/remove items to/from MenuItems (as MenuItems cannot be set to null) and check the chevron visibility accordingly. These checks could be added inside this method below our tests for the MenuItemsSource API (for example after clearing the bound collection or setting MenuItemsSource to null) so that we start testing using the MenuItems API with the chevron in collapsed state.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Are there no other tests that would verify the other branches of the HasChildren function? I thought that some other tests verified this implicitly.

Copy link
Contributor

@Felix-Dev Felix-Dev Jun 30, 2020

Choose a reason for hiding this comment

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

@chingucoding I haven't immediately found any other test (API/Interaction test) which we could fall back to here.

Edit: Just noticed there is no public NavigationViewItem.HasChildren API so ignore what I previously wrote here about having to create a test for it too 😅

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems that are no tests that test collection changes in the MenuItems API. Would be best if we add those here or if we create a tracking issue to do that later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am adding the tests for the MenuItems API now. Will push it once it passes the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All tests have passed even the tests for the MenuItems API.

image


// Add a child to parentItem and set the MenuItemsSource as null. This should collapse the chevron
children.Add("Child 2");
parentItem.MenuItemsSource = null;
Copy link
Contributor

@Felix-Dev Felix-Dev Jun 30, 2020

Choose a reason for hiding this comment

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

Do we need a Content.UpdateLayout() call here to make sure the chevron is visible again before setting the MenuItemsSource to null and verifying that it actually collapses the chevron? (Before adding "Child 2" item, the chevron is collapsed.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But we have already checked earlier at Line 787 that adding an item to children does make the chevron visible right? I think that it would be redundant to make that check again here?

Copy link
Contributor

@Felix-Dev Felix-Dev Jun 30, 2020

Choose a reason for hiding this comment

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

My thinking here is not to verify that adding an item actually makes the chevron visible (by adding an additional Verify.IsTrue(chevronUIElement.Visibility == Visibility.Visible)) but making sure that by the time we set parentItem.MenuItemsSource = null; the chevron is actually visible. That way, we can make sure that setting MenuItemsSource to null will indeed collapse the chevron.

In other words, my concern here is the following: Before we add our child item (Child 2) the chevron is collapsed. We then add this child and immediately after that set MenuItemsSource to null. I'm concerned that without the Content.UpdateLayout() call after adding the child, the chevron might not actually be made visible again, thus we are not actually verifying whether setting MenuItemsSource to null collapses the chevron. (Since the chevron might still be collapsed when we set the source to null and thus the Verify.IsTrue() call below succeeds, even though MenuItemsSource = null might not actually change the chevron visibility state at all.)

@StephenLPeters @chingucoding Your thoughts here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand now. Thanks. Will add those now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Valid concern, and another Verify would guarantee that was the case. Without it it could be the case that a bug causes the chevron to never show which wouldn't be caught without the "redundant" verify.

Copy link
Contributor

@StephenLPeters StephenLPeters left a comment

Choose a reason for hiding this comment

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

:shipit:

@StephenLPeters
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@Felix-Dev
Copy link
Contributor

Looks good to me 🙂

@MarkIvanDev
Copy link
Contributor Author

Thanks @Felix-Dev and @chingucoding for helping me through this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-NavigationView NavView control team-Controls Issue for the Controls team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Binding an Empty Collection to MenuItemSource of NavigationViewItem still shows a Chevron
5 participants