element-resize-detector_test.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  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. // Only run this shadow DOM test for browsers that support the feature
  726. if (!!HTMLElement.prototype.attachShadow) {
  727. it("should work for elements within an open shadow root (issue #127)", function(done) {
  728. var erd = elementResizeDetectorMaker({
  729. callOnAdd: false,
  730. reporter: reporter,
  731. strategy: "scroll"
  732. });
  733. var listener = jasmine.createSpy("listener");
  734. // Setup shadow root with a child div
  735. var shadow = $("#shadowtest")[0].attachShadow({mode: "open"});
  736. var shadowChild = document.createElement("div");
  737. shadow.appendChild(shadowChild);
  738. erd.listenTo(shadowChild, listener);
  739. setTimeout(function () {
  740. $(shadowChild).width(300);
  741. }, 200);
  742. setTimeout(function () {
  743. expect(listener).toHaveBeenCalledWith(shadowChild);
  744. done();
  745. }, 400);
  746. });
  747. }
  748. });
  749. describe("[" + strategy + "] resizeDetector.uninstall", function () {
  750. it("should completely remove detector from element", function (done) {
  751. var erd = elementResizeDetectorMaker({
  752. callOnAdd: false,
  753. strategy: strategy
  754. });
  755. var $testElem = $("#test");
  756. var listener = jasmine.createSpy("listener");
  757. erd.listenTo($testElem[0], listener);
  758. setTimeout(function () {
  759. erd.uninstall($testElem[0]);
  760. // detector element should be removed
  761. expect($testElem[0].childNodes.length).toBe(0);
  762. $testElem.width(300);
  763. }, 200);
  764. setTimeout(function () {
  765. expect(listener).not.toHaveBeenCalled();
  766. done();
  767. }, 400);
  768. });
  769. it("should completely remove detector from multiple elements", function (done) {
  770. var erd = elementResizeDetectorMaker({
  771. callOnAdd: false,
  772. strategy: strategy
  773. });
  774. var listener = jasmine.createSpy("listener");
  775. erd.listenTo($("#test, #test2"), listener);
  776. setTimeout(function () {
  777. erd.uninstall($("#test, #test2"));
  778. // detector element should be removed
  779. expect($("#test")[0].childNodes.length).toBe(0);
  780. expect($("#test2")[0].childNodes.length).toBe(0);
  781. $("#test, #test2").width(300);
  782. }, 200);
  783. setTimeout(function () {
  784. expect(listener).not.toHaveBeenCalled();
  785. done();
  786. }, 400);
  787. });
  788. it("should be able to call uninstall directly after listenTo", function () {
  789. var erd = elementResizeDetectorMaker({
  790. strategy: strategy
  791. });
  792. var $testElem = $("#test");
  793. var listener = jasmine.createSpy("listener");
  794. erd.listenTo($testElem[0], listener);
  795. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  796. });
  797. it("should be able to call uninstall directly async after listenTo", function (done) {
  798. var erd = elementResizeDetectorMaker({
  799. strategy: strategy
  800. });
  801. var $testElem = $("#test");
  802. var listener = jasmine.createSpy("listener");
  803. erd.listenTo($testElem[0], listener);
  804. setTimeout(function () {
  805. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  806. done();
  807. }, 0);
  808. });
  809. it("should be able to call uninstall in callOnAdd callback", function (done) {
  810. var error = false;
  811. // Ugly hack to catch async errors.
  812. window.onerror = function () {
  813. error = true;
  814. };
  815. var erd = elementResizeDetectorMaker({
  816. strategy: strategy,
  817. callOnAdd: true
  818. });
  819. erd.listenTo($("#test"), function () {
  820. expect(erd.uninstall.bind(null, ($("#test")))).not.toThrow();
  821. });
  822. setTimeout(function () {
  823. expect(error).toBe(false);
  824. done();
  825. window.error = null;
  826. }, 50);
  827. });
  828. it("should be able to call uninstall in callOnAdd callback with multiple elements", function (done) {
  829. var error = false;
  830. // Ugly hack to catch async errors.
  831. window.onerror = function () {
  832. error = true;
  833. };
  834. var erd = elementResizeDetectorMaker({
  835. strategy: strategy,
  836. callOnAdd: true
  837. });
  838. var listener = jasmine.createSpy("listener");
  839. erd.listenTo($("#test, #test2"), function () {
  840. expect(erd.uninstall.bind(null, ($("#test, #test2")))).not.toThrow();
  841. listener();
  842. });
  843. setTimeout(function () {
  844. expect(listener.calls.count()).toBe(1);
  845. expect(error).toBe(false);
  846. done();
  847. window.error = null;
  848. }, 50);
  849. });
  850. it("should be able to call uninstall on non-erd elements", function () {
  851. var erd = elementResizeDetectorMaker({
  852. strategy: strategy
  853. });
  854. var $testElem = $("#test");
  855. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  856. var listener = jasmine.createSpy("listener");
  857. erd.listenTo($testElem[0], listener);
  858. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  859. expect(erd.uninstall.bind(erd, $testElem[0])).not.toThrow();
  860. });
  861. });
  862. }
  863. function importantRuleTest(strategy) {
  864. describe("[" + strategy + "] resizeDetector.important", function () {
  865. it("should add all rules with important", function (done) {
  866. var erd = elementResizeDetectorMaker({
  867. callOnAdd: true,
  868. strategy: strategy,
  869. important: true
  870. });
  871. var testElem = $("#test");
  872. var listenerCall = jasmine.createSpy("listener");
  873. erd.listenTo(testElem[0], listenerCall);
  874. setTimeout(function () {
  875. if (strategy === "scroll") {
  876. expect(testElem[0].style.cssText).toMatch(/!important;$/);
  877. }
  878. testElem.find("*").toArray().forEach(function (element) {
  879. var rules = element.style.cssText.split(";").filter(function (rule) {
  880. return !!rule;
  881. });
  882. rules.forEach(function (rule) {
  883. expect(rule).toMatch(/!important$/);
  884. });
  885. });
  886. done();
  887. }, 50);
  888. });
  889. it("Overrides important CSS", function (done) {
  890. var erd = elementResizeDetectorMaker({
  891. callOnAdd: false,
  892. strategy: strategy,
  893. important: true
  894. });
  895. var listener = jasmine.createSpy("listener");
  896. var testElem = $("#test");
  897. var style = document.createElement("style");
  898. style.appendChild(document.createTextNode("#test { position: static !important; }"));
  899. document.head.appendChild(style);
  900. erd.listenTo(testElem[0], listener);
  901. setTimeout(function () {
  902. $("#test").width(300);
  903. }, 100);
  904. setTimeout(function () {
  905. expect(listener).toHaveBeenCalledWith($("#test")[0]);
  906. done();
  907. }, 200);
  908. });
  909. });
  910. }
  911. describe("element-resize-detector", function () {
  912. beforeEach(function () {
  913. //This messed with tests in IE8.
  914. //TODO: Investigate why, because it would be nice to have instead of the current solution.
  915. //loadFixtures("element-resize-detector_fixture.html");
  916. $("#fixtures").html("<div id=test></div><div id=test2></div><div id=shadowtest></div>");
  917. });
  918. describe("elementResizeDetectorMaker", function () {
  919. it("should be globally defined", function () {
  920. expect(elementResizeDetectorMaker).toBeDefined();
  921. });
  922. it("should create an element-resize-detector instance", function () {
  923. var erd = elementResizeDetectorMaker();
  924. expect(erd).toBeDefined();
  925. expect(erd.listenTo).toBeDefined();
  926. });
  927. });
  928. // listenToTest("object");
  929. // removalTest("object");
  930. // importantRuleTest("object");
  931. listenToTest("scroll");
  932. removalTest("scroll");
  933. importantRuleTest("scroll");
  934. });