| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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 com.google.gwt.user.client.ui; |
| |
| import com.google.gwt.dom.client.Document; |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.junit.DoNotRunWith; |
| import com.google.gwt.junit.Platform; |
| import com.google.gwt.junit.client.GWTTestCase; |
| import com.google.gwt.user.client.History; |
| import com.google.gwt.user.client.HistoryListener; |
| import com.google.gwt.user.client.Timer; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Tests for the history system. |
| * |
| * TODO: find a way to test unescaping of the initial hash value. |
| */ |
| public class HistoryTest extends GWTTestCase { |
| |
| private static native String getCurrentLocationHash() /*-{ |
| var href = $wnd.location.href; |
| |
| splitted = href.split("#"); |
| if (splitted.length != 2) { |
| return null; |
| } |
| |
| hashPortion = splitted[1]; |
| |
| return hashPortion; |
| }-*/; |
| |
| /* |
| * Copied from UserAgent.gwt.xml and HistoryImplSafari. |
| */ |
| private static native boolean isSafari2() /*-{ |
| var ua = navigator.userAgent; |
| |
| // copied from UserAgent.gwt.xml |
| if (ua.indexOf("webkit") == -1) { |
| return false; |
| } |
| |
| // copied from HistoryImplSafari |
| var exp = / AppleWebKit\/([\d]+)/; |
| var result = exp.exec(ua); |
| if (result) { |
| // The standard history implementation works fine on WebKit >= 522 |
| // (Safari 3 beta). |
| if (parseInt(result[1]) >= 522) { |
| return false; |
| } |
| } |
| |
| // The standard history implementation works just fine on the iPhone, which |
| // unfortunately reports itself as WebKit/420+. |
| if (ua.indexOf('iPhone') != -1) { |
| return false; |
| } |
| |
| return true; |
| }-*/; |
| |
| private HistoryListener historyListener; |
| private Timer timer; |
| |
| @Override |
| public String getModuleName() { |
| return "com.google.gwt.user.User"; |
| } |
| |
| /* Tests against issue #572: Double unescaping of history tokens. */ |
| public void testDoubleEscaping() { |
| final String escToken = "%24%24%24"; |
| |
| delayTestFinish(5000); |
| addHistoryListenerImpl(new HistoryListener() { |
| public void onHistoryChanged(String token) { |
| assertEquals(escToken, token); |
| finishTest(); |
| } |
| }); |
| History.newItem(escToken); |
| } |
| |
| /* |
| * Tests against issue #879: Ensure that empty history tokens do not add |
| * additional characters after the '#' symbol in the URL. |
| */ |
| public void testEmptyHistoryTokens() { |
| delayTestFinish(5000); |
| |
| addHistoryListenerImpl(new HistoryListener() { |
| public void onHistoryChanged(String historyToken) { |
| |
| if (historyToken == null) { |
| fail("historyToken should not be null"); |
| } |
| |
| if (historyToken.equals("foobar")) { |
| History.newItem(""); |
| } else { |
| assertEquals(0, historyToken.length()); |
| finishTest(); |
| } |
| } |
| }); |
| |
| // We must first start out with a non-blank history token. Adding a blank |
| // history token in the initial state will not cause an onHistoryChanged |
| // event to fire. |
| History.newItem("foobar"); |
| } |
| |
| /** |
| * Verify that no events are issued via newItem if there were not reqeuested. |
| */ |
| public void testNoEvents() { |
| delayTestFinish(5000); |
| addHistoryListenerImpl(new HistoryListener() { |
| { |
| timer = new Timer() { |
| public void run() { |
| finishTest(); |
| } |
| }; |
| timer.schedule(500); |
| } |
| |
| public void onHistoryChanged(String historyToken) { |
| fail("onHistoryChanged should not have been called"); |
| } |
| }); |
| History.newItem("testNoEvents", false); |
| } |
| |
| /* |
| * Ensure that non-url-safe strings (such as those containing spaces) are |
| * encoded/decoded correctly, and that programmatic 'back' works. |
| */ |
| @DoNotRunWith(Platform.HtmlUnitUnknown) |
| public void testHistory() { |
| if (isSafari2()) { |
| // History.back() is broken on Safari2, so we skip this test. |
| return; |
| } |
| |
| /* |
| * Sentinel token which should only be seen if tokens are lost during the |
| * rest of the test. Without this, History.back() might send the browser too |
| * far back, i.e. back to before the web app containing our test module. |
| */ |
| History.newItem("if-you-see-this-then-history-went-back-too-far"); |
| |
| delayTestFinish(10000); |
| addHistoryListenerImpl(new HistoryListener() { |
| private int state = 0; |
| |
| public void onHistoryChanged(String historyToken) { |
| switch (state) { |
| case 0: { |
| if (!historyToken.equals("foo bar")) { |
| fail("Expecting token 'foo bar', but got: " + historyToken); |
| } |
| |
| state = 1; |
| History.newItem("baz"); |
| break; |
| } |
| |
| case 1: { |
| if (!historyToken.equals("baz")) { |
| fail("Expecting token 'baz', but got: " + historyToken); |
| } |
| |
| state = 2; |
| History.back(); |
| break; |
| } |
| |
| case 2: { |
| if (!historyToken.equals("foo bar")) { |
| fail("Expecting token 'foo bar' after History.back(), but got: " + historyToken); |
| } |
| finishTest(); |
| break; |
| } |
| } |
| } |
| }); |
| |
| /* |
| * Delay kicking off the history transitions, so the browser has time to process |
| * the initial sentinel token |
| */ |
| new Timer() { |
| @Override |
| public void run() { |
| History.newItem("foo bar"); |
| } |
| }.schedule(5000); |
| } |
| |
| /** |
| * Verify that {@link HistoryListener#onHistoryChanged(String)} is only |
| * called once per {@link History#newItem(String)}. |
| */ |
| public void testHistoryChangedCount() { |
| delayTestFinish(5000); |
| timer = new Timer() { |
| private int count = 0; |
| |
| public void run() { |
| if (count++ == 0) { |
| // verify that duplicates don't issue another event |
| History.newItem("testHistoryChangedCount"); |
| timer.schedule(500); |
| } else { |
| finishTest(); |
| } |
| } |
| }; |
| addHistoryListenerImpl(new HistoryListener() { |
| final ArrayList<Object> counter = new ArrayList<Object>(); |
| |
| public void onHistoryChanged(String historyToken) { |
| counter.add(null); |
| if (counter.size() != 1) { |
| fail("onHistoryChanged called multiple times"); |
| } |
| // wait 500ms to see if we get called multiple times |
| timer.schedule(500); |
| } |
| }); |
| History.newItem("testHistoryChangedCount"); |
| } |
| |
| public void testTokenEscaping() { |
| final String shouldBeEncoded = "% ^[]|\"<>{}\\"; |
| final String shouldBeEncodedAs = "%25%20%5E%5B%5D%7C%22%3C%3E%7B%7D%5C"; |
| |
| delayTestFinish(5000); |
| addHistoryListenerImpl(new HistoryListener() { |
| public void onHistoryChanged(String token) { |
| if (!isSafari2()) { |
| // Safari2 does not update the URL, so we don't verify it |
| assertEquals(shouldBeEncodedAs, getCurrentLocationHash()); |
| } |
| assertEquals(shouldBeEncoded, token); |
| finishTest(); |
| } |
| }); |
| History.newItem(shouldBeEncoded); |
| } |
| |
| /* |
| * HtmlUnit reports: |
| * expected=abc;,/?:@&=+$-_.!~*()ABC123foo |
| * actual =abc;,/?:@&=%20$-_.!~*()ABC123foo |
| */ |
| @DoNotRunWith(Platform.HtmlUnitBug) |
| public void testTokenNonescaping() { |
| final String shouldNotChange = "abc;,/?:@&=+$-_.!~*()ABC123foo"; |
| |
| delayTestFinish(5000); |
| addHistoryListenerImpl(new HistoryListener() { |
| public void onHistoryChanged(String token) { |
| if (!isSafari2()) { |
| // Safari2 does not update the URL, so we don't verify it |
| assertEquals(shouldNotChange, getCurrentLocationHash()); |
| } |
| assertEquals(shouldNotChange, token); |
| finishTest(); |
| } |
| }); |
| History.newItem(shouldNotChange); |
| } |
| |
| /* |
| * Test against issue #2500. IE6 has a bug that causes it to not report any |
| * part of the current fragment after a '?' when read from location.hash; make |
| * sure that on affected browsers, we're not relying on this. |
| */ |
| public void testTokenWithQuestionmark() { |
| delayTestFinish(5000); |
| final String token = "foo?bar"; |
| |
| addHistoryListenerImpl(new HistoryListener() { |
| public void onHistoryChanged(String historyToken) { |
| if (historyToken == null) { |
| fail("historyToken should not be null"); |
| } |
| assertEquals(token, historyToken); |
| finishTest(); |
| } |
| }); |
| History.newItem(token); |
| } |
| |
| /** |
| * Test that using an empty history token works properly. There have been |
| * problems (see issue 2905) with this in the past on Safari. |
| * <p> |
| * Seems like a HtmlUnit bug. Need more investigation. |
| */ |
| @DoNotRunWith(Platform.HtmlUnitBug) |
| public void testEmptyHistoryToken() { |
| final ArrayList<Object> counter = new ArrayList<Object>(); |
| |
| addHistoryListenerImpl(new HistoryListener() { |
| public void onHistoryChanged(String historyToken) { |
| counter.add(new Object()); |
| assertFalse("Browser is borked by empty history token", isBorked()); |
| } |
| }); |
| |
| History.newItem("x"); |
| History.newItem(""); |
| |
| assertEquals("Expected two history events", 2, counter.size()); |
| } |
| |
| // Used by testEmptyHistoryToken() to catch a bizarre failure mode on Safari. |
| private static boolean isBorked() { |
| Element e = Document.get().createDivElement(); |
| e.setInnerHTML("string"); |
| return e.getInnerHTML().length() == 0; |
| } |
| |
| @Override |
| protected void gwtTearDown() throws Exception { |
| if (historyListener != null) { |
| History.removeHistoryListener(historyListener); |
| historyListener = null; |
| } |
| if (timer != null) { |
| timer.cancel(); |
| timer = null; |
| } |
| } |
| |
| private void addHistoryListenerImpl(HistoryListener historyListener) { |
| this.historyListener = historyListener; |
| History.addHistoryListener(historyListener); |
| } |
| } |