From: Henri Sara Date: Thu, 24 May 2012 10:07:19 +0000 (+0300) Subject: Implement first version of view and navigation APIs (#8859). X-Git-Tag: 7.0.0.alpha3~232 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=338563e4d772c395da863d8764bca4a4b759cbe3;p=vaadin-framework.git Implement first version of view and navigation APIs (#8859). --- diff --git a/src/com/vaadin/navigator/FragmentManager.java b/src/com/vaadin/navigator/FragmentManager.java new file mode 100644 index 0000000000..f1fd90e569 --- /dev/null +++ b/src/com/vaadin/navigator/FragmentManager.java @@ -0,0 +1,38 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +/** + * Fragment manager that handles interaction between Navigator and URI fragments + * or other similar view identification and bookmarking system. + * + * Alternative implementations can be created for HTML5 pushState, for portlet + * URL navigation and other similar systems. + * + * This interface is mostly for internal use by {@link Navigator}. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface FragmentManager extends Serializable { + /** + * Return the current fragment (location string) including view name and any + * optional parameters. + * + * @return current view and parameter string, not null + */ + public String getFragment(); + + /** + * Set the current fragment (location string) in the application URL or + * similar location, including view name and any optional parameters. + * + * @param fragment + * new view and parameter string, not null + */ + public void setFragment(String fragment); +} \ No newline at end of file diff --git a/src/com/vaadin/navigator/Navigator.java b/src/com/vaadin/navigator/Navigator.java new file mode 100644 index 0000000000..d0bba584f1 --- /dev/null +++ b/src/com/vaadin/navigator/Navigator.java @@ -0,0 +1,582 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import com.vaadin.ui.Component; +import com.vaadin.ui.CssLayout; +import com.vaadin.ui.CustomComponent; +import com.vaadin.ui.Root; +import com.vaadin.ui.Root.FragmentChangedEvent; +import com.vaadin.ui.Root.FragmentChangedListener; + +/** + * Navigator utility that allows switching of views in a part of an application. + * + * The view switching can be based e.g. on URI fragments containing the view + * name and parameters to the view. There are two types of parameters for views: + * an optional parameter string that is included in the fragment (may be + * bookmarkable) and optional internal parameters that are not. + * + * Views can be explicitly registered or dynamically generated and listening to + * view changes is possible. + * + * Note that {@link Navigator} is not a component itself but comes with + * {@link SimpleViewDisplay} which is a component that displays the selected + * view as its contents. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public class Navigator implements Serializable { + + // TODO divert navigation e.g. if no permissions? Or just show another view + // but keep URL? how best to intercept + // TODO investigate relationship to TouchKit navigation support + + /** + * Empty view component. + */ + public static class EmptyView extends CssLayout implements View { + public void init() { + setWidth("0px"); + setHeight("0px"); + } + + public void navigateTo(String fragmentParameters, + Object... internalParameters) { + // nothing to do + } + } + + /** + * Fragment manager using URI fragments of a Root to track views and enable + * listening to view changes. + * + * This class is mostly for internal use by Navigator, and is only public + * and static to enable testing. + */ + public static class UriFragmentManager implements FragmentManager, + FragmentChangedListener { + private final Root root; + private final Navigator navigator; + + /** + * Create a new URIFragmentManager and attach it to listen to URI + * fragment changes of a {@link Root}. + * + * @param root + * root whose URI fragment to get and modify + * @param navigator + * {@link Navigator} to notify of fragment changes (using + * {@link Navigator#navigateTo(String, Object...)} + */ + public UriFragmentManager(Root root, Navigator navigator) { + this.root = root; + this.navigator = navigator; + + root.addListener(this); + } + + public String getFragment() { + return root.getFragment(); + } + + public void setFragment(String fragment) { + // TODO ", false" ??? + root.setFragment(fragment); + } + + public void fragmentChanged(FragmentChangedEvent event) { + UriFragmentManager.this.navigator.navigateTo(getFragment()); + } + } + + /** + * View display that is a component itself and replaces its contents with + * the view. + * + * This display only supports views that are {@link Component}s themselves. + * Attempting to display a view that is not a component causes an exception + * to be thrown. + * + * By default, the view display has full size. + */ + public static class SimpleViewDisplay extends CustomComponent implements + ViewDisplay { + + /** + * Create new {@link ViewDisplay} that is itself a component displaying + * the view. + */ + public SimpleViewDisplay() { + setSizeFull(); + } + + public void showView(View view) { + if (view instanceof Component) { + setCompositionRoot((Component) view); + } else { + throw new IllegalArgumentException("View is not a component: " + + view); + } + } + } + + /** + * View provider which uses a map from view name to pre-created and + * registered view instances. + */ + public static class RegisteredViewProvider implements ViewProvider { + + private HashMap viewNameToView = new HashMap(); + + public String getViewName(String viewAndParameters) { + if (null == viewAndParameters) { + return null; + } + for (String viewName : viewNameToView.keySet()) { + if (viewAndParameters.equals(viewName) + || viewAndParameters.startsWith(viewName + "/")) { + return viewName; + } + } + return null; + } + + public View getView(String viewName) { + return viewNameToView.get(viewName); + } + + /** + * Register a view for a view name. + * + * Registering another view with a name that is already registered + * overwrites the old registration. + * + * @param viewName + * String that identifies a view (not null nor empty string) + * @param view + * {@link View} instance (not null) + */ + public void addView(String viewName, View view) { + + // Check parameters + if (viewName == null || view == null || viewName.length() == 0) { + throw new IllegalArgumentException( + "view and viewName must be non-null and not empty"); + } + + viewNameToView.put(viewName, view); + } + + /** + * Remove view from navigator. + * + * @param viewName + * name of the view to remove + */ + public void removeView(String viewName) { + viewNameToView.remove(viewName); + } + } + + /** + * View provider which uses a map from view name to the class to instantiate + * for the view. + * + * Views that have been created are cached and reused when the same view + * name is requested again. + * + * Note that the view class must be accessible by the class loader used by + * the provider. This may require its visibility to be public. + */ + public static class ClassBasedViewProvider implements ViewProvider { + + private HashMap> viewNameToClass = new HashMap>(); + private HashMap, String> classToViewName = new HashMap, String>(); + /** + * Already opened (cached) views that can be reopened or reused with new + * parameters. + */ + private HashMap, View> classToView = new HashMap, View>(); + + public String getViewName(String viewAndParameters) { + if (null == viewAndParameters) { + return null; + } + for (String viewName : viewNameToClass.keySet()) { + if (viewAndParameters.equals(viewName) + || viewAndParameters.startsWith(viewName + "/")) { + return viewName; + } + } + return null; + } + + public View getView(String viewName) { + Class newViewClass = viewNameToClass.get(viewName); + if (null == newViewClass) { + return null; + } + if (!classToView.containsKey(newViewClass)) { + try { + View view = newViewClass.newInstance(); + view.init(); + classToView.put(newViewClass, view); + } catch (InstantiationException e) { + // TODO error handling + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + // TODO error handling + throw new RuntimeException(e); + } + } + // return already cached view + final View v = classToView.get(newViewClass); + return v; + } + + /** + * Register a view class for a view name. + * + * @param viewName + * String that identifies a view (not null nor empty string) + * @param viewClass + * Component class that implements Navigator.View interface + * (not null) + */ + public void addView(String viewName, Class viewClass) { + + // Check parameters + if (viewName == null || viewClass == null || viewName.length() == 0) { + throw new IllegalArgumentException( + "viewClass and viewName must be non-null and not empty"); + } + + if (!View.class.isAssignableFrom(viewClass)) { + throw new IllegalArgumentException( + "viewClass must implement Navigator.View"); + } + + if (viewNameToClass.containsKey(viewName)) { + if (viewNameToClass.get(viewName) == viewClass) { + return; + } + + throw new IllegalArgumentException(viewNameToClass + .get(viewName).getName() + + " is already mapped to '" + + viewName + "'"); + } + + if (classToViewName.containsKey(viewClass)) { + throw new IllegalArgumentException( + "Each view class can only be added to Navigator with one view name"); + } + + viewNameToClass.put(viewName, viewClass); + classToViewName.put(viewClass, viewName); + } + + /** + * Remove view from navigator. + * + * @param viewName + * name of the view to remove + */ + public void removeView(String viewName) { + Class c = viewNameToClass.get(viewName); + if (c != null) { + viewNameToClass.remove(viewName); + classToViewName.remove(c); + classToView.remove(c); + } + } + + /** + * Get the view name for given view implementation class. + * + * @param viewClass + * Class that implements the view. + * @return view name for which the view class is registered, null if + * none + */ + public String getViewName(Class viewClass) { + return classToViewName.get(viewClass); + } + + /** + * Get the view class for given view name. + * + * @param viewName + * view name to get view for + * @return View that corresponds to the name + */ + public Class getViewClass(String viewName) { + return viewNameToClass.get(viewName); + } + } + + private final FragmentManager fragmentManager; + private final ViewDisplay display; + private String mainViewName = null; + private View currentView = null; + private List listeners = new LinkedList(); + private List providers = new LinkedList(); + + /** + * Create a navigator that is tracking the active view using URI fragments. + * + * @param root + * whose URI fragments are used + * @param display + * where to display the views + */ + public Navigator(Root root, ViewDisplay display) { + this.display = display; + fragmentManager = new UriFragmentManager(root, this); + } + + /** + * Create a navigator. + * + * When a custom fragment manager is not needed, use the constructor + * {@link #Navigator(Root, ViewDisplay)} which uses a URI fragment based + * fragment manager. + * + * @param fragmentManager + * fragment manager keeping track of the active view and enabling + * bookmarking and direct navigation + * @param display + * where to display the views + */ + public Navigator(FragmentManager fragmentManager, ViewDisplay display) { + this.display = display; + this.fragmentManager = fragmentManager; + } + + /** + * Navigate to a view and initialize the view with given parameters. + * + * The view string consists of a view name optionally followed by a slash + * and (fragment) parameters. ViewProviders are used to find and create the + * correct type of view. + * + * If the view being left indicates it wants a confirmation for the + * navigation operation, the user is asked for the confirmation. + * + * @param viewAndParameters + * view name and parameters + * @param internalParameters + * parameters that are only passed when switching views + * explicitly on the server side, not bookmarkable + */ + public void navigateTo(String viewAndParameters, + Object... internalParameters) { + if ("".equals(viewAndParameters)) { + viewAndParameters = mainViewName; + } + for (ViewProvider provider : providers) { + String viewName = provider.getViewName(viewAndParameters); + if (null != viewName) { + String parameters = null; + if (viewAndParameters.length() > viewName.length() + 1) { + parameters = viewAndParameters + .substring(viewName.length() + 1); + } + View view = provider.getView(viewName); + if (null != view) { + navigateTo(view, viewName, parameters, internalParameters); + // stop after a view is found + return; + } + } + } + // TODO if no view is found, use main view or do nothing? + } + + /** + * Internal method activating a view, setting its parameters and calling + * listeners. + * + * This method also verifies that the user is allowed to perform the + * navigation operation. + * + * @param view + * view to activate + * @param viewName + * (optional) name of the view or null not to set the fragment + * @param fragmentParameters + * parameters passed in the fragment for the view + * @param internalParameters + * parameters that are only passed when switching views + * explicitly on the server side, not bookmarkable + */ + protected void navigateTo(View view, String viewName, + String fragmentParameters, Object... internalParameters) { + if (!isViewChangeAllowed(currentView, view, viewName, + fragmentParameters, internalParameters)) { + return; + } + + if (null != viewName && getFragmentManager() != null) { + String currentFragment = viewName; + if (fragmentParameters != null) { + currentFragment += "/" + fragmentParameters; + } + if (!currentFragment.equals(getFragmentManager().getFragment())) { + getFragmentManager().setFragment(currentFragment); + } + } + + view.navigateTo(fragmentParameters, internalParameters); + View previousView = currentView; + currentView = view; + + if (display != null) { + display.showView(view); + } + + fireViewChange(previousView); + } + + /** + * Check whether view change is allowed. + * + * All related listeners are called. The view change is blocked if any of + * them wants to block the navigation operation. + * + * The view change listeners may also e.g. open a warning or question dialog + * and save the parameters to re-initiate the navigation operation upon user + * action. + * + * @param previous + * the view being deactivated + * @param next + * the view being activated + * @param viewName + * name of the view being activated + * @param fragmentParameters + * parameters passed in the fragment for the view + * @param internalParameters + * parameters that are only passed on the server side, not + * bookmarkable + * @return true if the view change should be allowed, false to silently + * block the navigation operation + */ + protected boolean isViewChangeAllowed(View previous, View next, + String viewName, String fragmentParameters, + Object[] internalParameters) { + for (ViewChangeListener l : listeners) { + if (!l.isViewChangeAllowed(previous, next, viewName, + fragmentParameters, internalParameters)) { + return false; + } + } + return true; + } + + /** + * Return the fragment manager that is used to get, listen to and manipulate + * the URI fragment or other source of navigation information. + * + * @return fragment manager in use + */ + protected FragmentManager getFragmentManager() { + return fragmentManager; + } + + /** + * Get the main view. + * + * Main view is the default view shown to user when he opens application + * without specifying view name. + * + * @return name of the main view. + */ + public String getMainView() { + return mainViewName; + } + + /** + * Set the main view. + * + * Main view is the default view shown to user when he opens application + * without specifying view name. If the {@link Navigator} does not have a + * current view, the main view is automatically selected when set. + * + * @param mainViewName + * name of the main view. + */ + public void setMainView(String mainViewName) { + this.mainViewName = mainViewName; + // TODO should this be done? + if (currentView == null) { + navigateTo(mainViewName); + } + } + + /** + * Fire an event when the current view has changed. + * + * @param previousView + */ + protected void fireViewChange(View previousView) { + for (ViewChangeListener l : listeners) { + l.navigatorViewChanged(previousView, currentView); + } + } + + /** + * Register a view provider (factory). + * + * Providers are called in order of registration until one that can handle + * the requested view name is found. + * + * @param provider + * provider to register + */ + public void registerProvider(ViewProvider provider) { + providers.add(provider); + } + + /** + * Unregister a view provider (factory). + * + * @param provider + * provider to unregister + */ + public void unregisterProvider(ViewProvider provider) { + providers.remove(provider); + } + + /** + * Listen to changes of the active view. + * + * The listener will get notified after the view has changed. + * + * @param listener + * Listener to invoke after view changes. + */ + public void addListener(ViewChangeListener listener) { + listeners.add(listener); + } + + /** + * Remove a view change listener. + * + * @param listener + * Listener to remove. + */ + public void removeListener(ViewChangeListener listener) { + listeners.remove(listener); + } + +} diff --git a/src/com/vaadin/navigator/View.java b/src/com/vaadin/navigator/View.java new file mode 100644 index 0000000000..efa13fbac2 --- /dev/null +++ b/src/com/vaadin/navigator/View.java @@ -0,0 +1,50 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +import com.vaadin.ui.Component; + +/** + * Interface for all views controlled by the navigator. + * + * Each view added to the navigator must implement this interface. Typically, a + * view is a {@link Component}. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface View extends Serializable { + + /** + * Init view. + * + * Convenience method which navigator calls just before the view is rendered + * for the first time. This is called only once in the lifetime of each view + * instance. + */ + public void init(); + + /** + * This view is navigated to. + * + * This method is always called before the view is shown on screen. If there + * is any additional id to data what should be shown in the view, it is also + * optionally passed as parameter. + * + * TODO fragmentParameters null if no parameters or empty string? + * + * @param fragmentParameters + * parameters to the view or null if none given. This is the + * string that appears e.g. in URI after "viewname/" + * @param internalParameters + * parameters given directly to + * {@link Navigator#navigateTo(String, Object...)}, not passed + * via the fragment and not preserved in bookmarks + */ + public void navigateTo(String fragmentParameters, + Object... internalParameters); +} \ No newline at end of file diff --git a/src/com/vaadin/navigator/ViewChangeListener.java b/src/com/vaadin/navigator/ViewChangeListener.java new file mode 100644 index 0000000000..9ac48e27cc --- /dev/null +++ b/src/com/vaadin/navigator/ViewChangeListener.java @@ -0,0 +1,62 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +/** + * Interface for listening to View changes before and after they occur. + * + * Implementations of this interface can also block navigation between views + * before it is performed. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface ViewChangeListener extends Serializable { + + /** + * Check whether changing the view is permissible. + * + * This method may also e.g. open a "save" dialog or question about the + * change, which may re-initiate the navigation operation after user action. + * + * If this listener does not want to block the view change (e.g. does not + * know the view in question), it should return true. If any listener + * returns false, the view change is not allowed. + * + * TODO move to separate interface? + * + * @param previous + * view that is being deactivated + * @param next + * view that is being activated + * @param viewName + * name of the new view that is being activated + * @param fragmentParameters + * fragment parameters (potentially bookmarkable) for the new + * view + * @param internalParameters + * internal parameters for the new view, not visible in the + * browser + * @return true if the view change should be allowed or this listener does + * not care about the view change, false to block the change + */ + public boolean isViewChangeAllowed(View previous, View next, + String viewName, String fragmentParameters, + Object... internalParameters); + + /** + * Invoked after the view has changed. Be careful for deadlocks if you + * decide to change the view again in the listener. + * + * @param previous + * Preview view before the change. + * @param current + * New view after the change. + */ + public void navigatorViewChanged(View previous, View current); + +} \ No newline at end of file diff --git a/src/com/vaadin/navigator/ViewDisplay.java b/src/com/vaadin/navigator/ViewDisplay.java new file mode 100644 index 0000000000..6016951394 --- /dev/null +++ b/src/com/vaadin/navigator/ViewDisplay.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +/** + * Interface for displaying a view in an appropriate location. + * + * The view display can be a component/layout itself or can modify a separate + * layout. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface ViewDisplay extends Serializable { + /** + * Remove previously shown view and show the newly selected view in its + * place. + * + * The parameters for the view have been set before this method is called. + * + * @param view + * new view to show + */ + public void showView(View view); +} \ No newline at end of file diff --git a/src/com/vaadin/navigator/ViewProvider.java b/src/com/vaadin/navigator/ViewProvider.java new file mode 100644 index 0000000000..4d9d22acab --- /dev/null +++ b/src/com/vaadin/navigator/ViewProvider.java @@ -0,0 +1,44 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.navigator; + +import java.io.Serializable; + +/** + * A provider for view instances that can return pre-registered views or + * dynamically create new views. + * + * If multiple providers are used, {@link #getViewName(String)} of each is + * called (in registration order) until one of them returns a non-null value. + * The {@link #getView(String)} method of that provider is then used. + * + * @author Vaadin Ltd + * @since 7.0 + */ +public interface ViewProvider extends Serializable { + /** + * Extract the view name from a combined view name and parameter string. + * This method should return a view name if and only if this provider + * handles creation of such views. + * + * @param viewAndParameters + * string with view name and its fragment parameters (if given), + * not null + * @return view name if the view is handled by this provider, null otherwise + */ + public String getViewName(String viewAndParameters); + + /** + * Create or return a pre-created instance of a view. + * + * The parameters for the view are set separately by the navigator when the + * view is activated. + * + * @param viewName + * name of the view, not null + * @return newly created view (null if none available for the view name) + */ + public View getView(String viewName); +} \ No newline at end of file diff --git a/tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java b/tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java new file mode 100644 index 0000000000..818229ab2f --- /dev/null +++ b/tests/server-side/com/vaadin/tests/server/navigator/ClassBasedViewProviderTest.java @@ -0,0 +1,208 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.server.navigator; + +import junit.framework.TestCase; + +import com.vaadin.navigator.Navigator.ClassBasedViewProvider; +import com.vaadin.navigator.View; +import com.vaadin.ui.Label; + +public class ClassBasedViewProviderTest extends TestCase { + + private ClassBasedViewProvider provider; + + public static class TestView extends Label implements View { + public boolean initialized = false; + public String parameters = null; + + public void init() { + initialized = true; + } + + public void navigateTo(String parameters, Object... internalParameters) { + this.parameters = parameters; + } + + } + + public static class TestView2 extends TestView { + + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + provider = new ClassBasedViewProvider(); + } + + public void testAddViewWithNullName() throws Exception { + try { + provider.addView(null, TestView.class); + fail("Should not be able to add view with null name"); + } catch (IllegalArgumentException e) { + } + } + + public void testAddViewWithEmptyStringName() throws Exception { + try { + provider.addView("", TestView.class); + fail("Should not be able to add view with empty name"); + } catch (IllegalArgumentException e) { + } + } + + public void testAddViewNull() throws Exception { + try { + provider.addView("test", null); + fail("Should not be able to add null view"); + } catch (IllegalArgumentException e) { + } + } + + public void testAddViewSameName() throws Exception { + try { + provider.addView("test", TestView.class); + provider.addView("test", TestView2.class); + fail("Should not be able to add two views with same name"); + } catch (IllegalArgumentException e) { + } + } + + public void testAddViewSameClass() throws Exception { + try { + provider.addView("test", TestView.class); + provider.addView("test2", TestView.class); + fail("Should not be able to add same view class with two different names"); + } catch (IllegalArgumentException e) { + } + } + + public void testGetViewNameForNullString() throws Exception { + assertNull( + "Found view name for null view string in an empty view provider", + provider.getViewName((String) null)); + + provider.addView("test", TestView.class); + assertNull("Found view name for null view string", + provider.getViewName((String) null)); + } + + public void testGetViewNameForNullClass() throws Exception { + assertNull("Found view name for null class", + provider.getViewName((Class) null)); + } + + public void testGetViewNameForEmptyString() throws Exception { + assertNull( + "Found view name for empty view string in an empty provider", + provider.getViewName("")); + provider.addView("test", TestView.class); + assertNull("Found view name for empty view string", + provider.getViewName("")); + } + + public void testGetViewNameForClass() throws Exception { + provider.addView("test", TestView.class); + assertEquals("No view name found for view class", "test", + provider.getViewName(TestView.class)); + } + + public void testGetViewNameWithParameters() throws Exception { + provider.addView("test", TestView.class); + assertEquals("Incorrect view name found for view string", "test", + provider.getViewName("test")); + assertEquals( + "Incorrect view name found for view string ending with slash", + "test", provider.getViewName("test/")); + assertEquals( + "Incorrect view name found for view string with parameters", + "test", provider.getViewName("test/params/are/here")); + } + + public void testGetViewNameMultipleRegisteredWithParameters() + throws Exception { + provider.addView("test", TestView.class); + provider.addView("test2", TestView2.class); + assertEquals("Incorrect view name found for view string", "test", + provider.getViewName("test/test2/params")); + } + + public void testGetViewNameNestedNames() throws Exception { + provider.addView("test/subview", TestView2.class); + provider.addView("test", TestView.class); + assertEquals("Incorrect view name found for subview string", + "test/subview", provider.getViewName("test/subview")); + assertEquals( + "Incorrect view name found for subview string with empty parameters", + "test/subview", provider.getViewName("test/subview/")); + assertEquals( + "Incorrect view name found for subview string with parameters", + "test/subview", provider.getViewName("test/subview/parameters")); + assertEquals("Incorrect view name found for top level view string", + "test", provider.getViewName("test")); + assertEquals( + "Incorrect view name found for top level view string with empty parameters", + "test", provider.getViewName("test/")); + assertEquals( + "Incorrect view name found for top level view string with parameters starting like subview name", + "test", provider.getViewName("test/subviewnothere")); + } + + public void testGetViewClass() throws Exception { + assertNull("View class found for empty view provider", + provider.getViewClass("test")); + provider.addView("test", TestView.class); + assertEquals("View class not found", TestView.class, + provider.getViewClass("test")); + assertNull("View class found for unregistered view name", + provider.getViewClass("test2")); + } + + public void testGetViewSimple() throws Exception { + assertNull("Found view in an empty view provider", + provider.getViewName("test")); + + provider.addView("test", TestView.class); + View view = provider.getView("test"); + assertNotNull("Did not get view from a provider", view); + assertEquals("Incorrect view type", TestView.class, view.getClass()); + assertTrue("View not initialized", ((TestView) view).initialized); + } + + public void testGetViewMultipleRegistered() throws Exception { + provider.addView("test", TestView.class); + provider.addView("test2", TestView2.class); + assertEquals("Incorrect view type", TestView.class, + provider.getView("test").getClass()); + assertEquals("Incorrect view type", TestView2.class, + provider.getView("test2").getClass()); + assertEquals("Incorrect view type", TestView.class, + provider.getView("test").getClass()); + } + + public void testRemoveView() throws Exception { + provider.addView("test", TestView.class); + assertNotNull("Did not get view from a provider", + provider.getView("test")); + provider.removeView("test"); + assertNull("View class found for removed view name", + provider.getViewClass("test")); + assertNull("View name found for removed view", + provider.getViewName(TestView.class)); + // cached view? + assertNull( + "Received view instance from a provider after removing view type", + provider.getView("test")); + } + + public void testGetViewCached() throws Exception { + provider.addView("test", TestView.class); + View view1 = provider.getView("test"); + View view2 = provider.getView("test"); + assertSame("View instance not cached", view1, view2); + } + +} diff --git a/tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java b/tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java new file mode 100644 index 0000000000..d6a7cc68c7 --- /dev/null +++ b/tests/server-side/com/vaadin/tests/server/navigator/NavigatorTest.java @@ -0,0 +1,252 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.server.navigator; + +import junit.framework.TestCase; + +import org.easymock.EasyMock; +import org.easymock.IMocksControl; + +import com.vaadin.navigator.FragmentManager; +import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener; +import com.vaadin.navigator.ViewDisplay; +import com.vaadin.navigator.ViewProvider; + +public class NavigatorTest extends TestCase { + + // TODO test internal parameters (and absence of them) + // TODO test listeners blocking navigation, multiple listeners + + public void testBasicNavigation() { + IMocksControl control = EasyMock.createControl(); + FragmentManager manager = control.createMock(FragmentManager.class); + ViewDisplay display = control.createMock(ViewDisplay.class); + ViewProvider provider = control.createMock(ViewProvider.class); + View view1 = control.createMock(View.class); + View view2 = control.createMock(View.class); + + // prepare mocks: what to expect + EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn(""); + view1.navigateTo(null); + display.showView(view1); + manager.setFragment("test1"); + + EasyMock.expect(provider.getViewName("test2/")).andReturn("test2"); + EasyMock.expect(provider.getView("test2")).andReturn(view2); + view2.init(); + EasyMock.expect(manager.getFragment()).andReturn("view1"); + view2.navigateTo(null); + display.showView(view2); + manager.setFragment("test2"); + + EasyMock.expect(provider.getViewName("test1/params")) + .andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn("view2"); + view1.navigateTo("params"); + display.showView(view1); + manager.setFragment("test1/params"); + + control.replay(); + + // create and test navigator + Navigator navigator = new Navigator(manager, display); + navigator.registerProvider(provider); + + navigator.navigateTo("test1"); + navigator.navigateTo("test2/"); + navigator.navigateTo("test1/params"); + } + + public void testMainView() { + IMocksControl control = EasyMock.createControl(); + FragmentManager manager = control.createMock(FragmentManager.class); + ViewDisplay display = control.createMock(ViewDisplay.class); + ViewProvider provider = control.createMock(ViewProvider.class); + View view1 = control.createMock(View.class); + View view2 = control.createMock(View.class); + + // prepare mocks: what to expect + EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn(""); + view1.navigateTo(null); + display.showView(view1); + manager.setFragment("test1"); + + EasyMock.expect(provider.getViewName("test2")).andReturn("test2"); + EasyMock.expect(provider.getView("test2")).andReturn(view2); + view2.init(); + EasyMock.expect(manager.getFragment()).andReturn("view1"); + view2.navigateTo(null); + display.showView(view2); + manager.setFragment("test2"); + + EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn(""); + view1.navigateTo(null); + display.showView(view1); + manager.setFragment("test1"); + + EasyMock.expect(provider.getViewName("test1/params")) + .andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn("view2"); + view1.navigateTo("params"); + display.showView(view1); + manager.setFragment("test1/params"); + + control.replay(); + + // create and test navigator + Navigator navigator = new Navigator(manager, display); + navigator.registerProvider(provider); + // this also triggers navigation + navigator.setMainView("test1"); + + navigator.navigateTo("test2"); + navigator.navigateTo(""); + navigator.navigateTo("test1/params"); + } + + public void testListeners() { + IMocksControl control = EasyMock.createControl(); + FragmentManager manager = control.createMock(FragmentManager.class); + ViewDisplay display = control.createMock(ViewDisplay.class); + ViewProvider provider = control.createMock(ViewProvider.class); + View view1 = control.createMock(View.class); + View view2 = control.createMock(View.class); + ViewChangeListener listener = control + .createMock(ViewChangeListener.class); + + // prepare mocks: what to expect + EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn(""); + EasyMock.expect( + listener.isViewChangeAllowed(null, view1, "test1", null, + new Object[0])).andReturn(true); + view1.navigateTo(null); + display.showView(view1); + manager.setFragment("test1"); + listener.navigatorViewChanged(null, view1); + + EasyMock.expect(provider.getViewName("test2")).andReturn("test2"); + EasyMock.expect(provider.getView("test2")).andReturn(view2); + view2.init(); + EasyMock.expect(manager.getFragment()).andReturn("view1"); + EasyMock.expect( + listener.isViewChangeAllowed(view1, view2, "test2", null, + new Object[0])).andReturn(true); + view2.navigateTo(null); + display.showView(view2); + manager.setFragment("test2"); + listener.navigatorViewChanged(view1, view2); + + control.replay(); + + // create and test navigator + Navigator navigator = new Navigator(manager, display); + navigator.registerProvider(provider); + navigator.addListener(listener); + + navigator.navigateTo("test1"); + navigator.navigateTo("test2"); + } + + public void testBlockNavigation() { + IMocksControl control = EasyMock.createControl(); + FragmentManager manager = control.createMock(FragmentManager.class); + ViewDisplay display = control.createMock(ViewDisplay.class); + ViewProvider provider = control.createMock(ViewProvider.class); + View view1 = control.createMock(View.class); + View view2 = control.createMock(View.class); + ViewChangeListener listener1 = control + .createMock(ViewChangeListener.class); + ViewChangeListener listener2 = control + .createMock(ViewChangeListener.class); + + // prepare mocks: what to expect + // first listener blocks first view change + EasyMock.expect(provider.getViewName("test1")).andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn(""); + EasyMock.expect( + listener1.isViewChangeAllowed(null, view1, "test1", null, + new Object[0])).andReturn(false); + + // second listener blocks second view change + EasyMock.expect(provider.getViewName("test1/test")).andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn(""); + EasyMock.expect( + listener1.isViewChangeAllowed(null, view1, "test1", "test", + new Object[0])).andReturn(true); + EasyMock.expect( + listener2.isViewChangeAllowed(null, view1, "test1", "test", + new Object[0])).andReturn(false); + + // both listeners allow view change + EasyMock.expect(provider.getViewName("test1/bar")).andReturn("test1"); + EasyMock.expect(provider.getView("test1")).andReturn(view1); + view1.init(); + EasyMock.expect(manager.getFragment()).andReturn(""); + EasyMock.expect( + listener1.isViewChangeAllowed(null, view1, "test1", "bar", + new Object[0])).andReturn(true); + EasyMock.expect( + listener2.isViewChangeAllowed(null, view1, "test1", "bar", + new Object[0])).andReturn(true); + view1.navigateTo("bar"); + display.showView(view1); + manager.setFragment("test1/bar"); + listener1.navigatorViewChanged(null, view1); + listener2.navigatorViewChanged(null, view1); + + // both listeners allow view change from non-null view + EasyMock.expect(provider.getViewName("test2")).andReturn("test2"); + EasyMock.expect(provider.getView("test2")).andReturn(view2); + view2.init(); + EasyMock.expect(manager.getFragment()).andReturn("view1"); + EasyMock.expect( + listener1.isViewChangeAllowed(view1, view2, "test2", null, + new Object[0])).andReturn(true); + EasyMock.expect( + listener2.isViewChangeAllowed(view1, view2, "test2", null, + new Object[0])).andReturn(true); + view2.navigateTo(null); + display.showView(view2); + manager.setFragment("test2"); + listener1.navigatorViewChanged(view1, view2); + listener2.navigatorViewChanged(view1, view2); + + control.replay(); + + // create and test navigator + Navigator navigator = new Navigator(manager, display); + navigator.registerProvider(provider); + navigator.addListener(listener1); + navigator.addListener(listener2); + + navigator.navigateTo("test1"); + navigator.navigateTo("test1/test"); + navigator.navigateTo("test1/bar"); + navigator.navigateTo("test2"); + } + +} diff --git a/tests/server-side/com/vaadin/tests/server/navigator/UriFragmentManagerTest.java b/tests/server-side/com/vaadin/tests/server/navigator/UriFragmentManagerTest.java new file mode 100644 index 0000000000..cfbf50f256 --- /dev/null +++ b/tests/server-side/com/vaadin/tests/server/navigator/UriFragmentManagerTest.java @@ -0,0 +1,51 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.server.navigator; + +import junit.framework.TestCase; + +import org.easymock.EasyMock; +import org.easymock.IMocksControl; + +import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.Navigator.UriFragmentManager; +import com.vaadin.ui.Root; +import com.vaadin.ui.Root.FragmentChangedEvent; + +public class UriFragmentManagerTest extends TestCase { + + public void testGetSetFragment() { + Root root = EasyMock.createMock(Root.class); + UriFragmentManager manager = new UriFragmentManager(root, null); + + // prepare mock + EasyMock.expect(root.getFragment()).andReturn(""); + root.setFragment("test"); + EasyMock.expect(root.getFragment()).andReturn("test"); + EasyMock.replay(root); + + // test manager using the mock + assertEquals("Incorrect fragment value", "", manager.getFragment()); + manager.setFragment("test"); + assertEquals("Incorrect fragment value", "test", manager.getFragment()); + } + + public void testListener() { + // create mocks + IMocksControl control = EasyMock.createControl(); + Navigator navigator = control.createMock(Navigator.class); + Root root = control.createMock(Root.class); + + UriFragmentManager manager = new UriFragmentManager(root, navigator); + + EasyMock.expect(root.getFragment()).andReturn("test"); + navigator.navigateTo("test"); + control.replay(); + + FragmentChangedEvent event = root.new FragmentChangedEvent(root, + "oldtest"); + manager.fragmentChanged(event); + } +}