public async Task It_produces_failed_notifications_for_RemoveEntry()
        {
            var sut = new BlindStackNavigator();

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(throwOnDispose: true)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM()));

            var events = new List <StackNavigatorEventArgs>();

            sut.StateChanged += Sut_StateChanged;

            void Sut_StateChanged(object sender, StackNavigatorEventArgs args)
            {
                events.Add(args);
            }

            await Assert.ThrowsAsync <InvalidOperationException>(async() => await sut.RemoveEntries(CancellationToken.None, new int[] { 0 }));

            sut.StateChanged -= Sut_StateChanged;

            // There must be 2 notifications: 1 Processing when the remove starts and 1 FailedToProcess when it ends.
            Assert.Equal(2, events.Count);
            Assert.Equal(NavigatorRequestState.Processing, events[0].CurrentState.LastRequestState);
            Assert.Equal(NavigatorRequestState.FailedToProcess, events[1].CurrentState.LastRequestState);
        }
        public async Task It_produces_2_notifications_for_Back()
        {
            var sut = new BlindStackNavigator();

            // Add 2 items to be able to perform a back operation.
            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM()));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM()));

            var events = new List <StackNavigatorEventArgs>();

            sut.StateChanged += Sut_StateChanged;

            void Sut_StateChanged(object sender, StackNavigatorEventArgs args)
            {
                events.Add(args);
            }

            await sut.NavigateBack(CancellationToken.None);

            sut.StateChanged -= Sut_StateChanged;

            // There must be 2 notifications: 1 Processing when the back starts and 1 Processed when it ends.
            Assert.Equal(2, events.Count);
            Assert.Equal(NavigatorRequestState.Processing, events[0].CurrentState.LastRequestState);
            Assert.Equal(NavigatorRequestState.Processed, events[1].CurrentState.LastRequestState);
        }
        public async Task It_removes_multiple_entries_and_navigate_back_correctly()
        {
            var sut = new BlindStackNavigator();

            // Add 6 items to the stack.
            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(0)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(2)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(3)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(4)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(5)));

            var indexes = Enumerable.Range(2, 3);

            // Remove the third, fourth and fifth items
            await sut.RemoveEntries(CancellationToken.None, indexes);

            // There must be 3 items left
            Assert.Equal(3, sut.State.Stack.Count);

            // Navigates back
            await sut.NavigateBack(CancellationToken.None);

            // The viewmodel count should be 2 and the current viewmodel id should be 1
            Assert.Equal(2, sut.State.Stack.Count);
            Assert.Equal(1, ((TestVM)sut.State.Stack.Last().ViewModel).Id);
        }
        public async Task Extended_interface_contract_changes_can_be_detected()
        {
            var             ct        = CancellationToken.None;
            IStackNavigator navigator = new BlindStackNavigator();

            // If the extension methods available in the abstraction package change their signatures, we get compilation errors here.

            TestVM vmAfterNavigate = await navigator.Navigate(ct, () => new TestVM(), suppressTransition : false);

            TestVM vmAfterNavigateAndClear = await navigator.NavigateAndClear(ct, () => new TestVM(), suppressTransition : false);

            INavigableViewModel vm = navigator.GetActiveViewModel();
            bool canGoBack         = navigator.CanNavigateBack();

            // Navigate twice so that RemovePrevious works.
            await navigator.Navigate(ct, () => new TestVM(), suppressTransition : false);

            await navigator.Navigate(ct, () => new TestVM(), suppressTransition : false);

            await navigator.RemovePrevious(ct);

            bool didNavigateBack = await navigator.TryNavigateBackTo <TestVM>(ct);

            await navigator.ProcessRequest(ct, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(), false, false));

            IObservable <EventPattern <StackNavigatorEventArgs> > ob1 = navigator.ObserveStateChanged();
            IObservable <StackNavigatorState> ob2 = navigator.ObserveCurrentState();
        }
        public async Task It_adds_entries_to_stack_when_it_navigates_forward()
        {
            var sut = new BlindStackNavigator();

            var vm = await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM()));

            Assert.NotNull(vm);
            Assert.Single(sut.State.Stack);
        }
        public async Task It_removes_all_stack_entries_when_it_clears()
        {
            var sut = new BlindStackNavigator();

            // Add an item to the stack.
            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM()));

            // Clear the stack.
            await sut.Clear(CancellationToken.None);

            // Stack must be empty.
            Assert.Empty(sut.State.Stack);
        }
        public async Task It_disposes_VM_when_navigating_back()
        {
            var sut = new BlindStackNavigator();

            // Add 2 items to the stack.
            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(0)));

            var vm = (TestVM)await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1)));

            // Navigate back to remove the last item.
            await sut.NavigateBack(CancellationToken.None);

            // The removed entry must be disposed.
            Assert.True(vm.IsDisposed);
        }
        public async Task It_disposes_VM_when_removing_entries()
        {
            var sut = new BlindStackNavigator();

            // Add 2 items to the stack.
            var vm = (TestVM)await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(0)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1)));

            // Remove the first item.
            await sut.RemoveEntries(CancellationToken.None, new int[] { 0 });

            // The removed entry must be disposed.
            Assert.True(vm.IsDisposed);
        }
