/* * Copyright (C) 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.errai.ui.nav.client.local; import java.util.Collection; import org.jboss.errai.common.client.util.CreationalCallback; import org.jboss.errai.enterprise.client.cdi.AbstractErraiCDITest; import org.jboss.errai.ioc.client.container.Factory; import org.jboss.errai.ioc.client.container.IOC; import org.jboss.errai.ui.nav.client.local.api.MissingPageRoleException; import org.jboss.errai.ui.nav.client.local.api.PageNotFoundException; import org.jboss.errai.ui.nav.client.local.pushstate.PushStateUtil; import org.jboss.errai.ui.nav.client.local.res.TestNavigationErrorHandler; import org.jboss.errai.ui.nav.client.local.spi.NavigationGraph; import org.jboss.errai.ui.nav.client.local.spi.PageNode; import org.jboss.errai.ui.nav.client.local.testpages.AppScopedPageWithNoLifecycleMethods; import org.jboss.errai.ui.nav.client.local.testpages.CircularRef1; import org.jboss.errai.ui.nav.client.local.testpages.CircularRef2; import org.jboss.errai.ui.nav.client.local.testpages.IsElementPageWithLeadingSlashPath; import org.jboss.errai.ui.nav.client.local.testpages.MissingPageRole; import org.jboss.errai.ui.nav.client.local.testpages.MissingUniquePageRole; import org.jboss.errai.ui.nav.client.local.testpages.NonCompositePage; import org.jboss.errai.ui.nav.client.local.testpages.PageA; import org.jboss.errai.ui.nav.client.local.testpages.PageB; import org.jboss.errai.ui.nav.client.local.testpages.PageBWithState; import org.jboss.errai.ui.nav.client.local.testpages.PageIsWidget; import org.jboss.errai.ui.nav.client.local.testpages.PageWithExtraState; import org.jboss.errai.ui.nav.client.local.testpages.PageWithLinkToIsWidget; import org.jboss.errai.ui.nav.client.local.testpages.PageWithNavigationControl; import org.jboss.errai.ui.nav.client.local.testpages.PageWithRole; import org.jboss.errai.ui.nav.client.local.testpages.PageWithTransitionToIsElement; import org.jboss.errai.ui.nav.client.local.testpages.PageWithTransitionToNonComposite; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap.Builder; import com.google.common.collect.Multimap; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.History; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.Location; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.web.bindery.event.shared.HandlerRegistration; import junit.framework.AssertionFailedError; public class NavigationTest extends AbstractErraiCDITest { private static final int TIMEOUT = 30000; private Navigation navigation; private NavigationGraph navGraph; private HandlerRegistration historyHandlerRegistration; private int historyHandlerInvocations; private HistoryTokenFactory htFactory; @Override public String getModuleName() { return "org.jboss.errai.ui.nav.NavigationTest"; } @Override protected void gwtSetUp() throws Exception { disableBus = true; super.gwtSetUp(); navigation = IOC.getBeanManager().lookupBean(Navigation.class).getInstance(); navGraph = navigation.getNavGraph(); htFactory = IOC.getBeanManager().lookupBean(HistoryTokenFactory.class).getInstance(); History.newItem("", false); } @Override protected void gwtTearDown() throws Exception { navigation.cleanUp(); super.gwtTearDown(); } public void testNavigationToNonCompositePage() throws Exception { navigation.goTo(NonCompositePage.class.getSimpleName()); assertEquals("Did not navigate to non composite page.", NonCompositePage.class, navigation.currentPage.contentType()); } public void testNavigationFromNonCompositePage() throws Exception { try { testNavigationToNonCompositePage(); } catch (AssertionError ae) { throw new AssertionError("Precondition failed: Could not navigate to non-composite page.", ae); } navigation.goTo(PageB.class, ImmutableMultimap.<String, String>of()); assertEquals("Did not navigate to PageB from composite.", PageB.class, navigation.currentPage.contentType()); } public void testMissingPage() throws Exception { try { navGraph.getPage("page that does not exist"); fail("Did not get an exception for a missing page"); } catch (PageNotFoundException ex) { assertTrue(ex.getMessage().contains("page that does not exist")); } } public void testDefaultErrorHandlerWithMissingPage() throws Exception { navigation.goTo("PageB"); // navigate to a known non-default page before // test because the expected behavior is that it // navigates back to the default page navigation.goTo("page that does not exist"); assertEquals("did not navigate to default page", PageA.class, navigation.currentPage.contentType()); } public void testErrorHandlerWithMissingPageThroughGoTo() throws Exception { TestNavigationErrorHandler newNavigationHandler = new TestNavigationErrorHandler(); navigation.setErrorHandler(newNavigationHandler); navigation.goTo("page that does not exist"); assertEquals("did not enter new handler", 1, newNavigationHandler.count); } public void testErrorHandlerWithMissingPageThroughHistoryNewItem() throws Exception { TestNavigationErrorHandler newNavigationHandler = new TestNavigationErrorHandler(); navigation.setErrorHandler(newNavigationHandler); History.newItem("page that does not exist"); assertEquals("did not enter new handler", 1, newNavigationHandler.count); } public void testMissingPageRole() throws Exception { navigation.goTo("PageB"); // navigate to a known non-default page before // test because the expected behavior is that it // navigates back to the default page navigation.goToWithRole(MissingPageRole.class); assertEquals("did not navigate to default page", PageA.class, navigation.currentPage.contentType()); } public void testMissingPageRoleWithTestErrorHandler() throws Exception { TestNavigationErrorHandler newNavigationHandler = new TestNavigationErrorHandler(); navigation.setErrorHandler(newNavigationHandler); navigation.goToWithRole(MissingPageRole.class); assertEquals("Did not go through test error handler", 1, newNavigationHandler.count); } public void testPageWithDefaultName() throws Exception { PageNode<?> pageA = navGraph.getPage("PageA"); assertNotNull(pageA); assertEquals("PageA", pageA.name()); } public void testPageWithProvidedName() throws Exception { PageNode<?> pageB = navGraph.getPage("PageB"); assertNotNull(pageB); assertEquals("PageB", pageB.name()); } public void testCircularReferences() throws Exception { PageNode<CircularRef1> cr1Node = navGraph.getPage(CircularRef1.class); // now fetch the bean instance final CircularRef1[] workaround = new CircularRef1[1]; cr1Node.produceContent(new CreationalCallback<CircularRef1>() { @Override public void callback(CircularRef1 beanInstance) { workaround[0] = beanInstance; } }); assertNotNull("CreationalCallback should have been invoked before produceContent returned!", workaround[0]); CircularRef1 cr1 = workaround[0]; TransitionTo<CircularRef2> transitionToCR2 = cr1.getLink(); Class<CircularRef2> cr2Type = transitionToCR2.toPageType(); assertEquals(CircularRef2.class, cr2Type); } public void testUrlUpdatesWithPageChange() throws Exception { navigation.goTo(PageWithExtraState.class, ImmutableMultimap.of("intThing", "42")); assertEquals("#PageWithExtraState;intThing=42", Window.Location.getHash()); } public void testUrlUpdateWithPageChangeByRole() { navigation.goToWithRole(DefaultPage.class); assertEquals("#PageA", Window.Location.getHash()); } public void testGetPageByRole() throws Exception { final Collection<PageNode<?>> pageByRole = navGraph.getPagesByRole(PageWithRole.AdminPage.class); assertNotNull(pageByRole); assertFalse(pageByRole.isEmpty()); assertTrue(pageByRole.size() == 2); for (PageNode<?> pageNode : pageByRole) { assertTrue(pageNode.name() + " is not a page annotated with the admin role", pageNode.name().matches("Page.?WithRole")); } } public void testGetMissingPageByRole() throws Exception { final Collection<PageNode<?>> pagesByRole = navGraph.getPagesByRole(MissingPageRole.class); assertNotNull(pagesByRole); assertTrue(pagesByRole.isEmpty()); } public void testGetMissingPageByUniqueRole() throws Exception { Throwable thrown = null; try { navGraph.getPageByRole(MissingUniquePageRole.class); } catch (Throwable e) { thrown = e; } assertNotNull("Expected an exception to be thrown.", thrown); assertEquals(MissingPageRoleException.class, thrown.getClass()); } public void testGetPageWithDefaultRole() { final PageNode<?> pageByRole = navGraph.getPageByRole(DefaultPage.class); assertNotNull(pageByRole); assertEquals("PageA", pageByRole.name()); } public void testIsWidgetPage() { final PageIsWidget page = IOC.getBeanManager().lookupBean(PageIsWidget.class).getInstance(); historyHandlerRegistration = History.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { historyHandlerRegistration.removeHandler(); assertEquals(PageIsWidget.class, navigation.getCurrentPage().contentType()); assertEquals(page.asWidget(), ((SimplePanel) navigation.getContentPanel().asWidget()).getWidget()); finishTest(); } }); delayTestFinish(5000); navigation.goTo(PageIsWidget.class, ImmutableMultimap.<String, String> of()); } public void testIsWidgetPageTransition() { final PageWithLinkToIsWidget page = IOC.getBeanManager().lookupBean(PageWithLinkToIsWidget.class).getInstance(); final PageIsWidget targetPage = IOC.getBeanManager().lookupBean(PageIsWidget.class).getInstance(); historyHandlerRegistration = History.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { historyHandlerRegistration.removeHandler(); assertEquals(PageIsWidget.class, navigation.getCurrentPage().contentType()); assertEquals(targetPage.asWidget(), ((SimplePanel) navigation.getContentPanel().asWidget()).getWidget()); finishTest(); } }); delayTestFinish(5000); page.getTransitionToIsWidget().go(); } public void testTransitionToNonCompositePage() throws Exception { final PageWithTransitionToNonComposite pageWithTransition = IOC.getBeanManager().lookupBean(PageWithTransitionToNonComposite.class).getInstance(); navigation.goTo(""); assertFalse("Precondition failed: Should not start test on " + NonCompositePage.class.getSimpleName(), navigation.currentPage.contentType().equals(NonCompositePage.class)); pageWithTransition.transition.go(); assertTrue("Should have navigated to " + NonCompositePage.class.getSimpleName(), navigation.currentPage.contentType().equals(NonCompositePage.class)); } public void testTransitionToIsElementPage() throws Exception { final PageWithTransitionToIsElement pageWithTransition = IOC.getBeanManager().lookupBean(PageWithTransitionToIsElement.class).getInstance(); navigation.goTo(""); assertFalse("Precondition failed: Should not start test on " + IsElementPageWithLeadingSlashPath.class.getSimpleName(), navigation.currentPage.contentType().equals(IsElementPageWithLeadingSlashPath.class)); pageWithTransition.transition.go(); assertEquals("Should have navigated to " + IsElementPageWithLeadingSlashPath.class.getSimpleName(), IsElementPageWithLeadingSlashPath.class, navigation.currentPage.contentType()); } public void testTransitionToPageWithLeadingSlash() throws Exception { final PageWithTransitionToIsElement pageWithTransition = IOC.getBeanManager().lookupBean(PageWithTransitionToIsElement.class).getInstance(); try { navigation.goTo(""); assertFalse("Precondition failed: Should not start test on " + IsElementPageWithLeadingSlashPath.class.getSimpleName(), navigation.currentPage.contentType().equals(IsElementPageWithLeadingSlashPath.class)); pageWithTransition.transition.go(); assertEquals("Should have navigated to " + IsElementPageWithLeadingSlashPath.class.getSimpleName(), IsElementPageWithLeadingSlashPath.class, navigation.currentPage.contentType()); } catch (Exception e) { throw new AssertionError("Precondition failed.", e); } assertEquals("#" + IsElementPageWithLeadingSlashPath.IS_ELEMENT_PAGE, Location.getHash()); } public void testIsWidgetAnchorTransition() { final PageWithLinkToIsWidget page = IOC.getBeanManager().lookupBean(PageWithLinkToIsWidget.class).getInstance(); final PageIsWidget targetPage = IOC.getBeanManager().lookupBean(PageIsWidget.class).getInstance(); historyHandlerRegistration = History.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { historyHandlerRegistration.removeHandler(); assertEquals(PageIsWidget.class, navigation.getCurrentPage().contentType()); assertEquals(targetPage.asWidget(), ((SimplePanel) navigation.getContentPanel().asWidget()).getWidget()); finishTest(); } }); delayTestFinish(5000); page.getLinkToIsWidget().click(); } public void testNotAttachedToRootPanel() { assertNull("Navigation Content Panel should not yet have a parent", navigation.getContentPanel().asWidget() .getParent()); } public void testAutomaticRootPanelAttachment() { runPostAttachTests(new Runnable() { @Override public void run() { assertEquals("Navigation Panel was not automatically attached", RootPanel.get(), navigation.getContentPanel() .asWidget().getParent()); } }, TIMEOUT, 500); } public void testAddNavToRootPanel() { runPostAttachTests(new Runnable() { @Override public void run() { RootPanel.get().add(navigation.getContentPanel()); assertEquals("Navigation Panel should still be attached to the RootPanel", RootPanel.get(), navigation .getContentPanel().asWidget().getParent()); } }, TIMEOUT, 500); } public void testAddNavPanelToOtherPanel() { runPostAttachTests(new Runnable() { @Override public void run() { SimplePanel newParent = new SimplePanel(); newParent.add(navigation.getContentPanel()); assertEquals("Navigation panel should be child of newParent", newParent, navigation.getContentPanel() .asWidget().getParent()); assertEquals("Navigation panel shoudl not be attached to the RootPanel", -1, RootPanel.get().getWidgetIndex(navigation.getContentPanel())); } }, TIMEOUT, 500); } public void testManuallyAttachToRootPanelBefore() { assertNull("Navigation content panel should not yet be attached", navigation.getContentPanel().asWidget() .getParent()); RootPanel.get().add(navigation.getContentPanel()); // Force navigation to run Navigation#maybeAttachContentPanel() navigation.goToWithRole(DefaultPage.class); assertEquals("RootPanel should still be parent", RootPanel.get(), navigation.getContentPanel().asWidget() .getParent()); } public void testManuallyAttachToOtherPanelBefore() { assertNull("Navigation content panel should not yet be attached", navigation.getContentPanel().asWidget() .getParent()); SimplePanel panel = new SimplePanel(); panel.add(navigation.getContentPanel()); // Force navigation to run Navigation#maybeAttachContentPanel() navigation.goToWithRole(DefaultPage.class); assertEquals(panel, navigation.getContentPanel().asWidget().getParent()); } /** * Test if a history event is fired when navigation is done. */ public void testHistoryEventFired() { final PageWithLinkToIsWidget page = IOC.getBeanManager().lookupBean(PageWithLinkToIsWidget.class).getInstance(); historyHandlerInvocations = 0; historyHandlerRegistration = History.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { historyHandlerInvocations++; // the first invocation is triggered by the init vote if (historyHandlerInvocations == 1) { page.getLinkToIsWidget().click(); } else { historyHandlerRegistration.removeHandler(); finishTest(); } } }); delayTestFinish(5000); } public void testNavigationControl() throws Exception { final PageWithNavigationControl page = Factory.maybeUnwrapProxy(IOC.getBeanManager().lookupBean(PageWithNavigationControl.class) .getInstance()); navigation.goTo(PageWithNavigationControl.class, ArrayListMultimap.<String, String> create()); page.showControl.proceed(); assertEquals(PageWithNavigationControl.class, navigation.getCurrentPage().contentType()); navigation.goTo(PageA.class, ArrayListMultimap.<String, String> create()); page.hideControl.proceed(); assertEquals(PageA.class, navigation.getCurrentPage().contentType()); } public void testNavigationControlRedirect() throws Exception { final PageWithNavigationControl page = Factory.maybeUnwrapProxy(IOC.getBeanManager().lookupBean(PageWithNavigationControl.class) .getInstance()); navigation.goTo(PageWithNavigationControl.class, ArrayListMultimap.<String, String> create()); page.showControl.redirect(PageA.class); assertEquals(PageA.class, navigation.getCurrentPage().contentType()); navigation.goTo(PageWithNavigationControl.class, ArrayListMultimap.<String, String> create()); page.showControl.proceed(); assertEquals(PageWithNavigationControl.class, navigation.getCurrentPage().contentType()); navigation.goTo(PageA.class, ArrayListMultimap.<String, String> create()); page.hideControl.redirect(PageB.class); assertEquals(PageB.class, navigation.getCurrentPage().contentType()); } /** * Give the bootstrapper time to attach the Navigation content panel to the RootPanel and then run a test. * * @param test * The test code to be executed after the content panel is attached * @param timeout * The time in milliseconds to wait for the bootstrapper before giving up * @param interval * The interval in milliseconds at which to poll the content panel while waiting for bootstrapping */ private void runPostAttachTests(final Runnable test, final int timeout, final int interval) { delayTestFinish(timeout + 2 * interval); final long startTime = System.currentTimeMillis(); new Timer() { @Override public void run() { if (System.currentTimeMillis() - startTime >= timeout) { this.cancel(); } else { try { assertEquals(RootPanel.get(), navigation.getContentPanel().asWidget().getParent()); } catch (AssertionFailedError e) { return; } test.run(); this.cancel(); finishTest(); } } }.scheduleRepeating(interval); } public void testURLWithExtraKeyValuePairs() throws Exception { String url = "/page/123/string;var3=4"; HistoryToken encodedToken = htFactory.parseURL(url); assertEquals("Unexpected state map contents: " + encodedToken.getState(), "123", encodedToken.getState() .get("var1").iterator().next()); assertEquals("Unexpected state map contents: " + encodedToken.getState(), "string", encodedToken.getState().get("var2").iterator().next()); assertEquals("Unexpected state map contents: " + encodedToken.getState(), "4", encodedToken.getState().get("var3") .iterator().next()); } public void testURLWithNonAsciiCharset() throws Exception { String url = "/page/123/параметр パラメーター 参数;var3=4"; HistoryToken encodedToken = htFactory.parseURL(url); assertEquals("Unexpected state map contents: " + encodedToken.getState(), "123", encodedToken.getState() .get("var1").iterator().next()); assertEquals("Unexpected state map contents: " + encodedToken.getState(), "параметр パラメーター 参数", encodedToken.getState().get("var2").iterator().next()); assertEquals("Unexpected state map contents: " + encodedToken.getState(), "4", encodedToken.getState().get("var3") .iterator().next()); } public void testPageStateWithNonAsciiParam() throws Exception { String pageName = "PageWithPathParameters"; Builder<String, String> builder = ImmutableMultimap.builder(); builder.put("var1", "123"); builder.put("var2", "параметр パラメーター 参数"); builder.put("var3", "4"); Multimap<String, String> pageStateMap = builder.build(); String decodedToken = URLPattern.decodeParsingCharacters(htFactory.createHistoryToken(pageName, pageStateMap).toString()); assertEquals("Incorrect HistoryToken URL generated: " + decodedToken, "/page/123/параметр パラメーター 参数;var3=4", decodedToken); } public void testPageStateWithOneExtraParam() throws Exception { String pageName = "PageWithPathParameters"; Builder<String, String> builder = ImmutableMultimap.builder(); builder.put("var1", "123"); builder.put("var2", "string"); builder.put("var3", "4"); Multimap<String, String> pageStateMap = builder.build(); String decodedToken = URLPattern.decodeParsingCharacters(htFactory.createHistoryToken(pageName, pageStateMap) .toString()); assertEquals("Incorrect HistoryToken URL generated: " + decodedToken, "/page/123/string;var3=4", decodedToken); } public void testPageStateWithMultipleExtraParams() throws Exception { String pageName = "PageWithPathParameters"; Builder<String, String> builder = ImmutableMultimap.builder(); builder.put("var1", "123"); builder.put("var2", "string"); builder.put("var3", "4"); builder.put("var4", "thing"); Multimap<String, String> pageStateMap = builder.build(); String decodedToken = URLPattern.decodeParsingCharacters(htFactory.createHistoryToken(pageName, pageStateMap) .toString()); assertEquals("Incorrect HistoryToken URL generated: " + decodedToken, "/page/123/string;var3=4&var4=thing", decodedToken); } public void testPageReloadWithChangedPageStateUsingGoTo() throws Exception { PageBWithState.hitCount = 0; Builder<String, String> oldBuilder = ImmutableMultimap.builder(); oldBuilder.put("uuid", "oldstate"); Multimap<String, String> oldState = oldBuilder.build(); navigation.goTo(PageBWithState.class, oldState); assertEquals("Did not hit @PageShowing method", 1, PageBWithState.hitCount); Builder<String, String> newBuilder = ImmutableMultimap.builder(); newBuilder.put("uuid", "newstate"); Multimap<String, String> newState = newBuilder.build(); navigation.goTo(PageBWithState.class, newState); assertEquals("Did not hit @PageShowing method", 2, PageBWithState.hitCount); } public void testPageReloadWhenPageStateChangedInURLBar() throws Exception { PageBWithState.hitCount = 0; Builder<String, String> oldBuilder = ImmutableMultimap.builder(); oldBuilder.put("uuid", "oldstate"); Multimap<String, String> state = oldBuilder.build(); navigation.goTo(PageBWithState.class, state); assertEquals("Did not hit @PageShowing method", 1, PageBWithState.hitCount); History.newItem("page_b_with_state;uuid=newstate", true); assertEquals("Did not hit @PageShowing method", 2, PageBWithState.hitCount); } public void testForNonAsciiPagePath() throws Exception { String pageName = "PageWithNonAsciiPath"; Builder<String, String> builder = ImmutableMultimap.builder(); builder.put("var1", "параметр パラメーター 参数"); builder.put("var2", "123"); Multimap<String, String> pageStateMap = builder.build(); String decodedToken = URLPattern.decodeParsingCharacters(htFactory.createHistoryToken(pageName, pageStateMap) .toString()); assertEquals("Incorrect HistoryToken URL generated: " + decodedToken, "pagename/path/some:параметр パラメーター " + "参数thing/öther123 thing", decodedToken); } public void testForEmptyContextWithoutPushState() throws Exception { runPostAttachTests(new Runnable() { @Override public void run() { assertFalse(PushStateUtil.isPushStateActivated()); assertEquals("", Navigation.getAppContext()); } }, TIMEOUT, 500); } public void testNavigationPanelInjection() { NavigationPanelTestApp app = IOC.getBeanManager().lookupBean(NavigationPanelTestApp.class).getInstance(); assertNotNull(app.getNavigationPanel()); } public void testAppScopedPageWithNoLifecycleMethodsIsLoadedOnNavigation() throws Exception { assertEquals("Precondition failed: the page should not have been loaded before navigation.", 0, AppScopedPageWithNoLifecycleMethods.postConstructCount); navigation.goTo(AppScopedPageWithNoLifecycleMethods.class.getSimpleName()); assertEquals("The page bean should have been loaded when the page was displayed", 1, AppScopedPageWithNoLifecycleMethods.postConstructCount); } }