pjax.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  1. if ($.support.pjax) {
  2. module("$.pjax", {
  3. setup: function() {
  4. var self = this
  5. stop()
  6. window.iframeLoad = function(frame) {
  7. self.frame = frame
  8. window.iframeLoad = $.noop
  9. start()
  10. }
  11. $("#qunit-fixture").append("<iframe src='home.html'>")
  12. this.iframe = $("iframe")[0]
  13. },
  14. teardown: function() {
  15. delete window.iframeLoad
  16. }
  17. })
  18. asyncTest("pushes new url", 2, function() {
  19. navigate(this.frame)
  20. .pjax({ url: "hello.html", container: "#main", cache: false }, function(frame) {
  21. equal(frame.location.pathname, "/hello.html")
  22. equal(frame.location.search, "")
  23. })
  24. })
  25. asyncTest("replaces container html from response data", 2, function() {
  26. navigate(this.frame)
  27. .pjax({ url: "hello.html", container: "#main" }, function(frame) {
  28. equal(frame.$("#main > p").html().trim(), "Hello!")
  29. equal(frame.$("#main").contents().eq(1).text().trim(), "How's it going?")
  30. })
  31. })
  32. asyncTest("sets title to response title tag", 2, function() {
  33. navigate(this.frame)
  34. .pjax({ url: "hello.html", container: "#main" }, function(frame) {
  35. equal(frame.document.title, "Hello")
  36. equal(frame.$("#main title").length, 0)
  37. })
  38. })
  39. asyncTest("sets title to response nested title tag", 2, function() {
  40. navigate(this.frame)
  41. .pjax({ url: "nested_title.html", container: "#main" }, function(frame) {
  42. equal(frame.document.title, "Hello")
  43. equal(frame.$("#main title").length, 0)
  44. })
  45. })
  46. asyncTest("sets title to response last title tag", 2, function() {
  47. navigate(this.frame)
  48. .pjax({ url: "double_title.html", container: "#main" }, function(frame) {
  49. equal(frame.document.title, "World!")
  50. equal(frame.$("#main title").length, 0)
  51. })
  52. })
  53. asyncTest("scrolls to top of page", 2, function() {
  54. this.frame.scrollTo(0, 100)
  55. equal(this.frame.pageYOffset, 100)
  56. navigate(this.frame)
  57. .pjax({ url: "long.html", container: "#main" }, function(frame) {
  58. equal(frame.pageYOffset, 0)
  59. })
  60. })
  61. asyncTest("scrollTo: false avoids changing current scroll position", 2, function() {
  62. this.frame.scrollTo(0, 100)
  63. equal(this.frame.pageYOffset, 100)
  64. navigate(this.frame)
  65. .pjax({ url: "long.html", scrollTo: false, container: "#main" }, function(frame) {
  66. equal(frame.window.pageYOffset, 100)
  67. })
  68. })
  69. asyncTest("evals scripts", 7, function() {
  70. var externalLoadedCount = 0
  71. this.frame.externalScriptLoaded = function() {
  72. externalLoadedCount++
  73. }
  74. navigate(this.frame)
  75. .pjax({ url: "scripts.html?name=one", container: "#main" }, function(frame) {
  76. deepEqual(frame.evaledInlineLog, ["one"])
  77. equal(externalLoadedCount, 0)
  78. return new PoorMansPromise(function(resolve) {
  79. setTimeout(resolve, 100)
  80. }).then(function() {
  81. equal(externalLoadedCount, 2, "expected scripts to have loaded")
  82. })
  83. })
  84. .pjax({ url: "scripts.html?name=two", container: "#main" }, function(frame) {
  85. deepEqual(frame.evaledInlineLog, ["one", "two"])
  86. })
  87. .back(-1, function(frame) {
  88. deepEqual(frame.evaledInlineLog, ["one", "two", "one"])
  89. })
  90. .back(+1, function(frame) {
  91. deepEqual(frame.evaledInlineLog, ["one", "two", "one", "two"])
  92. return new PoorMansPromise(function(resolve) {
  93. setTimeout(resolve, 100)
  94. }).then(function() {
  95. equal(externalLoadedCount, 2, "expected no extra scripts to load")
  96. })
  97. })
  98. })
  99. asyncTest("url option accepts function", 2, function() {
  100. var numCalls = 0
  101. var url = function() {
  102. numCalls++
  103. return "hello.html"
  104. }
  105. navigate(this.frame)
  106. .pjax({ url: url, container: "#main" }, function(frame) {
  107. equal(frame.$("#main > p").html().trim(), "Hello!")
  108. equal(numCalls, 1)
  109. })
  110. })
  111. asyncTest("sets X-PJAX header on XHR request", 1, function() {
  112. navigate(this.frame)
  113. .pjax({ url: "env.html", container: "#main" }, function(frame) {
  114. var env = JSON.parse(frame.$("#env").text())
  115. equal(env["HTTP_X_PJAX"], "true")
  116. })
  117. })
  118. asyncTest("sets X-PJAX-Container header to container on XHR request", 1, function() {
  119. navigate(this.frame)
  120. .pjax({ url: "env.html", container: "#main" }, function(frame) {
  121. var env = JSON.parse(frame.$("#env").text())
  122. equal(env["HTTP_X_PJAX_CONTAINER"], "#main")
  123. })
  124. })
  125. asyncTest("sets hidden _pjax param on XHR GET request", 1, function() {
  126. navigate(this.frame)
  127. .pjax({ data: undefined, url: "env.html", container: "#main" }, function(frame) {
  128. var env = JSON.parse(frame.$("#env").text())
  129. equal(env["rack.request.query_hash"]["_pjax"], "#main")
  130. })
  131. })
  132. asyncTest("sets hidden _pjax param if array data is supplied", 1, function() {
  133. var data = [{ name: "foo", value: "bar" }]
  134. navigate(this.frame)
  135. .pjax({ data: data, url: "env.html", container: "#main" }, function(frame) {
  136. var env = JSON.parse(frame.$("#env").text())
  137. deepEqual(env["rack.request.query_hash"], {
  138. "_pjax": "#main"
  139. , "foo": "bar"
  140. })
  141. })
  142. })
  143. asyncTest("sets hidden _pjax param if object data is supplied", 1, function() {
  144. var data = { foo: "bar" }
  145. navigate(this.frame)
  146. .pjax({ data: data, url: "env.html", container: "#main" }, function(frame) {
  147. var env = JSON.parse(frame.$("#env").text())
  148. deepEqual(env["rack.request.query_hash"], {
  149. "_pjax": "#main"
  150. , "foo": "bar"
  151. })
  152. })
  153. })
  154. asyncTest("preserves query string on GET request", 3, function() {
  155. navigate(this.frame)
  156. .pjax({ url: "env.html?foo=1&bar=2", container: "#main" }, function(frame) {
  157. equal(frame.location.pathname, "/env.html")
  158. equal(frame.location.search, "?foo=1&bar=2")
  159. var env = JSON.parse(frame.$("#env").text())
  160. deepEqual(env["rack.request.query_hash"], {
  161. "_pjax": "#main"
  162. , "foo": "1"
  163. , "bar": "2"
  164. })
  165. })
  166. })
  167. asyncTest("GET data is appended to query string", 6, function() {
  168. var data = { foo: 1, bar: 2 }
  169. navigate(this.frame)
  170. .pjax({ data: data, url: "env.html", container: "#main" }, function(frame) {
  171. equal(frame.location.pathname, "/env.html")
  172. equal(frame.location.search, "?foo=1&bar=2")
  173. var env = JSON.parse(frame.$("#env").text())
  174. deepEqual(env["rack.request.query_hash"], {
  175. "_pjax": "#main"
  176. , "foo": "1"
  177. , "bar": "2"
  178. })
  179. })
  180. var frame = this.frame
  181. setTimeout(function() {
  182. // URL is set immediately
  183. equal(frame.location.pathname, "/env.html")
  184. equal(frame.location.search, "?foo=1&bar=2")
  185. equal(frame.location.href.indexOf("#"), -1)
  186. }, 0)
  187. })
  188. asyncTest("GET data is merged into query string", 6, function() {
  189. var data = { bar: 2 }
  190. navigate(this.frame)
  191. .pjax({ data: data, url: "env.html?foo=1", container: "#main" }, function(frame) {
  192. equal(frame.location.pathname, "/env.html")
  193. equal(frame.location.search, "?foo=1&bar=2")
  194. var env = JSON.parse(frame.$("#env").text())
  195. deepEqual(env["rack.request.query_hash"], {
  196. "_pjax": "#main"
  197. , "foo": "1"
  198. , "bar": "2"
  199. })
  200. })
  201. var frame = this.frame
  202. setTimeout(function() {
  203. // URL is set immediately
  204. equal(frame.location.pathname, "/env.html")
  205. equal(frame.location.search, "?foo=1&bar=2")
  206. equal(frame.location.href.indexOf("#"), -1)
  207. }, 0)
  208. })
  209. asyncTest("mixed containers", 6, function() {
  210. navigate(this.frame)
  211. .pjax({ url: "fragment.html", container: "#main" })
  212. .pjax({ url: "aliens.html", container: "#foo" }, function(frame) {
  213. equal(frame.$("#main > #foo > ul > li").last().text(), "aliens")
  214. })
  215. .back(-1, function(frame) {
  216. equal(frame.$("#main > #foo").text().trim(), "Foo")
  217. })
  218. .pjax({ url: "env.html", replace: true, fragment: "#env", container: "#bar" }, function(frame) {
  219. // This replaceState shouldn't affect restoring other popstates
  220. equal(frame.$("#main > #foo").text().trim(), "Foo")
  221. ok(JSON.parse(frame.$("#bar").text()))
  222. })
  223. .back(-1, function(frame) {
  224. equal(frame.$("#main > ul > li").first().text(), "home")
  225. })
  226. .back(+1)
  227. .back(+1, function(frame) {
  228. equal(frame.$("#main > #foo > ul > li").last().text(), "aliens")
  229. })
  230. })
  231. asyncTest("only fragment is inserted", 2, function() {
  232. navigate(this.frame)
  233. .pjax({ url: "hello.html?layout=true", fragment: "#main", container: "#main" }, function(frame) {
  234. equal(frame.$("#main > p").html().trim(), "Hello!")
  235. equal(frame.document.title, "Hello")
  236. })
  237. })
  238. asyncTest("use body as fragment", 2, function() {
  239. navigate(this.frame)
  240. .pjax({ url: "hello.html?layout=true", fragment: "body", container: "body" }, function(frame) {
  241. equal(frame.$("body > #main > p").html().trim(), "Hello!")
  242. equal(frame.document.title, "Hello")
  243. })
  244. })
  245. asyncTest("fragment sets title to response title attr", 2, function() {
  246. navigate(this.frame)
  247. .pjax({ url: "fragment.html", fragment: "#foo", container: "#main" }, function(frame) {
  248. equal(frame.$("#main > p").html(), "Foo")
  249. equal(frame.document.title, "Foo")
  250. })
  251. })
  252. asyncTest("fragment sets title to response data-title attr", 2, function() {
  253. navigate(this.frame)
  254. .pjax({ url: "fragment.html", fragment: "#bar", container: "#main" }, function(frame) {
  255. equal(frame.$("#main > p").html(), "Bar")
  256. equal(frame.document.title, "Bar")
  257. })
  258. })
  259. asyncTest("missing fragment falls back to full load", 2, function() {
  260. var iframe = this.iframe
  261. navigate(this.frame)
  262. .pjax({ url: "hello.html?layout=true", fragment: "#missing", container: "#main" }, function() {
  263. return new PoorMansPromise(function(resolve) {
  264. iframe.onload = function() { resolve(this.contentWindow) }
  265. }).then(function(frame) {
  266. equal(frame.$("#main p").html(), "Hello!")
  267. equal(frame.location.pathname, "/hello.html")
  268. })
  269. })
  270. })
  271. asyncTest("missing data falls back to full load", 2, function() {
  272. var iframe = this.iframe
  273. navigate(this.frame)
  274. .pjax({ url: "empty.html", container: "#main" }, function() {
  275. return new PoorMansPromise(function(resolve) {
  276. iframe.onload = function() { resolve(this.contentWindow) }
  277. }).then(function(frame) {
  278. equal(frame.$("#main").html().trim(), "")
  279. equal(frame.location.pathname, "/empty.html")
  280. })
  281. })
  282. })
  283. asyncTest("full html page falls back to full load", 2, function() {
  284. var iframe = this.iframe
  285. navigate(this.frame)
  286. .pjax({ url: "hello.html?layout=true", container: "#main" }, function() {
  287. return new PoorMansPromise(function(resolve) {
  288. iframe.onload = function() { resolve(this.contentWindow) }
  289. }).then(function(frame) {
  290. equal(frame.$("#main p").html(), "Hello!")
  291. equal(frame.location.pathname, "/hello.html")
  292. })
  293. })
  294. })
  295. asyncTest("header version mismatch does a full load", 2, function() {
  296. var iframe = this.iframe
  297. this.frame.$.pjax.defaults.version = "v2"
  298. navigate(this.frame)
  299. .pjax({ url: "hello.html", container: "#main" }, function() {
  300. return new PoorMansPromise(function(resolve) {
  301. iframe.onload = function() { resolve(this.contentWindow) }
  302. }).then(function(frame) {
  303. equal(frame.$("#main p").html(), "Hello!")
  304. equal(frame.location.pathname, "/hello.html")
  305. })
  306. })
  307. })
  308. asyncTest("triggers pjax:start/end events from container", 12, function() {
  309. var eventLog = []
  310. var container = this.frame.document.getElementById("main")
  311. ok(container)
  312. this.frame.$(container).on("pjax:start pjax:end", function(event, xhr, options) {
  313. eventLog.push(arguments)
  314. })
  315. navigate(this.frame)
  316. .pjax({ url: "hello.html", container: "#main" }, function(frame) {
  317. equal(eventLog.length, 2)
  318. $.each(["pjax:start", "pjax:end"], function(i, expectedType) {
  319. (function(event, xhr, options){
  320. equal(event.type, expectedType)
  321. equal(event.target, container)
  322. equal(event.relatedTarget, null)
  323. equal(typeof xhr.abort, "function")
  324. equal(options.url, "hello.html")
  325. }).apply(this, eventLog[i])
  326. })
  327. })
  328. })
  329. asyncTest("events preserve explicit target as relatedTarget", 7, function() {
  330. var eventLog = []
  331. var container = this.frame.document.getElementById("main")
  332. ok(container)
  333. this.frame.$(container).on("pjax:start pjax:end", function(event, xhr, options) {
  334. eventLog.push(event)
  335. })
  336. navigate(this.frame)
  337. .pjax({ url: "hello.html", target: container, container: "#main" }, function(frame) {
  338. $.each(["pjax:start", "pjax:end"], function(i, expectedType) {
  339. var event = eventLog[i]
  340. equal(event.type, expectedType)
  341. equal(event.target, container)
  342. equal(event.relatedTarget, container)
  343. })
  344. })
  345. })
  346. asyncTest("stopping pjax:beforeSend prevents the request", 6, function() {
  347. var eventLog = []
  348. var container = this.frame.document.getElementById("main")
  349. ok(container)
  350. this.frame.$(container).on("pjax:beforeSend", function(event, xhr, settings) {
  351. eventLog.push(arguments)
  352. return false
  353. })
  354. navigate(this.frame)
  355. .pjax({ url: "hello.html", container: "#main" }, function(frame) {
  356. ok(false)
  357. })
  358. setTimeout(function() {
  359. equal(eventLog.length, 1)
  360. ;(function(event, xhr, settings){
  361. equal(event.type, "pjax:beforeSend")
  362. equal(event.target, container)
  363. equal(typeof xhr.abort, "function")
  364. equal(settings.dataType, "html")
  365. }).apply(this, eventLog[0])
  366. start()
  367. }, 100)
  368. })
  369. asyncTest("triggers pjax:beforeReplace event from container", 9, function() {
  370. var eventLog = []
  371. var container = this.frame.document.getElementById("main")
  372. ok(container)
  373. this.frame.$(container).on("pjax:beforeReplace", function(event, contents, options) {
  374. eventLog.push(arguments)
  375. equal(container.textContent.indexOf("Hello!"), -1)
  376. })
  377. var urlPrefix = location.protocol + "//" + location.host
  378. navigate(this.frame)
  379. .pjax({ url: "hello.html", container: "#main" }, function(frame) {
  380. equal(eventLog.length, 1)
  381. ;(function(event, contents, options){
  382. equal(event.target, container)
  383. equal(event.state.url, urlPrefix + "/hello.html")
  384. equal(event.previousState.url, urlPrefix + "/home.html")
  385. equal(contents[0].nodeName, "P")
  386. // FIXME: Should this be absolute URL?
  387. equal(options.url, "hello.html")
  388. }).apply(this, eventLog[0])
  389. ok(container.textContent.indexOf("Hello!") >= 0)
  390. })
  391. })
  392. asyncTest("triggers pjax:success/complete events from container", function() {
  393. var eventLog = []
  394. var container = this.frame.document.getElementById("main")
  395. ok(container)
  396. this.frame.$(container).on("pjax:success pjax:complete", function(event) {
  397. eventLog.push(arguments)
  398. })
  399. navigate(this.frame)
  400. .pjax({ url: "hello.html", container: "#main" }, function(frame) {
  401. equal(eventLog.length, 2)
  402. ;(function(event, data, status, xhr, options){
  403. equal(event.type, "pjax:success")
  404. equal(event.target, container)
  405. ok(data.indexOf("<p>Hello!</p>") >= 0)
  406. equal(status, "success")
  407. equal(typeof xhr.abort, "function")
  408. equal(options.url, "hello.html")
  409. }).apply(this, eventLog[0])
  410. ;(function(event, xhr, status, options){
  411. equal(event.type, "pjax:complete")
  412. equal(event.target, container)
  413. equal(typeof xhr.abort, "function")
  414. equal(status, "success")
  415. equal(options.url, "hello.html")
  416. }).apply(this, eventLog[1])
  417. })
  418. })
  419. asyncTest("triggers pjax:error event from container", function() {
  420. var frame = this.frame
  421. frame.$("#main").on("pjax:error", function(event, xhr, status, error, options) {
  422. ok(event)
  423. equal(xhr.status, 500)
  424. equal(status, 'error')
  425. equal(error.trim(), 'Internal Server Error')
  426. equal(options.url, "boom.html")
  427. start()
  428. })
  429. frame.$.pjax({
  430. url: "boom.html",
  431. container: "#main"
  432. })
  433. })
  434. asyncTest("stopping pjax:error disables default behavior", function() {
  435. var frame = this.frame
  436. frame.$("#main").on("pjax:error", function(event, xhr) {
  437. ok(true)
  438. setTimeout(function() {
  439. xhr.abort()
  440. start()
  441. }, 0)
  442. return false
  443. })
  444. this.iframe.onload = function() { ok(false) }
  445. frame.$.pjax({
  446. url: "boom.html",
  447. container: "#main"
  448. })
  449. })
  450. asyncTest("loads fallback if timeout event isn't handled", function() {
  451. var frame = this.frame
  452. frame.$.pjax({
  453. url: "timeout.html#hello",
  454. container: "#main"
  455. })
  456. equal(frame.location.pathname, "/timeout.html")
  457. equal(frame.location.hash, "#hello")
  458. this.iframe.onload = function() {
  459. equal(frame.$("#main p").html(), "SLOW DOWN!")
  460. equal(frame.location.pathname, "/timeout.html")
  461. equal(frame.location.hash, "#hello")
  462. start()
  463. }
  464. })
  465. asyncTest("stopping pjax:timeout disables default behavior", function() {
  466. var frame = this.frame
  467. frame.$("#main").on("pjax:timeout", function(event, xhr) {
  468. ok(true)
  469. setTimeout(function() {
  470. xhr.abort()
  471. start()
  472. }, 0)
  473. return false
  474. })
  475. this.iframe.onload = function() { ok(false) }
  476. frame.$.pjax({
  477. url: "timeout.html",
  478. container: "#main"
  479. })
  480. })
  481. asyncTest("POST never times out", function() {
  482. var frame = this.frame
  483. frame.$("#main").on("pjax:complete", function() {
  484. equal(frame.$("#main p").html(), "SLOW DOWN!")
  485. equal(frame.location.pathname, "/timeout.html")
  486. start()
  487. })
  488. frame.$("#main").on("pjax:timeout", function(event, xhr) {
  489. ok(false)
  490. })
  491. this.iframe.onload = function() { ok(false) }
  492. frame.$.pjax({
  493. type: 'POST',
  494. url: "timeout.html",
  495. container: "#main"
  496. })
  497. })
  498. asyncTest("500 loads fallback", function() {
  499. var frame = this.frame
  500. frame.$.pjax({
  501. url: "boom.html",
  502. container: "#main"
  503. })
  504. this.iframe.onload = function() {
  505. equal(frame.$("#main p").html(), "500")
  506. equal(frame.location.pathname, "/boom.html")
  507. start()
  508. }
  509. })
  510. asyncTest("POST 500 never loads fallback", function() {
  511. var frame = this.frame
  512. frame.$("#main").on("pjax:complete", function() {
  513. equal(frame.location.pathname, "/boom.html")
  514. start()
  515. })
  516. frame.$("#main").on("pjax:error", function(event, xhr) {
  517. ok(true)
  518. })
  519. frame.$("#main").on("pjax:timeout", function(event, xhr) {
  520. ok(false)
  521. })
  522. this.iframe.onload = function() { ok(false) }
  523. frame.$.pjax({
  524. type: 'POST',
  525. url: "boom.html",
  526. container: "#main"
  527. })
  528. })
  529. function goBack(frame, callback) {
  530. setTimeout(function() {
  531. frame.$("#main").one("pjax:end", callback)
  532. frame.history.back()
  533. }, 0)
  534. }
  535. asyncTest("clicking back while loading cancels XHR", function() {
  536. var frame = this.frame
  537. frame.$('#main').on('pjax:timeout', function(event) {
  538. event.preventDefault()
  539. })
  540. frame.$("#main").one('pjax:send', function() {
  541. // Check that our request is aborted (need to check
  542. // how robust this is across browsers)
  543. frame.$("#main").one('pjax:complete', function(e, xhr, textStatus) {
  544. equal(xhr.status, 0)
  545. equal(textStatus, 'abort')
  546. })
  547. setTimeout(function() {
  548. frame.history.back()
  549. }, 250)
  550. // Make sure the URL and content remain the same after the
  551. // XHR would have arrived (delay on timeout.html is 1s)
  552. setTimeout(function() {
  553. var afterBackLocation = frame.location.pathname
  554. var afterBackTitle = frame.document.title
  555. setTimeout(function() {
  556. equal(frame.location.pathname, afterBackLocation)
  557. equal(frame.document.title, afterBackTitle)
  558. start()
  559. }, 1000)
  560. }, 500)
  561. })
  562. frame.$.pjax({
  563. url: "timeout.html",
  564. container: "#main"
  565. })
  566. })
  567. asyncTest("popstate going back/forward in history", 14, function() {
  568. var eventLog = []
  569. var container = this.frame.document.getElementById("main")
  570. ok(container)
  571. this.frame.$(container).on("pjax:popstate", function(event) {
  572. eventLog.push(event)
  573. })
  574. navigate(this.frame)
  575. .pjax({ url: "hello.html", container: "#main" }, function(frame) {
  576. equal(frame.location.pathname, "/hello.html")
  577. equal(frame.document.title, "Hello")
  578. equal(eventLog.length, 0)
  579. })
  580. .back(-1, function(frame) {
  581. equal(frame.location.pathname, "/home.html")
  582. equal(frame.document.title, "Home")
  583. equal(eventLog.length, 1)
  584. equal(eventLog[0].direction, "back")
  585. equal(eventLog[0].state.container, "#main")
  586. })
  587. .back(+1, function(frame) {
  588. equal(frame.location.pathname, "/hello.html")
  589. equal(frame.document.title, "Hello")
  590. equal(eventLog.length, 2)
  591. equal(eventLog[1].direction, "forward")
  592. equal(eventLog[1].state.container, "#main")
  593. })
  594. })
  595. asyncTest("popstate restores original scroll position", 2, function() {
  596. this.frame.scrollTo(0, 100)
  597. equal(this.frame.pageYOffset, 100)
  598. navigate(this.frame)
  599. .pjax({ url: "long.html", container: "#main" }, function(frame) {
  600. equal(frame.pageYOffset, 0)
  601. })
  602. .back(-1, function(frame) {
  603. // FIXME: Seems like this functionality is natively broken in PhantomJS and Safari
  604. // equal(frame.pageYOffset, 100)
  605. })
  606. })
  607. asyncTest("popstate triggers pjax:beforeReplace event", 10, function() {
  608. var eventLog = []
  609. var container = this.frame.document.getElementById("main")
  610. ok(container)
  611. this.frame.$(container).on("pjax:beforeReplace", function(event, contents, options) {
  612. eventLog.push(arguments)
  613. if (eventLog.length == 2) {
  614. equal(container.textContent.indexOf("home"), -1)
  615. }
  616. })
  617. var urlPrefix = location.protocol + "//" + location.host
  618. navigate(this.frame)
  619. .pjax({ url: "hello.html", container: "#main" })
  620. .back(-1, function(frame) {
  621. equal(eventLog.length, 2)
  622. // FIXME: First "pjax:beforeReplace" event has relative URL,
  623. // while the 2nd (triggered by popstate) has absolute URL.
  624. equal(eventLog[0][2].url, "hello.html")
  625. ;(function(event, contents, options){
  626. equal(event.target, container)
  627. equal(event.previousState.url, urlPrefix + "/hello.html")
  628. equal(event.state.url, urlPrefix + "/home.html")
  629. equal(contents[1].nodeName, "UL")
  630. equal(options.url, urlPrefix + "/home.html")
  631. }).apply(this, eventLog[1])
  632. ok(container.textContent.indexOf("home") >= 0)
  633. })
  634. })
  635. asyncTest("no initial pjax:popstate event", function() {
  636. var frame = this.frame
  637. var count = 0
  638. window.iframeLoad = function() {
  639. count++
  640. if (count == 1) {
  641. equal(frame.location.pathname, "/home.html")
  642. frame.location.pathname = "/hello.html"
  643. } else if (count == 2) {
  644. equal(frame.location.pathname, "/hello.html")
  645. frame.$.pjax({url: "env.html", container: "#main"})
  646. } else if (count == 3) {
  647. equal(frame.location.pathname, "/env.html")
  648. frame.history.back()
  649. } else if (count == 4) {
  650. equal(frame.location.pathname, "/hello.html")
  651. frame.history.back()
  652. } else if (count == 5) {
  653. equal(frame.location.pathname, "/home.html")
  654. frame.history.forward()
  655. } else if (count == 6) {
  656. frame.$('#main').on('pjax:popstate', function(event) {
  657. if (count == 6) {
  658. // Should skip pjax:popstate since there's no initial pjax.state
  659. ok(event.state.url.match("/hello.html"), event.state.url)
  660. ok(false)
  661. } else if (count == 7) {
  662. ok(event.state.url.match("/env.html"), event.state.url)
  663. ok(true)
  664. }
  665. })
  666. frame.$(frame.window).on('popstate', function() {
  667. if (count == 6) {
  668. count++
  669. frame.history.forward()
  670. }
  671. })
  672. // Browsers that don't fire initial "popstate" should just resume
  673. setTimeout(function() {
  674. start()
  675. }, 100)
  676. }
  677. }
  678. window.iframeLoad()
  679. })
  680. asyncTest("hitting the back button obeys maxCacheLength", function() {
  681. var frame = this.frame
  682. var count = 0
  683. var didHitServer
  684. // Reduce the maxCacheLength for this spec to make it easier to test.
  685. frame.$.pjax.defaults.maxCacheLength = 1
  686. // This event will fire only when we request a page from the server, so we
  687. // can use it to detect a cache miss.
  688. frame.$("#main").on("pjax:beforeSend", function() {
  689. didHitServer = true
  690. })
  691. frame.$("#main").on("pjax:end", function() {
  692. count++
  693. // First, navigate twice.
  694. if (count == 1) {
  695. frame.$.pjax({url: "env.html", container: "#main"})
  696. } else if (count == 2) {
  697. frame.$.pjax({url: "hello.html", container: "#main"})
  698. } else if (count == 3) {
  699. // There should now be one item in the back cache.
  700. didHitServer = false
  701. frame.history.back()
  702. } else if (count == 4) {
  703. equal(frame.location.pathname, "/env.html", "Went backward")
  704. equal(didHitServer, false, "Hit cache")
  705. frame.history.back()
  706. } else if (count == 5) {
  707. equal(frame.location.pathname, "/hello.html", "Went backward")
  708. equal(didHitServer, true, "Hit server")
  709. start()
  710. }
  711. })
  712. frame.$.pjax({url: "hello.html", container: "#main"})
  713. })
  714. asyncTest("hitting the forward button obeys maxCacheLength", function() {
  715. var frame = this.frame
  716. var count = 0
  717. var didHitServer
  718. // Reduce the maxCacheLength for this spec to make it easier to test.
  719. frame.$.pjax.defaults.maxCacheLength = 1
  720. // This event will fire only when we request a page from the server, so we
  721. // can use it to detect a cache miss.
  722. frame.$("#main").on("pjax:beforeSend", function() {
  723. didHitServer = true
  724. })
  725. frame.$("#main").on("pjax:end", function() {
  726. count++
  727. if (count == 1) {
  728. frame.$.pjax({url: "env.html", container: "#main"})
  729. } else if (count == 2) {
  730. frame.$.pjax({url: "hello.html", container: "#main"})
  731. } else if (count == 3) {
  732. frame.history.back()
  733. } else if (count == 4) {
  734. frame.history.back()
  735. } else if (count == 5) {
  736. // There should now be one item in the forward cache.
  737. didHitServer = false
  738. frame.history.forward()
  739. } else if (count == 6) {
  740. equal(frame.location.pathname, "/env.html", "Went forward")
  741. equal(didHitServer, false, "Hit cache")
  742. frame.history.forward()
  743. } else if (count == 7) {
  744. equal(frame.location.pathname, "/hello.html", "Went forward")
  745. equal(didHitServer, true, "Hit server")
  746. start()
  747. }
  748. })
  749. frame.$.pjax({url: "hello.html", container: "#main"})
  750. })
  751. asyncTest("setting maxCacheLength to 0 disables caching", function() {
  752. var frame = this.frame
  753. var count = 0
  754. var didHitServer
  755. // Set maxCacheLength to 0 to disable caching completely.
  756. frame.$.pjax.defaults.maxCacheLength = 0
  757. // This event will fire only when we request a page from the server, so we
  758. // can use it to detect a cache miss.
  759. frame.$("#main").on("pjax:beforeSend", function() {
  760. didHitServer = true
  761. })
  762. frame.$("#main").on("pjax:end", function() {
  763. count++
  764. if (count == 1) {
  765. didHitServer = false
  766. frame.$.pjax({url: "env.html", container: "#main"})
  767. } else if (count == 2) {
  768. equal(frame.location.pathname, "/env.html", "Navigated to a new page")
  769. equal(didHitServer, true, "Hit server")
  770. didHitServer = false
  771. frame.history.back()
  772. } else if (count == 3) {
  773. equal(frame.location.pathname, "/hello.html", "Went backward")
  774. equal(didHitServer, true, "Hit server")
  775. didHitServer = false
  776. frame.history.forward()
  777. } else if (count == 4) {
  778. equal(frame.location.pathname, "/env.html", "Went forward")
  779. equal(didHitServer, true, "Hit server")
  780. start()
  781. }
  782. })
  783. frame.$.pjax({url: "hello.html", container: "#main"})
  784. })
  785. asyncTest("lazily sets initial $.pjax.state", function() {
  786. var frame = this.frame
  787. equal(frame.$.pjax.state, null)
  788. frame.$('#main').on("pjax:success", function() {
  789. start()
  790. })
  791. frame.$.pjax({
  792. url: "hello.html",
  793. container: "#main"
  794. })
  795. var initialState = frame.$.pjax.state
  796. ok(initialState.id)
  797. equal(initialState.url, "http://" + frame.location.host + "/home.html")
  798. equal(initialState.container, "#main")
  799. })
  800. asyncTest("updates $.pjax.state to new page", function() {
  801. var frame = this.frame
  802. frame.$('#main').on("pjax:success", function() {
  803. var state = frame.$.pjax.state
  804. ok(state.id)
  805. equal(state.url, "http://" + frame.location.host + "/hello.html#new")
  806. equal(state.container, "#main")
  807. start()
  808. })
  809. frame.$.pjax({
  810. url: "hello.html#new",
  811. container: "#main"
  812. })
  813. var initialState = frame.$.pjax.state
  814. })
  815. asyncTest("new id is generated for new pages", function() {
  816. var frame = this.frame
  817. var oldId
  818. frame.$('#main').on("pjax:success", function() {
  819. ok(frame.$.pjax.state.id)
  820. notEqual(oldId, frame.$.pjax.state.id)
  821. start()
  822. })
  823. frame.$.pjax({
  824. url: "hello.html",
  825. container: "#main"
  826. })
  827. ok(frame.$.pjax.state.id)
  828. oldId = frame.$.pjax.state.id
  829. })
  830. asyncTest("id is the same going back", function() {
  831. var frame = this.frame
  832. var oldId
  833. equal(frame.location.pathname, "/home.html")
  834. frame.$('#main').on("pjax:complete", function() {
  835. ok(frame.$.pjax.state.id)
  836. notEqual(oldId, frame.$.pjax.state.id)
  837. ok(frame.history.length > 1)
  838. goBack(frame, function() {
  839. ok(frame.$.pjax.state.id)
  840. equal(oldId, frame.$.pjax.state.id)
  841. start()
  842. })
  843. })
  844. frame.$.pjax({
  845. url: "hello.html",
  846. container: "#main"
  847. })
  848. ok(frame.$.pjax.state.id)
  849. oldId = frame.$.pjax.state.id
  850. })
  851. asyncTest("handles going back to pjaxed state after reloading a fragment navigation", function() {
  852. var iframe = this.iframe
  853. var frame = this.frame
  854. var supportsHistoryState = 'state' in window.history
  855. // Get some pjax state in the history.
  856. frame.$.pjax({
  857. url: "hello.html",
  858. container: "#main",
  859. })
  860. frame.$("#main").on("pjax:complete", function() {
  861. var state = frame.history.state
  862. ok(frame.$.pjax.state)
  863. if (supportsHistoryState)
  864. ok(frame.history.state)
  865. // Navigate to a fragment, which will result in a new history entry with
  866. // no state object. $.pjax.state remains unchanged however.
  867. iframe.src = frame.location.href + '#foo'
  868. ok(frame.$.pjax.state)
  869. if (supportsHistoryState)
  870. ok(!frame.history.state)
  871. // Reload the frame. This will clear out $.pjax.state.
  872. frame.location.reload()
  873. $(iframe).one("load", function() {
  874. ok(!frame.$.pjax.state)
  875. if (supportsHistoryState)
  876. ok(!frame.history.state)
  877. // Go back to #main. We'll get a popstate event with a pjax state
  878. // object attached from the initial pjax navigation, even though
  879. // $.pjax.state is null.
  880. window.iframeLoad = function() {
  881. ok(frame.$.pjax.state)
  882. if (supportsHistoryState) {
  883. ok(frame.history.state)
  884. equal(frame.$.pjax.state.id, state.id)
  885. }
  886. start()
  887. }
  888. frame.history.back()
  889. })
  890. })
  891. })
  892. asyncTest("handles going back to page after loading an error page", function() {
  893. var frame = this.frame
  894. var iframe = this.iframe
  895. equal(frame.location.pathname, "/home.html")
  896. equal(frame.document.title, "Home")
  897. $(iframe).one("load", function() {
  898. window.iframeLoad = function() {
  899. equal(frame.location.pathname, "/home.html")
  900. equal(frame.document.title, "Home")
  901. start()
  902. }
  903. frame.history.back()
  904. })
  905. frame.$.pjax({
  906. url: "boom_sans_pjax.html",
  907. container: "#main"
  908. })
  909. })
  910. asyncTest("copes with ampersands when pushing urls", 2, function() {
  911. navigate(this.frame)
  912. .pjax({ url: "/some-&-path/hello.html", container: "#main" }, function(frame) {
  913. equal(frame.location.pathname, "/some-&-path/hello.html")
  914. equal(frame.location.search, "")
  915. })
  916. })
  917. }