element-resize-detector_test.js 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130
  1. /* global describe:false, it:false, beforeEach:false, expect:false, elementResizeDetectorMaker:false, _:false, $:false, jasmine:false */
  2. "use strict";
  3. function ensureMapEqual(before, after, ignore) {
  4. var beforeKeys = _.keys(before);
  5. var afterKeys = _.keys(after);
  6. var unionKeys = _.union(beforeKeys, afterKeys);
  7. var diffValueKeys = _.filter(unionKeys, function (key) {
  8. var beforeValue = before[key];
  9. var afterValue = after[key];
  10. return !ignore(key, beforeValue, afterValue) && beforeValue !== afterValue;
  11. });
  12. if (diffValueKeys.length) {
  13. var beforeDiffObject = {};
  14. var afterDiffObject = {};
  15. _.forEach(diffValueKeys, function (key) {
  16. beforeDiffObject[key] = before[key];
  17. afterDiffObject[key] = after[key];
  18. });
  19. expect(afterDiffObject).toEqual(beforeDiffObject);
  20. }
  21. }
  22. function getStyle(element) {
  23. function clone(styleObject) {
  24. var clonedTarget = {};
  25. _.forEach(styleObject.cssText.split(";").slice(0, -1), function (declaration) {
  26. var colonPos = declaration.indexOf(":");
  27. var attr = declaration.slice(0, colonPos).trim();
  28. if (attr.indexOf("-") === -1) { // Remove attributes like "background-image", leaving "backgroundImage"
  29. clonedTarget[attr] = declaration.slice(colonPos + 2);
  30. }
  31. });
  32. return clonedTarget;
  33. }
  34. var style = getComputedStyle(element);
  35. return clone(style);
  36. }
  37. function getAttributes(element) {
  38. var attrs = {};
  39. _.forEach(element.attributes, function (attr) {
  40. attrs[attr.nodeName] = attr.value;
  41. });
  42. return attrs;
  43. }
  44. var ensureAttributes = ensureMapEqual;
  45. var reporter = {
  46. log: function () {
  47. throw new Error("Reporter.log should not be called");
  48. },
  49. warn: function () {
  50. throw new Error("Reporter.warn should not be called");
  51. },
  52. error: function () {
  53. throw new Error("Reporter.error should not be called");
  54. }
  55. };
  56. $("body").prepend("<div id=fixtures></div>");
  57. function listenToTest(strategy) {
  58. describe("[" + strategy + "] listenTo", function () {
  59. it("should be able to attach a listener to an element", function (done) {
  60. var erd = elementResizeDetectorMaker({
  61. callOnAdd: false,
  62. reporter: reporter,
  63. strategy: strategy
  64. });
  65. var listener = jasmine.createSpy("listener");
  66. erd.listenTo($("#test")[0], listener);
  67. setTimeout(function () {
  68. $("#test").width(300);
  69. }, 200);
  70. setTimeout(function () {
  71. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  72. done();
  73. }, 400);
  74. });
  75. it("should throw on invalid parameters", function () {
  76. var erd = elementResizeDetectorMaker({
  77. callOnAdd: false,
  78. reporter: reporter,
  79. strategy: strategy
  80. });
  81. expect(erd.listenTo).toThrow();
  82. expect(_.partial(erd.listenTo, $("#test")[0])).toThrow();
  83. });
  84. describe("option.onReady", function () {
  85. it("should be called when installing a listener to an element", function (done) {
  86. var erd = elementResizeDetectorMaker({
  87. callOnAdd: false,
  88. reporter: reporter,
  89. strategy: strategy
  90. });
  91. var listener = jasmine.createSpy("listener");
  92. erd.listenTo({
  93. onReady: function () {
  94. $("#test").width(200);
  95. setTimeout(function () {
  96. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  97. done();
  98. }, 200);
  99. }
  100. }, $("#test")[0], listener);
  101. });
  102. it("should be called when all elements are ready", function (done) {
  103. var erd = elementResizeDetectorMaker({
  104. callOnAdd: false,
  105. reporter: reporter,
  106. strategy: strategy
  107. });
  108. var listener = jasmine.createSpy("listener");
  109. erd.listenTo({
  110. onReady: function () {
  111. $("#test").width(200);
  112. $("#test2").width(300);
  113. setTimeout(function () {
  114. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  115. expect(listener).toHaveBeenCalledWith($("#test2")[0]);
  116. done();
  117. }, 200);
  118. }
  119. }, $("#test, #test2"), listener);
  120. });
  121. it("should be able to handle listeners for the same element but different calls", function (done) {
  122. var erd = elementResizeDetectorMaker({
  123. callOnAdd: false,
  124. reporter: reporter,
  125. strategy: strategy
  126. });
  127. var onReady1 = jasmine.createSpy("listener");
  128. var onReady2 = jasmine.createSpy("listener");
  129. erd.listenTo({
  130. onReady: onReady1
  131. }, $("#test"), function noop() {
  132. });
  133. erd.listenTo({
  134. onReady: onReady2
  135. }, $("#test"), function noop() {
  136. });
  137. setTimeout(function () {
  138. expect(onReady1.calls.count()).toBe(1);
  139. expect(onReady2.calls.count()).toBe(1);
  140. done();
  141. }, 300);
  142. });
  143. it("should be able to handle when elements occur multiple times in the same call (and other calls)", function (done) {
  144. var erd = elementResizeDetectorMaker({
  145. callOnAdd: false,
  146. reporter: reporter,
  147. strategy: strategy
  148. });
  149. var onReady1 = jasmine.createSpy("listener");
  150. var onReady2 = jasmine.createSpy("listener");
  151. erd.listenTo({
  152. onReady: onReady1
  153. }, [$("#test")[0], $("#test")[0]], function noop() {
  154. });
  155. erd.listenTo({
  156. onReady: onReady2
  157. }, $("#test"), function noop() {
  158. });
  159. setTimeout(function () {
  160. expect(onReady1.calls.count()).toBe(1);
  161. expect(onReady2.calls.count()).toBe(1);
  162. done();
  163. }, 300);
  164. });
  165. });
  166. it("should be able to attach multiple listeners to an element", function (done) {
  167. var erd = elementResizeDetectorMaker({
  168. callOnAdd: false,
  169. reporter: reporter,
  170. strategy: strategy
  171. });
  172. var listener1 = jasmine.createSpy("listener1");
  173. var listener2 = jasmine.createSpy("listener2");
  174. erd.listenTo($("#test")[0], listener1);
  175. erd.listenTo($("#test")[0], listener2);
  176. setTimeout(function () {
  177. $("#test").width(300);
  178. }, 200);
  179. setTimeout(function () {
  180. expect(listener1).toHaveBeenCalledWith($("#test")[0]);
  181. expect(listener2).toHaveBeenCalledWith($("#test")[0]);
  182. done();
  183. }, 400);
  184. });
  185. it("should be able to attach a listener to an element multiple times within the same call", function (done) {
  186. var erd = elementResizeDetectorMaker({
  187. callOnAdd: false,
  188. reporter: reporter,
  189. strategy: strategy
  190. });
  191. var listener1 = jasmine.createSpy("listener1");
  192. erd.listenTo([$("#test")[0], $("#test")[0]], listener1);
  193. setTimeout(function () {
  194. $("#test").width(300);
  195. }, 200);
  196. setTimeout(function () {
  197. expect(listener1).toHaveBeenCalledWith($("#test")[0]);
  198. expect(listener1.calls.count()).toBe(2);
  199. done();
  200. }, 400);
  201. });
  202. it("should be able to attach listeners to multiple elements", function (done) {
  203. var erd = elementResizeDetectorMaker({
  204. callOnAdd: false,
  205. reporter: reporter,
  206. strategy: strategy
  207. });
  208. var listener1 = jasmine.createSpy("listener1");
  209. erd.listenTo($("#test, #test2"), listener1);
  210. setTimeout(function () {
  211. $("#test").width(200);
  212. }, 200);
  213. setTimeout(function () {
  214. expect(listener1).toHaveBeenCalledWith($("#test")[0]);
  215. }, 400);
  216. setTimeout(function () {
  217. $("#test2").width(500);
  218. }, 600);
  219. setTimeout(function () {
  220. expect(listener1).toHaveBeenCalledWith($("#test2")[0]);
  221. done();
  222. }, 800);
  223. });
  224. //Only run this test if the browser actually is able to get the computed style of an element.
  225. //Only IE8 is lacking the getComputedStyle method.
  226. if (window.getComputedStyle) {
  227. it("should keep the style of the element intact", function (done) {
  228. var erd = elementResizeDetectorMaker({
  229. callOnAdd: false,
  230. reporter: reporter,
  231. strategy: strategy
  232. });
  233. function ignoreStyleChange(key, before, after) {
  234. return (key === "position" && before === "static" && after === "relative") ||
  235. (/^(top|right|bottom|left)$/.test(key) && before === "auto" && after === "0px");
  236. }
  237. var beforeComputedStyle = getStyle($("#test")[0]);
  238. erd.listenTo($("#test")[0], _.noop);
  239. var afterComputedStyle = getStyle($("#test")[0]);
  240. ensureMapEqual(beforeComputedStyle, afterComputedStyle, ignoreStyleChange);
  241. //Test styles async since making an element listenable is async.
  242. setTimeout(function () {
  243. var afterComputedStyleAsync = getStyle($("#test")[0]);
  244. ensureMapEqual(beforeComputedStyle, afterComputedStyleAsync, ignoreStyleChange);
  245. expect(true).toEqual(true); // Needed so that jasmine does not warn about no expects in the test (the actual expects are in the ensureMapEqual).
  246. done();
  247. }, 200);
  248. });
  249. }
  250. describe("options.callOnAdd", function () {
  251. it("should be true default and call all functions when listenTo succeeds", function (done) {
  252. var erd = elementResizeDetectorMaker({
  253. reporter: reporter,
  254. strategy: strategy
  255. });
  256. var listener = jasmine.createSpy("listener");
  257. var listener2 = jasmine.createSpy("listener2");
  258. erd.listenTo($("#test")[0], listener);
  259. erd.listenTo($("#test")[0], listener2);
  260. setTimeout(function () {
  261. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  262. expect(listener2).toHaveBeenCalledWith($("#test")[0]);
  263. listener.calls.reset();
  264. listener2.calls.reset();
  265. $("#test").width(300);
  266. }, 200);
  267. setTimeout(function () {
  268. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  269. expect(listener2).toHaveBeenCalledWith($("#test")[0]);
  270. done();
  271. }, 400);
  272. });
  273. it("should call listener multiple times when listening to multiple elements", function (done) {
  274. var erd = elementResizeDetectorMaker({
  275. reporter: reporter,
  276. strategy: strategy
  277. });
  278. var listener1 = jasmine.createSpy("listener1");
  279. erd.listenTo($("#test, #test2"), listener1);
  280. setTimeout(function () {
  281. expect(listener1).toHaveBeenCalledWith($("#test")[0]);
  282. expect(listener1).toHaveBeenCalledWith($("#test2")[0]);
  283. done();
  284. }, 200);
  285. });
  286. });
  287. it("should call listener if the element is changed synchronously after listenTo", function (done) {
  288. var erd = elementResizeDetectorMaker({
  289. callOnAdd: false,
  290. reporter: reporter,
  291. strategy: strategy
  292. });
  293. var listener1 = jasmine.createSpy("listener1");
  294. erd.listenTo($("#test"), listener1);
  295. $("#test").width(200);
  296. setTimeout(function () {
  297. expect(listener1).toHaveBeenCalledWith($("#test")[0]);
  298. done();
  299. }, 200);
  300. });
  301. it("should not emit resize when listenTo is called", function (done) {
  302. var erd = elementResizeDetectorMaker({
  303. callOnAdd: false,
  304. reporter: reporter,
  305. strategy: strategy
  306. });
  307. var listener1 = jasmine.createSpy("listener1");
  308. erd.listenTo($("#test"), listener1);
  309. setTimeout(function () {
  310. expect(listener1).not.toHaveBeenCalledWith($("#test")[0]);
  311. done();
  312. }, 200);
  313. });
  314. it("should not emit resize event even though the element is back to its start size", function (done) {
  315. var erd = elementResizeDetectorMaker({
  316. callOnAdd: false,
  317. reporter: reporter,
  318. strategy: strategy
  319. });
  320. var listener = jasmine.createSpy("listener1");
  321. $("#test").width(200);
  322. erd.listenTo($("#test"), listener);
  323. setTimeout(function () {
  324. expect(listener).not.toHaveBeenCalledWith($("#test")[0]);
  325. listener.calls.reset();
  326. $("#test").width(100);
  327. }, 200);
  328. setTimeout(function () {
  329. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  330. listener.calls.reset();
  331. $("#test").width(200);
  332. }, 400);
  333. setTimeout(function () {
  334. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  335. done();
  336. }, 600);
  337. });
  338. it("should use the option.idHandler if present", function (done) {
  339. var ID_ATTR = "some-fancy-id-attr";
  340. var idHandler = {
  341. get: function (element, readonly) {
  342. if (element[ID_ATTR] === undefined) {
  343. if (readonly) {
  344. return null;
  345. }
  346. this.set(element);
  347. }
  348. return $(element).attr(ID_ATTR);
  349. },
  350. set: function (element) {
  351. var id;
  352. if ($(element).attr("id") === "test") {
  353. id = "test+1";
  354. } else if ($(element).attr("id") === "test2") {
  355. id = "test2+2";
  356. }
  357. $(element).attr(ID_ATTR, id);
  358. return id;
  359. }
  360. };
  361. var erd = elementResizeDetectorMaker({
  362. idHandler: idHandler,
  363. callOnAdd: false,
  364. reporter: reporter,
  365. strategy: strategy
  366. });
  367. var listener1 = jasmine.createSpy("listener1");
  368. var listener2 = jasmine.createSpy("listener1");
  369. var attrsBeforeTest = getAttributes($("#test")[0]);
  370. var attrsBeforeTest2 = getAttributes($("#test2")[0]);
  371. erd.listenTo($("#test"), listener1);
  372. erd.listenTo($("#test, #test2"), listener2);
  373. var attrsAfterTest = getAttributes($("#test")[0]);
  374. var attrsAfterTest2 = getAttributes($("#test2")[0]);
  375. var ignoreValidIdAttrAndStyle = function (key) {
  376. return key === ID_ATTR || key === "style";
  377. };
  378. ensureAttributes(attrsBeforeTest, attrsAfterTest, ignoreValidIdAttrAndStyle);
  379. ensureAttributes(attrsBeforeTest2, attrsAfterTest2, ignoreValidIdAttrAndStyle);
  380. expect($("#test").attr(ID_ATTR)).toEqual("test+1");
  381. expect($("#test2").attr(ID_ATTR)).toEqual("test2+2");
  382. setTimeout(function () {
  383. $("#test").width(300);
  384. $("#test2").width(500);
  385. }, 200);
  386. setTimeout(function () {
  387. expect(listener1).toHaveBeenCalledWith($("#test")[0]);
  388. expect(listener2).toHaveBeenCalledWith($("#test")[0]);
  389. expect(listener2).toHaveBeenCalledWith($("#test2")[0]);
  390. done();
  391. }, 600);
  392. });
  393. it("should be able to install into elements that are detached from the DOM", function (done) {
  394. var erd = elementResizeDetectorMaker({
  395. callOnAdd: false,
  396. reporter: reporter,
  397. strategy: strategy
  398. });
  399. var listener1 = jasmine.createSpy("listener1");
  400. var div = document.createElement("div");
  401. div.style.width = "100%";
  402. div.style.height = "100%";
  403. erd.listenTo(div, listener1);
  404. setTimeout(function () {
  405. $("#test")[0].appendChild(div);
  406. }, 200);
  407. setTimeout(function () {
  408. $("#test").width(200);
  409. }, 400);
  410. setTimeout(function () {
  411. expect(listener1).toHaveBeenCalledWith(div);
  412. done();
  413. }, 600);
  414. });
  415. it("should handle iframes, by using initDocument", function (done) {
  416. var erd = elementResizeDetectorMaker({
  417. callOnAdd: false,
  418. strategy: strategy,
  419. reporter: reporter
  420. });
  421. var listener1 = jasmine.createSpy("listener1");
  422. var iframe = document.createElement("iframe");
  423. $("#test")[0].appendChild(iframe);
  424. erd.initDocument(iframe.contentDocument);
  425. var div = iframe.contentDocument.createElement("div");
  426. div.style.width = "100%";
  427. div.style.height = "100%";
  428. div.id = "target";
  429. erd.listenTo(div, listener1);
  430. setTimeout(function () {
  431. // FireFox triggers the onload state of the iframe and wipes its content.
  432. iframe.contentDocument.body.appendChild(div);
  433. erd.initDocument(iframe.contentDocument);
  434. }, 10);
  435. setTimeout(function () {
  436. div.style.width = "100px";
  437. }, 200);
  438. setTimeout(function () {
  439. expect(listener1).toHaveBeenCalledWith(div);
  440. done();
  441. }, 400);
  442. });
  443. it("should detect resizes caused by padding and font-size changes", function (done) {
  444. var erd = elementResizeDetectorMaker({
  445. callOnAdd: false,
  446. reporter: reporter,
  447. strategy: strategy
  448. });
  449. var listener = jasmine.createSpy("listener");
  450. $("#test").html("test");
  451. $("#test").css("padding", "0px");
  452. $("#test").css("font-size", "16px");
  453. erd.listenTo($("#test"), listener);
  454. $("#test").css("padding", "10px");
  455. setTimeout(function () {
  456. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  457. listener.calls.reset();
  458. $("#test").css("font-size", "20px");
  459. }, 200);
  460. setTimeout(function () {
  461. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  462. done();
  463. }, 400);
  464. });
  465. describe("should handle unrendered elements correctly", function () {
  466. it("when installing", function (done) {
  467. var erd = elementResizeDetectorMaker({
  468. callOnAdd: false,
  469. reporter: reporter,
  470. strategy: strategy
  471. });
  472. $("#test").html("<div id=\"inner\"></div>");
  473. $("#test").css("display", "none");
  474. var listener = jasmine.createSpy("listener");
  475. erd.listenTo($("#inner"), listener);
  476. setTimeout(function () {
  477. expect(listener).not.toHaveBeenCalled();
  478. $("#test").css("display", "");
  479. }, 200);
  480. setTimeout(function () {
  481. expect(listener).toHaveBeenCalledWith($("#inner")[0]);
  482. listener.calls.reset();
  483. $("#inner").width("300px");
  484. }, 400);
  485. setTimeout(function () {
  486. expect(listener).toHaveBeenCalledWith($("#inner")[0]);
  487. listener.calls.reset();
  488. done();
  489. }, 600);
  490. });
  491. it("when element gets unrendered after installation", function (done) {
  492. var erd = elementResizeDetectorMaker({
  493. callOnAdd: false,
  494. reporter: reporter,
  495. strategy: strategy
  496. });
  497. // The div is rendered to begin with.
  498. $("#test").html("<div id=\"inner\"></div>");
  499. var listener = jasmine.createSpy("listener");
  500. erd.listenTo($("#inner"), listener);
  501. // The it gets unrendered, and it changes width.
  502. setTimeout(function () {
  503. expect(listener).not.toHaveBeenCalled();
  504. $("#test").css("display", "none");
  505. $("#inner").width("300px");
  506. }, 100);
  507. // Render the element again.
  508. setTimeout(function () {
  509. expect(listener).not.toHaveBeenCalled();
  510. $("#test").css("display", "");
  511. }, 200);
  512. // ERD should detect that the element has changed size as soon as it gets rendered again.
  513. setTimeout(function () {
  514. expect(listener).toHaveBeenCalledWith($("#inner")[0]);
  515. done();
  516. }, 300);
  517. });
  518. });
  519. describe("inline elements", function () {
  520. it("should be listenable", function (done) {
  521. var erd = elementResizeDetectorMaker({
  522. callOnAdd: false,
  523. reporter: reporter,
  524. strategy: strategy
  525. });
  526. $("#test").html("<span id=\"inner\">test</span>");
  527. var listener = jasmine.createSpy("listener");
  528. erd.listenTo($("#inner"), listener);
  529. setTimeout(function () {
  530. expect(listener).not.toHaveBeenCalled();
  531. $("#inner").append("testing testing");
  532. }, 100);
  533. setTimeout(function () {
  534. expect(listener).toHaveBeenCalledWith($("#inner")[0]);
  535. done();
  536. }, 200);
  537. });
  538. it("should not get altered dimensions", function (done) {
  539. var erd = elementResizeDetectorMaker({
  540. callOnAdd: false,
  541. reporter: reporter,
  542. strategy: strategy
  543. });
  544. $("#test").html("<span id=\"inner\"></span>");
  545. var widthBefore = $("#inner").width();
  546. var heightBefore = $("#inner").height();
  547. var listener = jasmine.createSpy("listener");
  548. erd.listenTo($("#inner"), listener);
  549. setTimeout(function () {
  550. expect($("#inner").width()).toEqual(widthBefore);
  551. expect($("#inner").height()).toEqual(heightBefore);
  552. done();
  553. }, 100);
  554. });
  555. });
  556. it("should handle dir=rtl correctly", function (done) {
  557. var erd = elementResizeDetectorMaker({
  558. callOnAdd: false,
  559. reporter: reporter,
  560. strategy: strategy
  561. });
  562. var listener = jasmine.createSpy("listener");
  563. $("#test")[0].dir = "rtl";
  564. erd.listenTo($("#test")[0], listener);
  565. setTimeout(function () {
  566. $("#test").width(300);
  567. }, 200);
  568. setTimeout(function () {
  569. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  570. done();
  571. }, 400);
  572. });
  573. it("should handle fast consecutive resizes", function (done) {
  574. var erd = elementResizeDetectorMaker({
  575. callOnAdd: false,
  576. strategy: strategy,
  577. reporter: reporter
  578. });
  579. var listener = jasmine.createSpy("listener");
  580. $("#test").width(100);
  581. erd.listenTo($("#test")[0], listener);
  582. setTimeout(function () {
  583. $("#test").width(300);
  584. }, 50);
  585. setTimeout(function () {
  586. expect(listener.calls.count()).toEqual(1);
  587. $("#test").width(500);
  588. setTimeout(function () {
  589. $("#test").width(300);
  590. }, 0);
  591. }, 100);
  592. // Some browsers skip the 300 -> 500 -> 300 resize, and some actually processes it.
  593. // So the resize events may be 1 or 3 at this point.
  594. setTimeout(function () {
  595. var count = listener.calls.count();
  596. expect(count === 1 || count === 3).toEqual(true);
  597. }, 150);
  598. setTimeout(function () {
  599. var count = listener.calls.count();
  600. expect(count === 1 || count === 3).toEqual(true);
  601. $("#test").width(800);
  602. }, 200);
  603. setTimeout(function () {
  604. var count = listener.calls.count();
  605. expect(count === 2 || count === 4).toEqual(true);
  606. done();
  607. }, 250);
  608. });
  609. });
  610. }
  611. function removalTest(strategy) {
  612. describe("[" + strategy + "] resizeDetector.removeListener", function () {
  613. it("should remove listener from element", function (done) {
  614. var erd = elementResizeDetectorMaker({
  615. callOnAdd: false,
  616. strategy: strategy
  617. });
  618. var $testElem = $("#test");
  619. var listenerCall = jasmine.createSpy("listener");
  620. var listenerNotCall = jasmine.createSpy("listener");
  621. erd.listenTo($testElem[0], listenerCall);
  622. erd.listenTo($testElem[0], listenerNotCall);
  623. setTimeout(function () {
  624. erd.removeListener($testElem[0], listenerNotCall);
  625. $testElem.width(300);
  626. }, 200);
  627. setTimeout(function () {
  628. expect(listenerCall).toHaveBeenCalled();
  629. expect(listenerNotCall).not.toHaveBeenCalled();
  630. done();
  631. }, 400);
  632. });
  633. });
  634. describe("[" + strategy + "] resizeDetector.removeAllListeners", function () {
  635. it("should remove all listeners from element", function (done) {
  636. var erd = elementResizeDetectorMaker({
  637. callOnAdd: false,
  638. strategy: strategy
  639. });
  640. var $testElem = $("#test");
  641. var listener1 = jasmine.createSpy("listener");
  642. var listener2 = jasmine.createSpy("listener");
  643. erd.listenTo($testElem[0], listener1);
  644. erd.listenTo($testElem[0], listener2);
  645. setTimeout(function () {
  646. erd.removeAllListeners($testElem[0]);
  647. $testElem.width(300);
  648. }, 200);
  649. setTimeout(function () {
  650. expect(listener1).not.toHaveBeenCalled();
  651. expect(listener2).not.toHaveBeenCalled();
  652. done();
  653. }, 400);
  654. });
  655. it("should work for elements that don't have the detector installed", function () {
  656. var erd = elementResizeDetectorMaker({
  657. strategy: strategy
  658. });
  659. var $testElem = $("#test");
  660. expect(erd.removeAllListeners.bind(erd, $testElem[0])).not.toThrow();
  661. });
  662. });
  663. describe("[scroll] Specific scenarios", function () {
  664. it("should be able to call uninstall in the middle of a resize", function (done) {
  665. var erd = elementResizeDetectorMaker({
  666. strategy: "scroll"
  667. });
  668. var $testElem = $("#test");
  669. var testElem = $testElem[0];
  670. var listener = jasmine.createSpy("listener");
  671. erd.listenTo(testElem, listener);
  672. setTimeout(function () {
  673. // We want the uninstall to happen exactly when a scroll event occured before the delayed batched is going to be processed.
  674. // So we intercept the erd shrink/expand functions in the state so that we may call uninstall after the handling of the event.
  675. var uninstalled = false;
  676. function wrapOnScrollEvent(oldFn) {
  677. return function () {
  678. oldFn();
  679. if (!uninstalled) {
  680. expect(erd.uninstall.bind(erd, testElem)).not.toThrow();
  681. uninstalled = true;
  682. done();
  683. }
  684. };
  685. }
  686. var state = testElem._erd;
  687. state.onExpand = wrapOnScrollEvent(state.onExpand);
  688. state.onShrink = wrapOnScrollEvent(state.onShrink);
  689. $("#test").width(300);
  690. }, 50);
  691. });
  692. it("should be able to call uninstall and then install in the middle of a resize (issue #61)", function (done) {
  693. var erd = elementResizeDetectorMaker({
  694. strategy: "scroll",
  695. reporter: reporter
  696. });
  697. var $testElem = $("#test");
  698. var testElem = $testElem[0];
  699. var listener = jasmine.createSpy("listener");
  700. erd.listenTo(testElem, listener);
  701. setTimeout(function () {
  702. // We want the uninstall to happen exactly when a scroll event occured before the delayed batched is going to be processed.
  703. // So we intercept the erd shrink/expand functions in the state so that we may call uninstall after the handling of the event.
  704. var uninstalled = false;
  705. function wrapOnScrollEvent(oldFn) {
  706. return function () {
  707. oldFn();
  708. if (!uninstalled) {
  709. expect(erd.uninstall.bind(erd, testElem)).not.toThrow();
  710. uninstalled = true;
  711. var listener2 = jasmine.createSpy("listener");
  712. expect(erd.listenTo.bind(erd, testElem, listener2)).not.toThrow();
  713. setTimeout(function () {
  714. done();
  715. }, 0);
  716. }
  717. };
  718. }
  719. var state = testElem._erd;
  720. state.onExpand = wrapOnScrollEvent(state.onExpand);
  721. state.onShrink = wrapOnScrollEvent(state.onShrink);
  722. $("#test").width(300);
  723. }, 50);
  724. });
  725. });
  726. describe("[" + strategy + "] resizeDetector.uninstall", function () {
  727. it("should completely remove detector from element", function (done) {
  728. var erd = elementResizeDetectorMaker({
  729. callOnAdd: false,
  730. strategy: strategy
  731. });
  732. var $testElem = $("#test");
  733. var listener = jasmine.createSpy("listener");
  734. erd.listenTo($testElem[0], listener);
  735. setTimeout(function () {
  736. erd.uninstall($testElem[0]);
  737. // detector element should be removed
  738. expect($testElem[0].childNodes.length).toBe(0);
  739. $testElem.width(300);
  740. }, 200);
  741. setTimeout(function () {
  742. expect(listener).not.toHaveBeenCalled();
  743. done();
  744. }, 400);
  745. });
  746. it("should completely remove detector from multiple elements", function (done) {
  747. var erd = elementResizeDetectorMaker({
  748. callOnAdd: false,
  749. strategy: strategy
  750. });
  751. var listener = jasmine.createSpy("listener");
  752. erd.listenTo($("#test, #test2"), listener);
  753. setTimeout(function () {
  754. erd.uninstall($("#test, #test2"));
  755. // detector element should be removed
  756. expect($("#test")[0].childNodes.length).toBe(0);
  757. expect($("#test2")[0].childNodes.length).toBe(0);
  758. $("#test, #test2").width(300);
  759. }, 200);
  760. setTimeout(function () {
  761. expect(listener).not.toHaveBeenCalled();
  762. done();
  763. }, 400);
  764. });
  765. it("should be able to call uninstall directly after listenTo", function () {
  766. var erd = elementResizeDetectorMaker({
  767. strategy: strategy
  768. });
  769. var $testElem = $("#test");
  770. var listener = jasmine.createSpy("listener");
  771. erd.listenTo($testElem[0], listener);
  772. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  773. });
  774. it("should be able to call uninstall directly async after listenTo", function (done) {
  775. var erd = elementResizeDetectorMaker({
  776. strategy: strategy
  777. });
  778. var $testElem = $("#test");
  779. var listener = jasmine.createSpy("listener");
  780. erd.listenTo($testElem[0], listener);
  781. setTimeout(function () {
  782. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  783. done();
  784. }, 0);
  785. });
  786. it("should be able to call uninstall in callOnAdd callback", function (done) {
  787. var error = false;
  788. // Ugly hack to catch async errors.
  789. window.onerror = function () {
  790. error = true;
  791. };
  792. var erd = elementResizeDetectorMaker({
  793. strategy: strategy,
  794. callOnAdd: true
  795. });
  796. erd.listenTo($("#test"), function () {
  797. expect(erd.uninstall.bind(null, ($("#test")))).not.toThrow();
  798. });
  799. setTimeout(function () {
  800. expect(error).toBe(false);
  801. done();
  802. window.error = null;
  803. }, 50);
  804. });
  805. it("should be able to call uninstall in callOnAdd callback with multiple elements", function (done) {
  806. var error = false;
  807. // Ugly hack to catch async errors.
  808. window.onerror = function () {
  809. error = true;
  810. };
  811. var erd = elementResizeDetectorMaker({
  812. strategy: strategy,
  813. callOnAdd: true
  814. });
  815. var listener = jasmine.createSpy("listener");
  816. erd.listenTo($("#test, #test2"), function () {
  817. expect(erd.uninstall.bind(null, ($("#test, #test2")))).not.toThrow();
  818. listener();
  819. });
  820. setTimeout(function () {
  821. expect(listener.calls.count()).toBe(1);
  822. expect(error).toBe(false);
  823. done();
  824. window.error = null;
  825. }, 50);
  826. });
  827. it("should be able to call uninstall on non-erd elements", function () {
  828. var erd = elementResizeDetectorMaker({
  829. strategy: strategy
  830. });
  831. var $testElem = $("#test");
  832. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  833. var listener = jasmine.createSpy("listener");
  834. erd.listenTo($testElem[0], listener);
  835. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  836. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  837. });
  838. });
  839. }
  840. function importantRuleTest(strategy) {
  841. describe("[" + strategy + "] resizeDetector.important", function () {
  842. it("should add all rules with important", function (done) {
  843. var erd = elementResizeDetectorMaker({
  844. callOnAdd: true,
  845. strategy: strategy,
  846. important: true
  847. });
  848. var testElem = $("#test");
  849. var listenerCall = jasmine.createSpy("listener");
  850. erd.listenTo(testElem[0], listenerCall);
  851. setTimeout(function () {
  852. if (strategy === "scroll") {
  853. expect(testElem[0].style.cssText).toMatch(/!important;$/);
  854. }
  855. testElem.find("*").toArray().forEach(function (element) {
  856. var rules = element.style.cssText.split(";").filter(function (rule) {
  857. return !!rule;
  858. });
  859. rules.forEach(function (rule) {
  860. expect(rule).toMatch(/!important$/);
  861. });
  862. });
  863. done();
  864. }, 50);
  865. });
  866. it("Overrides important CSS", function (done) {
  867. var erd = elementResizeDetectorMaker({
  868. callOnAdd: false,
  869. strategy: strategy,
  870. important: true
  871. });
  872. var listener = jasmine.createSpy("listener");
  873. var testElem = $("#test");
  874. var style = document.createElement("style");
  875. style.appendChild(document.createTextNode("#test { position: static !important; }"));
  876. document.head.appendChild(style);
  877. erd.listenTo(testElem[0], listener);
  878. setTimeout(function () {
  879. $("#test").width(300);
  880. }, 100);
  881. setTimeout(function () {
  882. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  883. done();
  884. }, 200);
  885. });
  886. });
  887. }
  888. describe("element-resize-detector", function () {
  889. beforeEach(function () {
  890. //This messed with tests in IE8.
  891. //TODO: Investigate why, because it would be nice to have instead of the current solution.
  892. //loadFixtures("element-resize-detector_fixture.html");
  893. $("#fixtures").html("<div id=test></div><div id=test2></div>");
  894. });
  895. describe("elementResizeDetectorMaker", function () {
  896. it("should be globally defined", function () {
  897. expect(elementResizeDetectorMaker).toBeDefined();
  898. });
  899. it("should create an element-resize-detector instance", function () {
  900. var erd = elementResizeDetectorMaker();
  901. expect(erd).toBeDefined();
  902. expect(erd.listenTo).toBeDefined();
  903. });
  904. });
  905. // listenToTest("object");
  906. // removalTest("object");
  907. // importantRuleTest("object");
  908. listenToTest("scroll");
  909. removalTest("scroll");
  910. importantRuleTest("scroll");
  911. });