Exemple #9
0
        /// <summary>
        /// Sets the active section using the provided section name and navigates.
        /// </summary>
        /// <typeparam name="TViewModel">The type of the view model.</typeparam>
        /// <param name="sectionsNavigator">The sections navigator.</param>
        /// <param name="ct">The cancellation token.</param>
        /// <param name="sectionName">The name of the section to set as active.</param>
        /// <param name="viewModelProvider">The method to make the view model instance. It will be invoked only if necessary.</param>
        /// <param name="returnToRoot">When this is true, the navigator will navigate back to the view model matching the type <typeparamref name="TViewModel"/>.</param>
        /// <returns>The stack navigator of the active section.</returns>
        public static async Task <ISectionStackNavigator> SetActiveSection <TViewModel>(this ISectionsNavigator sectionsNavigator,
                                                                                        CancellationToken ct,
                                                                                        string sectionName,
                                                                                        Func <TViewModel> viewModelProvider,
                                                                                        bool returnToRoot = false)
            where TViewModel : INavigableViewModel
        {
            if (ct.IsCancellationRequested)
            {
                typeof(SectionsNavigatorExtensions).Log().LogWarning($"Canceled 'SetActiveSection' operation to '{typeof(TViewModel).Name}' because of cancellation token.");

                return(null);
            }

            // No cancellation beyond this point.
            ct = CancellationToken.None;

            var sectionNavigator = sectionsNavigator.State.Sections[sectionName];

            if (sectionNavigator.State.Stack.LastOrDefault() == null)
            {
                // Create the default page if there's nothing in the section.
                await sectionNavigator.Navigate(ct, StackNavigatorRequest.GetNavigateRequest(viewModelProvider, suppressTransition: true));
            }
            else if (returnToRoot && sectionNavigator.State.Stack.Last().ViewModel.GetType() != typeof(TViewModel))
            {
                if (sectionNavigator.State.Stack.Any(e => e.ViewModel.GetType() == typeof(TViewModel)))
                {
                    // If the stack contains the root page of the section, remove all other entries and navigate back to it.
                    var indexesToRemove = sectionNavigator.State.Stack
                                          .Select((entry, index) => (entry, index))
                                          .Where(t => t.entry.ViewModel.GetType() != typeof(TViewModel) && t.index < sectionNavigator.State.Stack.Count - 1)
                                          .Select(t => t.index)
                                          .ToList();

                    await sectionNavigator.RemoveEntries(ct, indexesToRemove);

                    await sectionNavigator.NavigateBack(ct);
                }
                else
                {
                    // If the section root page isn't in the stack, clear everything and navigate to it.
                    await sectionNavigator.Navigate(ct, StackNavigatorRequest.GetNavigateRequest(viewModelProvider, suppressTransition: true, clearBackStack: true));
                }
            }

            return(await sectionsNavigator.SetActiveSection(ct, SectionsNavigatorRequest.GetSetActiveSectionRequest(sectionName)));
        }
        public async Task It_disposes_VMs_when_clearing()
        {
            var sut = new BlindStackNavigator();

            // Add 2 items to the stack.
            var vm0 = (TestVM)await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(0)));

            var vm1 = (TestVM)await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1)));

            // Clear to remove all items.
            await sut.Clear(CancellationToken.None);

            // All entries must be disposed.
            Assert.True(vm0.IsDisposed);
            Assert.True(vm1.IsDisposed);
        }
        public async Task It_removes_entries_correctly()
        {
            var sut = new BlindStackNavigator();

            // Add 2 items to the stack.
            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(0)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1)));

            // Remove the first item
            await sut.RemoveEntries(CancellationToken.None, new int[] { 0 });

            // There must be only 1 item left in the stack because we just removed one.
            Assert.Single(sut.State.Stack);

            // The remaining item must be the second navigation.
            Assert.Equal(1, ((TestVM)sut.State.Stack.Last().ViewModel).Id);
        }
        public async Task Interface_contract_changes_can_be_detected()
        {
            var             ct        = CancellationToken.None;
            IStackNavigator navigator = new BlindStackNavigator();

            // If the core contract changes, we get compilation errors here.
            INavigableViewModel vmAfterNavigate = await navigator.Navigate(ct, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(), false, false));

            INavigableViewModel vmAfterBack = await navigator.NavigateBack(ct);

            await navigator.Clear(ct);

            // Navigate twice so that RemoveEntries works.
            await navigator.Navigate(ct, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(), false, false));

            await navigator.Navigate(ct, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(), false, false));

            await navigator.RemoveEntries(ct, new int[] { 0 }.AsEnumerable());
        }
        public async Task It_fails_to_navigate_back_when_there_arent_enough_entries()
        {
            var sut = new BlindStackNavigator();

            // Add 1 item to the stack.
            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM()));

            var stackBeforeNavigateBack = sut.State.Stack;

            // Navigating back must 'fail' if there is only 1 entry.
            // There is no exception, but the result vm must be null.
            var vm = await sut.NavigateBack(CancellationToken.None);

            Assert.Null(vm);

            var stackAfterNavigateBack = sut.State.Stack;

            // Because the back didn't do anything, the stack must not change.
            Assert.Equal(stackAfterNavigateBack, stackBeforeNavigateBack);
        }
        public async Task It_produces_2_notifications_when_a_section_navigates()
        {
            var sut       = new BlindSectionsNavigator("defaultSection1", "defaultSection2");
            var eventList = new List <(object sender, SectionsNavigatorEventArgs args)>();

            var section = await sut.SetActiveSection(CancellationToken.None, SectionsNavigatorRequest.GetSetActiveSectionRequest("defaultSection1"));

            sut.StateChanged += OnStateChanged;
            void OnStateChanged(object sender, SectionsNavigatorEventArgs args)
            {
                eventList.Add((sender, args));
            }

            await section.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1), suppressTransition: true));

            Assert.Equal(2, eventList.Count);
            Assert.Equal(NavigatorRequestState.Processing, eventList[0].args.CurrentState.LastRequestState);
            Assert.Equal(SectionsNavigatorRequestType.ReportSectionStateChanged, eventList[0].args.CurrentState.LastRequest.RequestType);
            Assert.Equal(NavigatorRequestState.Processed, eventList[1].args.CurrentState.LastRequestState);
            Assert.Equal(SectionsNavigatorRequestType.ReportSectionStateChanged, eventList[1].args.CurrentState.LastRequest.RequestType);
        }
        public async Task It_navigates_back()
        {
            var sut = new BlindStackNavigator();

            // Add 2 items to the stack.
            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(0)));

            await sut.Navigate(CancellationToken.None, StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1)));

            // The last entry must match the last navigation.
            Assert.Equal(1, ((TestVM)sut.State.Stack.Last().ViewModel).Id);

            var vmFromNavigation = await sut.NavigateBack(CancellationToken.None);

            var vmFromStack = (TestVM)sut.State.Stack.Last().ViewModel;

            // The ViewModels from the navigation and stack must be the same.
            Assert.Equal(vmFromStack, vmFromNavigation);

            // The final ViewModel must be the first one we navigated to.
            Assert.Equal(0, vmFromStack.Id);
        }
