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 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_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 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); }
/// <summary> /// Create a new instance of <see cref="SectionsNavigatorRequest"/> of type <see cref="SectionsNavigatorRequestType.OpenModal"/>. /// </summary> /// <param name="newModalStackNavigationRequest">The initial <see cref="StackNavigatorRequest"/> for the new <see cref="IModalStackNavigator"/>.</param> /// <param name="modalName">The optional modal name.</param> /// <param name="modalPriority">The optional modal priority.</param> /// <param name="transitionInfo">The optional transition info.</param> /// <param name="newModalClosingTransitionInfo">The optional transition info for the future close modal request.</param> /// <returns>The newly created request.</returns> public static SectionsNavigatorRequest GetOpenModalRequest(StackNavigatorRequest newModalStackNavigationRequest, string modalName = null, int?modalPriority = null, SectionsTransitionInfo transitionInfo = null, SectionsTransitionInfo newModalClosingTransitionInfo = null) => new SectionsNavigatorRequest( SectionsNavigatorRequestType.OpenModal, sectionName: null, modalName: modalName, modalPriority: modalPriority, newModalStackNavigationRequest: newModalStackNavigationRequest, transitionInfo: transitionInfo, newModalClosingTransitionInfo: newModalClosingTransitionInfo );
/// <summary> /// Creates a new instance of <see cref="SectionsNavigatorRequest"/>. /// </summary> /// <param name="requestType">The type of request.</param> /// <param name="sectionName">The sections name associated to this request.</param> /// <param name="modalName">The modal name associated to this request.</param> /// <param name="modalPriority">The modal priority associated to this request.</param> /// <param name="newModalStackNavigationRequest">The <see cref="StackNavigatorRequest"/> associated to this request.</param> /// <param name="transitionInfo">The transition info of this request.</param> /// <param name="newModalClosingTransitionInfo">The default transiton info of the future close modal request.</param> public SectionsNavigatorRequest(SectionsNavigatorRequestType requestType, string sectionName, string modalName, int?modalPriority, StackNavigatorRequest newModalStackNavigationRequest, SectionsTransitionInfo transitionInfo, SectionsTransitionInfo newModalClosingTransitionInfo) { RequestType = requestType; SectionName = sectionName; ModalName = modalName; ModalPriority = modalPriority; NewModalStackNavigationRequest = newModalStackNavigationRequest; TransitionInfo = transitionInfo; }
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); }
/// <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); }
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_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_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); }
/// <inheritdoc cref="StackNavigatorExtensions.ProcessRequest(IStackNavigator, CancellationToken, StackNavigatorRequest)"/> public static Task ProcessRequest(this IModalStackNavigator stackNavigator, CancellationToken ct, StackNavigatorRequest request) { return(StackNavigatorExtensions.ProcessRequest(stackNavigator, ct, request)); }
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)); }); }
/// <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()); }
/// <inheritdoc/> public Task <INavigableViewModel> Navigate(CancellationToken ct, StackNavigatorRequest request) { return(_inner.Navigate(ct, request)); }