* Added support for HTML5 push/replaceState for proper deep linkin features * Automated test script now works at least on chrome * Uses html5 push/popstate to implement uri fragment feature * fire legacy fragment change events also via popstate events rpc calls * send new fragments via pushstate mechanism * formatting * Formatting and adding test and workaround for IE bug * Formatting and depracated UriFragmentListener * Aligned naming in the new API * Ignored IE due to web driver bug Tested a workaround with javascript based window.location.href fetch, but that don’t seem to work stable enough.tags/8.0.0.beta2
@@ -26,11 +26,7 @@ import com.google.gwt.event.dom.client.ScrollHandler; | |||
import com.google.gwt.event.logical.shared.HasResizeHandlers; | |||
import com.google.gwt.event.logical.shared.ResizeEvent; | |||
import com.google.gwt.event.logical.shared.ResizeHandler; | |||
import com.google.gwt.event.logical.shared.ValueChangeEvent; | |||
import com.google.gwt.event.logical.shared.ValueChangeHandler; | |||
import com.google.gwt.event.shared.HandlerRegistration; | |||
import com.google.gwt.http.client.URL; | |||
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.ui.SimplePanel; | |||
@@ -46,7 +42,6 @@ import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; | |||
import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; | |||
import com.vaadin.client.ui.ui.UIConnector; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.ui.ui.UIConstants; | |||
/** | |||
* | |||
@@ -99,51 +94,8 @@ public class VUI extends SimplePanel implements ResizeHandler, | |||
/** For internal use only. May be removed or replaced in the future. */ | |||
public boolean resizeLazy = false; | |||
private HandlerRegistration historyHandlerRegistration; | |||
private TouchScrollHandler touchScrollHandler; | |||
/** | |||
* The current URI fragment, used to avoid sending updates if nothing has | |||
* changed. | |||
* <p> | |||
* For internal use only. May be removed or replaced in the future. | |||
*/ | |||
public String currentFragment; | |||
/** | |||
* Listener for URI fragment changes. Notifies the server of the new value | |||
* whenever the value changes. | |||
*/ | |||
private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() { | |||
@Override | |||
public void onValueChange(ValueChangeEvent<String> event) { | |||
String newFragment = event.getValue(); | |||
// Send the location to the server if the fragment has changed | |||
// and flush active connectors in UI. | |||
if (!newFragment.equals(currentFragment) && connection != null) { | |||
/* | |||
* Ensure the fragment is properly encoded in all browsers | |||
* (#10769) | |||
* | |||
* createUrlBuilder does not properly pass an empty fragment to | |||
* UrlBuilder on Webkit browsers so do it manually (#11686) | |||
*/ | |||
String location = Window.Location.createUrlBuilder() | |||
.setHash(URL | |||
.decodeQueryString(Window.Location.getHash())) | |||
.buildString(); | |||
currentFragment = newFragment; | |||
connection.flushActiveConnector(); | |||
connection.updateVariable(id, UIConstants.LOCATION_VARIABLE, | |||
location, true); | |||
} | |||
} | |||
}; | |||
private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200, | |||
new ScheduledCommand() { | |||
@@ -186,21 +138,6 @@ public class VUI extends SimplePanel implements ResizeHandler, | |||
} | |||
} | |||
@Override | |||
protected void onAttach() { | |||
super.onAttach(); | |||
historyHandlerRegistration = History | |||
.addValueChangeHandler(historyChangeHandler); | |||
currentFragment = History.getToken(); | |||
} | |||
@Override | |||
protected void onDetach() { | |||
super.onDetach(); | |||
historyHandlerRegistration.removeHandler(); | |||
historyHandlerRegistration = null; | |||
} | |||
/** | |||
* Stop monitoring for parent element resizes. | |||
*/ |
@@ -43,18 +43,17 @@ import com.google.gwt.event.dom.client.ScrollHandler; | |||
import com.google.gwt.event.logical.shared.ResizeEvent; | |||
import com.google.gwt.event.logical.shared.ResizeHandler; | |||
import com.google.gwt.event.shared.HandlerRegistration; | |||
import com.google.gwt.http.client.URL; | |||
import com.google.gwt.user.client.Command; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.Event; | |||
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.Widget; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; | |||
import com.vaadin.client.BrowserInfo; | |||
import com.vaadin.client.ComponentConnector; | |||
import com.vaadin.client.ConnectorHierarchyChangeEvent; | |||
import com.vaadin.client.Focusable; | |||
@@ -105,6 +104,8 @@ import com.vaadin.shared.ui.ui.UIState; | |||
import com.vaadin.shared.util.SharedUtil; | |||
import com.vaadin.ui.UI; | |||
import elemental.client.Browser; | |||
@Connect(value = UI.class, loadStyle = LoadStyle.EAGER) | |||
public class UIConnector extends AbstractSingleComponentContainerConnector | |||
implements Paintable, MayScrollChildren { | |||
@@ -115,6 +116,12 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
private HandlerRegistration windowOrderRegistration; | |||
/* | |||
* Used to workaround IE bug related to popstate events and certain fragment | |||
* only changes | |||
*/ | |||
private String currentLocation; | |||
private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() { | |||
@Override | |||
public void onStateChanged(StateChangeEvent stateChangeEvent) { | |||
@@ -232,6 +239,28 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
} | |||
} | |||
}); | |||
Browser.getWindow().setOnpopstate(evt -> { | |||
final String newLocation = Browser.getWindow().getLocation() | |||
.toString(); | |||
getRpcProxy(UIServerRpc.class).popstate(newLocation); | |||
currentLocation = newLocation; | |||
}); | |||
// IE doesn't fire popstate correctly with certain hash changes. | |||
// Simulate the missing event with History handler. | |||
if (BrowserInfo.get().isIE()) { | |||
History.addValueChangeHandler(evt -> { | |||
final String newLocation = Browser.getWindow().getLocation() | |||
.toString(); | |||
if (!newLocation.equals(currentLocation)) { | |||
currentLocation = newLocation; | |||
getRpcProxy(UIServerRpc.class).popstate( | |||
Browser.getWindow().getLocation().toString()); | |||
} | |||
}); | |||
currentLocation = Browser.getWindow().getLocation().toString(); | |||
} | |||
} | |||
private native void open(String url, String name) | |||
@@ -402,34 +431,13 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
scrollIntoView(connector); | |||
} | |||
if (uidl.hasAttribute(UIConstants.LOCATION_VARIABLE)) { | |||
String location = uidl | |||
.getStringAttribute(UIConstants.LOCATION_VARIABLE); | |||
String newFragment; | |||
int fragmentIndex = location.indexOf('#'); | |||
if (fragmentIndex >= 0) { | |||
// Decode fragment to avoid double encoding (#10769) | |||
newFragment = URL.decodePathSegment( | |||
location.substring(fragmentIndex + 1)); | |||
if (newFragment.isEmpty() | |||
&& Location.getHref().indexOf('#') == -1) { | |||
// Ensure there is a trailing # even though History and | |||
// Location.getHash() treat null and "" the same way. | |||
Location.assign(Location.getHref() + "#"); | |||
} | |||
} else { | |||
// No fragment in server-side location, but can't completely | |||
// remove the browser fragment since that would reload the page | |||
newFragment = ""; | |||
} | |||
getWidget().currentFragment = newFragment; | |||
if (!newFragment.equals(History.getToken())) { | |||
History.newItem(newFragment, true); | |||
} | |||
if (uidl.hasAttribute(UIConstants.ATTRIBUTE_PUSH_STATE)) { | |||
Browser.getWindow().getHistory().pushState(null, "", | |||
uidl.getStringAttribute(UIConstants.ATTRIBUTE_PUSH_STATE)); | |||
} | |||
if (uidl.hasAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE)) { | |||
Browser.getWindow().getHistory().replaceState(null, "", uidl | |||
.getStringAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE)); | |||
} | |||
if (firstPaint) { |
@@ -8,8 +8,7 @@ | |||
<inherits name="com.vaadin.Vaadin" /> | |||
<!-- Elemental is used for handling Json only --> | |||
<inherits name="elemental.Json" /> | |||
<inherits name="elemental.Elemental" /> | |||
<inherits name="com.google.gwt.precompress.Precompress" /> | |||
@@ -251,7 +251,9 @@ public class Page implements Serializable { | |||
* changes. | |||
* | |||
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener) | |||
* @deprecated Use {@link PopStateListener} instead | |||
*/ | |||
@Deprecated | |||
@FunctionalInterface | |||
public interface UriFragmentChangedListener extends Serializable { | |||
/** | |||
@@ -272,6 +274,32 @@ public class Page implements Serializable { | |||
.findMethod(Page.UriFragmentChangedListener.class, | |||
"uriFragmentChanged", UriFragmentChangedEvent.class); | |||
/** | |||
* Listener that that gets notified when the URI of the page changes due to | |||
* back/forward functionality of the browser. | |||
* | |||
* @see Page#addPopStateListener(PopStateListener) | |||
* @since 8.0 | |||
*/ | |||
@FunctionalInterface | |||
public interface PopStateListener extends Serializable { | |||
/** | |||
* Event handler method invoked when the URI fragment of the page | |||
* changes. Please note that the initial URI fragment has already been | |||
* set when a new UI is initialized, so there will not be any initial | |||
* event for listeners added during {@link UI#init(VaadinRequest)}. | |||
* | |||
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener) | |||
* | |||
* @param event | |||
* the URI fragment changed event | |||
*/ | |||
public void uriChanged(PopStateEvent event); | |||
} | |||
private static final Method URI_CHANGED_METHOD = ReflectTools.findMethod( | |||
Page.PopStateListener.class, "uriChanged", PopStateEvent.class); | |||
/** | |||
* Resources to be opened automatically on next repaint. The list is | |||
* automatically cleared when it has been sent to the client. | |||
@@ -328,6 +356,53 @@ public class Page implements Serializable { | |||
} | |||
} | |||
/** | |||
* Event fired when the URI of a <code>Page</code> changes (aka HTML 5 | |||
* popstate event) on the client side due to browsers back/forward | |||
* functionality. | |||
* | |||
* @see Page#addPopStateListener(PopStateListener) | |||
* @since 8.0 | |||
*/ | |||
public static class PopStateEvent extends EventObject { | |||
/** | |||
* The new URI as String | |||
*/ | |||
private final String uri; | |||
/** | |||
* Creates a new instance of PopstateEvent. | |||
* | |||
* @param source | |||
* the Source of the event. | |||
* @param uri | |||
* the new uri | |||
*/ | |||
public PopStateEvent(Page source, String uri) { | |||
super(source); | |||
this.uri = uri; | |||
} | |||
/** | |||
* Gets the page in which the uri has changed. | |||
* | |||
* @return the page in which the uri has changed | |||
*/ | |||
public Page getPage() { | |||
return (Page) getSource(); | |||
} | |||
/** | |||
* Get the new URI | |||
* | |||
* @return the new uri | |||
*/ | |||
public String getUri() { | |||
return uri; | |||
} | |||
} | |||
@FunctionalInterface | |||
private static interface InjectedStyle extends Serializable { | |||
public void paint(int id, PaintTarget target) throws PaintException; | |||
@@ -483,6 +558,9 @@ public class Page implements Serializable { | |||
private String windowName; | |||
private String newPushState; | |||
private String newReplaceState; | |||
public Page(UI uI, PageState state) { | |||
this.uI = uI; | |||
this.state = state; | |||
@@ -516,13 +594,37 @@ public class Page implements Serializable { | |||
* @param listener | |||
* the URI fragment listener to add | |||
* @return a registration object for removing the listener | |||
* @deprecated Use {@link Page#addPopStateListener(PopStateListener)} | |||
* instead | |||
*/ | |||
@Deprecated | |||
public Registration addUriFragmentChangedListener( | |||
Page.UriFragmentChangedListener listener) { | |||
return addListener(UriFragmentChangedEvent.class, listener, | |||
URI_FRAGMENT_CHANGED_METHOD); | |||
} | |||
/** | |||
* Adds a listener that gets notified every time the URI of this page is | |||
* changed due to back/forward functionality of the browser. | |||
* <p> | |||
* Note that one only gets notified when the back/forward button affects | |||
* history changes with-in same UI, created by | |||
* {@link Page#pushState(String)} or {@link Page#replaceState(String)} | |||
* functions. | |||
* | |||
* @see #getLocation() | |||
* @see Registration | |||
* | |||
* @param listener | |||
* the Popstate listener to add | |||
* @return a registration object for removing the listener | |||
* @since 8.0 | |||
*/ | |||
public Registration addPopStateListener(Page.PopStateListener listener) { | |||
return addListener(PopStateEvent.class, listener, URI_CHANGED_METHOD); | |||
} | |||
/** | |||
* Removes a URI fragment listener that was previously added to this page. | |||
* | |||
@@ -580,6 +682,7 @@ public class Page implements Serializable { | |||
try { | |||
location = new URI(location.getScheme(), | |||
location.getSchemeSpecificPart(), newUriFragment); | |||
pushState(location); | |||
} catch (URISyntaxException e) { | |||
// This should not actually happen as the fragment syntax is not | |||
// constrained | |||
@@ -588,7 +691,6 @@ public class Page implements Serializable { | |||
if (fireEvents) { | |||
fireEvent(new UriFragmentChangedEvent(this, newUriFragment)); | |||
} | |||
uI.markAsDirty(); | |||
} | |||
private void fireEvent(EventObject event) { | |||
@@ -866,9 +968,14 @@ public class Page implements Serializable { | |||
notifications = null; | |||
} | |||
if (location != null) { | |||
target.addAttribute(UIConstants.LOCATION_VARIABLE, | |||
location.toString()); | |||
if (newPushState != null) { | |||
target.addAttribute(UIConstants.ATTRIBUTE_PUSH_STATE, newPushState); | |||
newPushState = null; | |||
} | |||
if (newReplaceState != null) { | |||
target.addAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE, | |||
newReplaceState); | |||
newReplaceState = null; | |||
} | |||
if (styles != null) { | |||
@@ -931,6 +1038,84 @@ public class Page implements Serializable { | |||
return location; | |||
} | |||
/** | |||
* Updates the browsers URI without causing actual page change. This method | |||
* is useful if you wish implement "deep linking" to your application. | |||
* Calling the method also adds a new entry to clients browser history and | |||
* you can further use {@link PopStateListener} to track the usage of | |||
* back/forward feature in browser. | |||
* <p> | |||
* Note, the current implementation supports setting only one new uri in one | |||
* user interaction. | |||
* | |||
* @param uri | |||
* to be used for pushState operation. The URI is resolved over | |||
* the current location. If the given URI is absolute, it must be | |||
* of same origin as the current URI or the browser will not | |||
* accept the new value. | |||
* @since 8.0 | |||
*/ | |||
public void pushState(String uri) { | |||
newPushState = uri; | |||
uI.markAsDirty(); | |||
location = location.resolve(uri); | |||
} | |||
/** | |||
* Updates the browsers URI without causing actual page change. This method | |||
* is useful if you wish implement "deep linking" to your application. | |||
* Calling the method also adds a new entry to clients browser history and | |||
* you can further use {@link PopStateListener} to track the usage of | |||
* back/forward feature in browser. | |||
* <p> | |||
* Note, the current implementation supports setting only one new uri in one | |||
* user interaction. | |||
* | |||
* @param uri | |||
* the URI to be used for pushState operation. The URI is | |||
* resolved over the current location. If the given URI is | |||
* absolute, it must be of same origin as the current URI or the | |||
* browser will not accept the new value. | |||
* @since 8.0 | |||
*/ | |||
public void pushState(URI uri) { | |||
pushState(uri.toString()); | |||
} | |||
/** | |||
* Updates the browsers URI without causing actual page change in the same | |||
* way as {@link #pushState(String)}, but does not add new entry to browsers | |||
* history. | |||
* | |||
* @param uri | |||
* the URI to be used for replaceState operation. The URI is | |||
* resolved over the current location. If the given URI is | |||
* absolute, it must be of same origin as the current URI or the | |||
* browser will not accept the new value. | |||
* @since 8.0 | |||
*/ | |||
public void replaceState(String uri) { | |||
newReplaceState = uri; | |||
uI.markAsDirty(); | |||
location = location.resolve(uri); | |||
} | |||
/** | |||
* Updates the browsers URI without causing actual page change in the same | |||
* way as {@link #pushState(URI)}, but does not add new entry to browsers | |||
* history. | |||
* | |||
* @param uri | |||
* the URI to be used for replaceState operation. The URI is | |||
* resolved over the current location. If the given URI is | |||
* absolute, it must be of same origin as the current URI or the | |||
* browser will not accept the new value. | |||
* @since 8.0 | |||
*/ | |||
public void replaceState(URI uri) { | |||
replaceState(uri.toString()); | |||
} | |||
/** | |||
* For internal use only. Used to update the server-side location when the | |||
* client-side location changes. | |||
@@ -943,7 +1128,7 @@ public class Page implements Serializable { | |||
*/ | |||
@Deprecated | |||
public void updateLocation(String location) { | |||
updateLocation(location, true); | |||
updateLocation(location, true, false); | |||
} | |||
/** | |||
@@ -957,8 +1142,11 @@ public class Page implements Serializable { | |||
* @param fireEvents | |||
* whether to fire {@link UriFragmentChangedEvent} if the URI | |||
* fragment changes | |||
* @param firePopstate | |||
* whether to fire {@link PopStateEvent} | |||
*/ | |||
public void updateLocation(String location, boolean fireEvents) { | |||
public void updateLocation(String location, boolean fireEvents, | |||
boolean firePopstate) { | |||
try { | |||
String oldUriFragment = this.location.getFragment(); | |||
this.location = new URI(location); | |||
@@ -967,6 +1155,9 @@ public class Page implements Serializable { | |||
&& !SharedUtil.equals(oldUriFragment, newUriFragment)) { | |||
fireEvent(new UriFragmentChangedEvent(this, newUriFragment)); | |||
} | |||
if (firePopstate) { | |||
fireEvent(new PopStateEvent(this, location)); | |||
} | |||
} catch (URISyntaxException e) { | |||
throw new RuntimeException(e); | |||
} |
@@ -193,6 +193,12 @@ public abstract class UI extends AbstractSingleComponentContainer | |||
public void acknowledge() { | |||
// Nothing to do, just need the message to be sent and processed | |||
} | |||
@Override | |||
public void popstate(String uri) { | |||
getPage().updateLocation(uri, true, true); | |||
} | |||
}; | |||
private DebugWindowServerRpc debugRpc = new DebugWindowServerRpc() { | |||
@Override | |||
@@ -437,11 +443,6 @@ public abstract class UI extends AbstractSingleComponentContainer | |||
actionManager.handleActions(variables, this); | |||
} | |||
if (variables.containsKey(UIConstants.LOCATION_VARIABLE)) { | |||
String location = (String) variables | |||
.get(UIConstants.LOCATION_VARIABLE); | |||
getPage().updateLocation(location, true); | |||
} | |||
} | |||
/* | |||
@@ -789,10 +790,10 @@ public abstract class UI extends AbstractSingleComponentContainer | |||
int newWidth = page.getBrowserWindowWidth(); | |||
int newHeight = page.getBrowserWindowHeight(); | |||
page.updateLocation(oldLocation.toString(), false); | |||
page.updateLocation(oldLocation.toString(), false, false); | |||
page.updateBrowserWindowSize(oldWidth, oldHeight, false); | |||
page.updateLocation(newLocation.toString(), true); | |||
page.updateLocation(newLocation.toString(), true, false); | |||
page.updateBrowserWindowSize(newWidth, newHeight, true); | |||
} | |||
@@ -28,7 +28,10 @@ public class UIConstants implements Serializable { | |||
public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain"; | |||
@Deprecated | |||
public static final String LOCATION_VARIABLE = "location"; | |||
public static final String ATTRIBUTE_PUSH_STATE = "ps"; | |||
@Deprecated | |||
public static final String ATTRIBUTE_REPLACE_STATE = "rs"; | |||
@Deprecated | |||
public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style"; |
@@ -35,6 +35,8 @@ public interface UIServerRpc extends ClickRpc, ServerRpc { | |||
* should always be called to ensure the message is flushed right away. | |||
*/ | |||
public void poll(); | |||
public void popstate(String uri); | |||
@NoLoadingIndicator | |||
public void acknowledge(); |
@@ -0,0 +1,80 @@ | |||
package com.vaadin.tests.components.ui; | |||
import java.net.URI; | |||
import com.vaadin.server.Page; | |||
import com.vaadin.server.Page.PopStateEvent; | |||
import com.vaadin.server.Page.PopStateListener; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractReindeerTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.CheckBox; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.Notification; | |||
public class PushStateAndReplaceState extends AbstractReindeerTestUI { | |||
private final Label locationLabel = new Label(); | |||
private CheckBox replace; | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
locationLabel.setId("locationLabel"); | |||
addComponent(locationLabel); | |||
updateLabel(); | |||
getPage().addPopStateListener(new PopStateListener() { | |||
@Override | |||
public void uriChanged(PopStateEvent event) { | |||
Notification.show("Popstate event"); | |||
updateLabel(); | |||
} | |||
}); | |||
replace = new CheckBox("replace"); | |||
replace.setId("replace"); | |||
addComponent(replace); | |||
addComponent(createButton("test", "Move to ./test", | |||
Page.getCurrent().getLocation().toString() + "/test")); | |||
addComponent(createButton("X", "Move to X", "X")); | |||
addComponent(createButton("root_X", "Move to /X", "/X")); | |||
} | |||
private Button createButton(String id, String caption, | |||
final String newUri) { | |||
Button button = new Button(caption, new Button.ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
if (replace.getValue()) { | |||
getPage().replaceState(newUri); | |||
} else { | |||
getPage().pushState(newUri); | |||
} | |||
updateLabel(); | |||
} | |||
}); | |||
button.setId(id); | |||
return button; | |||
} | |||
private void updateLabel() { | |||
URI location = getPage().getLocation(); | |||
locationLabel.setValue("Current Location: " + location.toString()); | |||
} | |||
@Override | |||
public String getTestDescription() { | |||
return "Modern web framework shouldn't force you to use hashbang style urls for deep linking"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return null; | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
package com.vaadin.tests.components.ui; | |||
import com.vaadin.server.ExternalResource; | |||
import com.vaadin.server.Page; | |||
import com.vaadin.server.Page.UriFragmentChangedEvent; | |||
import com.vaadin.server.VaadinRequest; | |||
@@ -7,6 +8,7 @@ import com.vaadin.tests.components.AbstractReindeerTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.Link; | |||
public class UriFragment extends AbstractReindeerTestUI { | |||
@@ -29,6 +31,12 @@ public class UriFragment extends AbstractReindeerTestUI { | |||
addComponent(createButton("test", "Navigate to #test", "test")); | |||
addComponent(createButton("empty", "Navigate to #", "")); | |||
addComponent(createButton("null", "setUriFragment(null)", null)); | |||
Link link = new Link("Navigate to #linktest", | |||
new ExternalResource("#linktest")); | |||
link.setId("link"); | |||
addComponent(link); | |||
} | |||
private Button createButton(String id, String caption, |
@@ -0,0 +1,69 @@ | |||
package com.vaadin.tests.components.ui; | |||
import static org.junit.Assert.assertEquals; | |||
import java.net.URI; | |||
import org.junit.Test; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class PushStateAndReplaceStateTest extends MultiBrowserTest { | |||
@Test | |||
public void testUriFragment() throws Exception { | |||
driver.get(getTestUrl()); | |||
assertUri(getTestUrl()); | |||
hitButton("test"); | |||
assertUri(getTestUrl() + "/test"); | |||
driver.navigate().back(); | |||
driver.findElement(By.className("v-Notification")).getText() | |||
.contains("Popstate event"); | |||
assertUri(getTestUrl()); | |||
hitButton("test"); | |||
URI base = new URI(getTestUrl() + "/test"); | |||
hitButton("X"); | |||
URI current = base.resolve("X"); | |||
driver.findElement(By.xpath("//*[@id = 'replace']/input")).click(); | |||
hitButton("root_X"); | |||
current = current.resolve("/X"); | |||
assertUri(current.toString()); | |||
// Now that last change was with replace state, two back calls should go | |||
// to initial | |||
driver.navigate().back(); | |||
driver.navigate().back(); | |||
assertUri(getTestUrl()); | |||
} | |||
private void assertUri(String uri) { | |||
final String expectedText = "Current Location: " + uri; | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
return expectedText.equals(getLocationLabelValue()); | |||
} | |||
}); | |||
assertEquals(uri, driver.getCurrentUrl()); | |||
} | |||
private String getLocationLabelValue() { | |||
String text = vaadinElementById("locationLabel").getText(); | |||
return text; | |||
} | |||
} |
@@ -7,6 +7,7 @@ import org.openqa.selenium.JavascriptExecutor; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class UriFragmentTest extends MultiBrowserTest { | |||
@@ -42,6 +43,12 @@ public class UriFragmentTest extends MultiBrowserTest { | |||
navigateToNull(); // Setting to null when there is a fragment actually | |||
// sets it to # | |||
assertEquals("Current URI fragment:", getFragmentLabelValue()); | |||
// ensure IE works with new popstate based implementation, see | |||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/3740423/ | |||
driver.findElement(By.xpath("//*[@id = 'link']/a")).click(); | |||
assertFragment("linktest"); | |||
} | |||
private void assertFragment(String fragment) { |
@@ -17,15 +17,26 @@ package com.vaadin.tests.navigator; | |||
import static org.junit.Assert.assertEquals; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class NavigatorViewBlocksBackButtonActionTest extends MultiBrowserTest { | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
// IE web driver fails to read fragment properly, these must be tested | |||
// manually. See | |||
// https://github.com/SeleniumHQ/selenium-google-code-issue-archive/issues/7966 | |||
return getBrowsersExcludingIE(); | |||
} | |||
@Test | |||
public void testIfConfirmBack() { | |||
openTestURL(); |
@@ -18,9 +18,12 @@ package com.vaadin.tests.urifragments; | |||
import static com.vaadin.tests.urifragments.FragmentHandlingAndAsynchUIUpdate.FRAG_NAME_TPL; | |||
import static com.vaadin.tests.urifragments.FragmentHandlingAndAsynchUIUpdate.START_FRAG_ID; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.openqa.selenium.JavascriptExecutor; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.By; | |||
@@ -33,6 +36,14 @@ import com.vaadin.tests.tb3.MultiBrowserTest; | |||
*/ | |||
public class FragmentHandlingAndAsynchUIUpdateTest extends MultiBrowserTest { | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
// IE web driver fails to read fragment properly, these must be tested | |||
// manually. See | |||
// https://github.com/SeleniumHQ/selenium-google-code-issue-archive/issues/7966 | |||
return getBrowsersExcludingIE(); | |||
} | |||
/** | |||
* The case when we successively set 10 fragments, go back 9 times and then | |||
* go forward 9 times |
@@ -15,8 +15,11 @@ | |||
*/ | |||
package com.vaadin.tests.urifragments; | |||
import java.util.List; | |||
import org.junit.Test; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
@@ -29,6 +32,14 @@ import com.vaadin.tests.tb3.MultiBrowserTest; | |||
*/ | |||
public class SettingNullFragmentTest extends MultiBrowserTest { | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
// IE web driver fails to read fragment properly, these must be tested | |||
// manually. See | |||
// https://github.com/SeleniumHQ/selenium-google-code-issue-archive/issues/7966 | |||
return getBrowsersExcludingIE(); | |||
} | |||
@Test | |||
public void testSettingNullURIFragment() throws Exception { | |||
openTestURL(); |