Exemple #16
0
        /// <summary>
        /// Opens a new modal.
        /// </summary>
        /// <typeparam name="TViewModel">The type of the view model.</typeparam>
        /// <param name="sectionsNavigator">The sections navigator.</param>
        /// <param name="ct"></param>
        /// <param name="viewModelProvider">The method invoked to instanciate the new ViewModel.</param>
        /// <param name="priority">The modal's priority.</param>
        /// <param name="name">The modal's name.</param>
        /// <returns>The newly created ViewModel instance.</returns>
        public static async Task <TViewModel> OpenModal <TViewModel>(this ISectionsNavigator sectionsNavigator, CancellationToken ct, Func <TViewModel> viewModelProvider, int?priority = null, string name = null)
            where TViewModel : INavigableViewModel
        {
            var modalNavigator = await sectionsNavigator.OpenModal(ct, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(viewModelProvider, suppressTransition: true), name, priority));

            // Note that modalNavigator can be null if the OpenModal gets cancelled.
            return((TViewModel)modalNavigator?.GetActiveViewModel());
        }
        public async Task It_orders_modals()
        {
            var sut = new BlindSectionsNavigator();

            var modalNavigator3 = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(3), suppressTransition: true), modalPriority: 3));

            var modalNavigator1 = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1), suppressTransition: true), modalPriority: 1));

            Assert.Equal(sut.State.Modals[0], modalNavigator1);
            Assert.Equal(sut.State.Modals[1], modalNavigator3);

            var modalNavigator2 = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(2), suppressTransition: true), modalPriority: 2));

            Assert.Equal(sut.State.Modals[0], modalNavigator1);
            Assert.Equal(sut.State.Modals[1], modalNavigator2);
            Assert.Equal(sut.State.Modals[2], modalNavigator3);
        }
        public async Task It_disposes_modal_VM_when_closing_modals()
        {
            var sut = new BlindSectionsNavigator();

            var modalNavigator = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1), suppressTransition: true)));

            var vm = (TestVM)modalNavigator.State.Stack.Last().ViewModel;

            await sut.CloseModal(CancellationToken.None, SectionsNavigatorRequest.GetCloseModalRequest(null));

            Assert.True(vm.IsDisposed);
        }
        public async Task It_stacks_modals_navigators()
        {
            var sut = new BlindSectionsNavigator();

            var modalNavigator = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1), suppressTransition: true)));

            Assert.NotNull(modalNavigator);
            Assert.Equal(1, modalNavigator.Priority);
            Assert.Single(modalNavigator.State.Stack);

            Assert.Single(sut.State.Modals);
            Assert.NotNull(sut.State.ActiveModal);
            Assert.Equal(modalNavigator, sut.State.ActiveModal);
            Assert.Equal(modalNavigator, sut.State.Modals[0]);

            var modalNavigator2 = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(2), suppressTransition: true)));

            Assert.NotNull(modalNavigator2);
            Assert.Equal(2, modalNavigator2.Priority);
            Assert.Single(modalNavigator2.State.Stack);

            Assert.Equal(2, sut.State.Modals.Count);
            Assert.NotNull(sut.State.ActiveModal);
            Assert.Equal(modalNavigator2, sut.State.ActiveModal);
            Assert.Equal(modalNavigator, sut.State.Modals[0]);
            Assert.Equal(modalNavigator2, sut.State.Modals[1]);
        }
        public async Task It_produces_failed_notifications_for_CloseModal()
        {
            var sut = new BlindSectionsNavigator();

            var eventList = new List <(object sender, SectionsNavigatorEventArgs args)>();

            var modalNavigator = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1, throwOnDispose: true), suppressTransition: true)));

            sut.StateChanged += OnStateChanged;
            void OnStateChanged(object sender, SectionsNavigatorEventArgs args)
            {
                eventList.Add((sender, args));
            }

            await Assert.ThrowsAsync <InvalidOperationException>(async() => await sut.CloseModal(CancellationToken.None, SectionsNavigatorRequest.GetCloseModalRequest(null)));

            sut.StateChanged -= OnStateChanged;

            Assert.Null(sut.State.ActiveSection);

            Assert.Equal(2, eventList.Count);
            Assert.Equal(NavigatorRequestState.Processing, eventList[0].args.CurrentState.LastRequestState);
            Assert.Equal(NavigatorRequestState.FailedToProcess, eventList[1].args.CurrentState.LastRequestState);
        }
        public async Task It_produces_2_notifications_for_OpenModal()
        {
            var sut       = new BlindSectionsNavigator();
            var eventList = new List <(object sender, SectionsNavigatorEventArgs args)>();

            sut.StateChanged += OnStateChanged;
            void OnStateChanged(object sender, SectionsNavigatorEventArgs args)
            {
                eventList.Add((sender, args));
            }

            var modalNavigator = await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(1), suppressTransition: true)));

            sut.StateChanged -= OnStateChanged;

            Assert.Equal(2, eventList.Count);
            Assert.Equal(NavigatorRequestState.Processing, eventList[0].args.CurrentState.LastRequestState);
            Assert.Equal(SectionsNavigatorRequestType.OpenModal, eventList[0].args.CurrentState.LastRequest.RequestType);

            Assert.Equal(NavigatorRequestState.Processed, eventList[1].args.CurrentState.LastRequestState);
            Assert.Equal(SectionsNavigatorRequestType.OpenModal, eventList[1].args.CurrentState.LastRequest.RequestType);
        }
        public async Task It_fails_to_open_2_modals_with_same_priority()
        {
            var sut = new BlindSectionsNavigator();

            await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(), suppressTransition: true), modalPriority: 1));

            await Assert.ThrowsAsync <ArgumentException>(async() =>
            {
                await sut.OpenModal(CancellationToken.None, SectionsNavigatorRequest.GetOpenModalRequest(StackNavigatorRequest.GetNavigateRequest(() => new TestVM(), suppressTransition: true), modalPriority: 1));
            });
        }