coreSpec.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. /**
  2. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-FileCopyrightText: 2014 ownCloud Inc.
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. describe('Core base tests', function() {
  7. var debounceStub
  8. beforeEach(function() {
  9. debounceStub = sinon.stub(_, 'debounce').callsFake(function(callback) {
  10. return function() {
  11. // defer instead of debounce, to make it work with clock
  12. _.defer(callback);
  13. };
  14. });
  15. });
  16. afterEach(function() {
  17. // many tests call window.initCore so need to unregister global events
  18. // ideally in the future we'll need a window.unloadCore() function
  19. $(document).off('ajaxError.main');
  20. $(document).off('unload.main');
  21. $(document).off('beforeunload.main');
  22. OC._userIsNavigatingAway = false;
  23. OC._reloadCalled = false;
  24. debounceStub.restore();
  25. });
  26. describe('Base values', function() {
  27. it('Sets webroots', function() {
  28. expect(OC.getRootPath()).toBeDefined();
  29. expect(window._oc_appswebroots).toBeDefined();
  30. });
  31. });
  32. describe('filePath', function() {
  33. beforeEach(function() {
  34. window._oc_webroot = 'http://localhost';
  35. window._oc_appswebroots.files = OC.getRootPath() + '/apps3/files';
  36. });
  37. afterEach(function() {
  38. delete window._oc_appswebroots.files;
  39. });
  40. it('Uses a direct link for css and images,' , function() {
  41. expect(OC.filePath('core', 'css', 'style.css')).toEqual('http://localhost/core/css/style.css');
  42. expect(OC.filePath('files', 'css', 'style.css')).toEqual('http://localhost/apps3/files/css/style.css');
  43. expect(OC.filePath('core', 'img', 'image.png')).toEqual('http://localhost/core/img/image.png');
  44. expect(OC.filePath('files', 'img', 'image.png')).toEqual('http://localhost/apps3/files/img/image.png');
  45. });
  46. it('Routes PHP files via index.php,' , function() {
  47. expect(OC.filePath('core', 'ajax', 'test.php')).toEqual('http://localhost/index.php/core/ajax/test.php');
  48. expect(OC.filePath('files', 'ajax', 'test.php')).toEqual('http://localhost/index.php/apps/files/ajax/test.php');
  49. });
  50. });
  51. describe('Link functions', function() {
  52. var TESTAPP = 'testapp';
  53. var TESTAPP_ROOT = OC.getRootPath() + '/appsx/testapp';
  54. beforeEach(function() {
  55. window._oc_appswebroots[TESTAPP] = TESTAPP_ROOT;
  56. });
  57. afterEach(function() {
  58. // restore original array
  59. delete window._oc_appswebroots[TESTAPP];
  60. });
  61. it('Generates correct links for core apps', function() {
  62. expect(OC.linkTo('core', 'somefile.php')).toEqual(OC.getRootPath() + '/core/somefile.php');
  63. expect(OC.linkTo('admin', 'somefile.php')).toEqual(OC.getRootPath() + '/admin/somefile.php');
  64. });
  65. it('Generates correct links for regular apps', function() {
  66. expect(OC.linkTo(TESTAPP, 'somefile.php')).toEqual(OC.getRootPath() + '/index.php/apps/' + TESTAPP + '/somefile.php');
  67. });
  68. it('Generates correct remote links', function() {
  69. expect(OC.linkToRemote('webdav')).toEqual(window.location.protocol + '//' + window.location.host + OC.getRootPath() + '/remote.php/webdav');
  70. });
  71. describe('Images', function() {
  72. it('Generates image path with given extension', function() {
  73. expect(OC.imagePath('core', 'somefile.jpg')).toEqual(OC.getRootPath() + '/core/img/somefile.jpg');
  74. expect(OC.imagePath(TESTAPP, 'somefile.jpg')).toEqual(TESTAPP_ROOT + '/img/somefile.jpg');
  75. });
  76. it('Generates image path with svg extension', function() {
  77. expect(OC.imagePath('core', 'somefile')).toEqual(OC.getRootPath() + '/core/img/somefile.svg');
  78. expect(OC.imagePath(TESTAPP, 'somefile')).toEqual(TESTAPP_ROOT + '/img/somefile.svg');
  79. });
  80. });
  81. });
  82. describe('Query string building', function() {
  83. it('Returns empty string when empty params', function() {
  84. expect(OC.buildQueryString()).toEqual('');
  85. expect(OC.buildQueryString({})).toEqual('');
  86. });
  87. it('Encodes regular query strings', function() {
  88. expect(OC.buildQueryString({
  89. a: 'abc',
  90. b: 'def'
  91. })).toEqual('a=abc&b=def');
  92. });
  93. it('Encodes special characters', function() {
  94. expect(OC.buildQueryString({
  95. unicode: '汉字'
  96. })).toEqual('unicode=%E6%B1%89%E5%AD%97');
  97. expect(OC.buildQueryString({
  98. b: 'spaace value',
  99. 'space key': 'normalvalue',
  100. 'slash/this': 'amp&ersand'
  101. })).toEqual('b=spaace%20value&space%20key=normalvalue&slash%2Fthis=amp%26ersand');
  102. });
  103. it('Encodes data types and empty values', function() {
  104. expect(OC.buildQueryString({
  105. 'keywithemptystring': '',
  106. 'keywithnull': null,
  107. 'keywithundefined': null,
  108. something: 'else'
  109. })).toEqual('keywithemptystring=&keywithnull&keywithundefined&something=else');
  110. expect(OC.buildQueryString({
  111. 'booleanfalse': false,
  112. 'booleantrue': true
  113. })).toEqual('booleanfalse=false&booleantrue=true');
  114. expect(OC.buildQueryString({
  115. 'number': 123
  116. })).toEqual('number=123');
  117. });
  118. });
  119. describe('Session heartbeat', function() {
  120. var clock,
  121. oldConfig,
  122. counter;
  123. beforeEach(function() {
  124. clock = sinon.useFakeTimers();
  125. oldConfig = OC.config;
  126. counter = 0;
  127. fakeServer.autoRespond = true;
  128. fakeServer.autoRespondAfter = 0;
  129. fakeServer.respondWith(/\/csrftoken/, function(xhr) {
  130. counter++;
  131. xhr.respond(200, {'Content-Type': 'application/json'}, '{"token": "pgBEsb3MzTb1ZPd2mfDZbQ6/0j3OrXHMEZrghHcOkg8=:3khw5PSa+wKQVo4f26exFD3nplud9ECjJ8/Y5zk5/k4="}');
  132. });
  133. $(document).off('ajaxComplete'); // ignore previously registered heartbeats
  134. });
  135. afterEach(function() {
  136. clock.restore();
  137. /* jshint camelcase: false */
  138. OC.config = oldConfig;
  139. $(document).off('ajaxError');
  140. $(document).off('ajaxComplete');
  141. });
  142. it('sends heartbeat half the session lifetime when heartbeat enabled', function() {
  143. /* jshint camelcase: false */
  144. OC.config = {
  145. session_keepalive: true,
  146. session_lifetime: 300
  147. };
  148. window.initCore();
  149. expect(counter).toEqual(0);
  150. // less than half, still nothing
  151. clock.tick(100 * 1000);
  152. expect(counter).toEqual(0);
  153. // reach past half (160), one call
  154. clock.tick(55 * 1000);
  155. expect(counter).toEqual(1);
  156. // almost there to the next, still one
  157. clock.tick(140 * 1000);
  158. expect(counter).toEqual(1);
  159. // past it, second call
  160. clock.tick(20 * 1000);
  161. expect(counter).toEqual(2);
  162. });
  163. it('does not send heartbeat when heartbeat disabled', function() {
  164. /* jshint camelcase: false */
  165. OC.config = {
  166. session_keepalive: false,
  167. session_lifetime: 300
  168. };
  169. window.initCore();
  170. expect(counter).toEqual(0);
  171. clock.tick(1000000);
  172. // still nothing
  173. expect(counter).toEqual(0);
  174. });
  175. it('limits the heartbeat between one minute and one day', function() {
  176. /* jshint camelcase: false */
  177. var setIntervalStub = sinon.stub(window, 'setInterval');
  178. OC.config = {
  179. session_keepalive: true,
  180. session_lifetime: 5
  181. };
  182. window.initCore();
  183. expect(setIntervalStub.getCall(0).args[1]).toEqual(60 * 1000);
  184. setIntervalStub.reset();
  185. OC.config = {
  186. session_keepalive: true,
  187. session_lifetime: 48 * 3600
  188. };
  189. window.initCore();
  190. expect(setIntervalStub.getCall(0).args[1]).toEqual(24 * 3600 * 1000);
  191. setIntervalStub.restore();
  192. });
  193. });
  194. describe('Parse query string', function() {
  195. it('Parses query string from full URL', function() {
  196. var query = OC.parseQueryString('http://localhost/stuff.php?q=a&b=x');
  197. expect(query).toEqual({q: 'a', b: 'x'});
  198. });
  199. it('Parses query string from query part alone', function() {
  200. var query = OC.parseQueryString('q=a&b=x');
  201. expect(query).toEqual({q: 'a', b: 'x'});
  202. });
  203. it('Returns null hash when empty query', function() {
  204. var query = OC.parseQueryString('');
  205. expect(query).toEqual(null);
  206. });
  207. it('Returns empty hash when empty query with question mark', function() {
  208. var query = OC.parseQueryString('?');
  209. expect(query).toEqual({});
  210. });
  211. it('Decodes regular query strings', function() {
  212. var query = OC.parseQueryString('a=abc&b=def');
  213. expect(query).toEqual({
  214. a: 'abc',
  215. b: 'def'
  216. });
  217. });
  218. it('Ignores empty parts', function() {
  219. var query = OC.parseQueryString('&q=a&&b=x&');
  220. expect(query).toEqual({q: 'a', b: 'x'});
  221. });
  222. it('Ignores lone equal signs', function() {
  223. var query = OC.parseQueryString('&q=a&=&b=x&');
  224. expect(query).toEqual({q: 'a', b: 'x'});
  225. });
  226. it('Includes extra equal signs in value', function() {
  227. var query = OC.parseQueryString('u=a=x&q=a=b');
  228. expect(query).toEqual({u: 'a=x', q: 'a=b'});
  229. });
  230. it('Decodes plus as space', function() {
  231. var query = OC.parseQueryString('space+key=space+value');
  232. expect(query).toEqual({'space key': 'space value'});
  233. });
  234. it('Decodes special characters', function() {
  235. var query = OC.parseQueryString('unicode=%E6%B1%89%E5%AD%97');
  236. expect(query).toEqual({unicode: '汉字'});
  237. query = OC.parseQueryString('b=spaace%20value&space%20key=normalvalue&slash%2Fthis=amp%26ersand');
  238. expect(query).toEqual({
  239. b: 'spaace value',
  240. 'space key': 'normalvalue',
  241. 'slash/this': 'amp&ersand'
  242. });
  243. });
  244. it('Decodes empty values', function() {
  245. var query = OC.parseQueryString('keywithemptystring=&keywithnostring');
  246. expect(query).toEqual({
  247. 'keywithemptystring': '',
  248. 'keywithnostring': null
  249. });
  250. });
  251. it('Does not interpret data types', function() {
  252. var query = OC.parseQueryString('booleanfalse=false&booleantrue=true&number=123');
  253. expect(query).toEqual({
  254. 'booleanfalse': 'false',
  255. 'booleantrue': 'true',
  256. 'number': '123'
  257. });
  258. });
  259. });
  260. describe('Generate Url', function() {
  261. it('returns absolute urls', function() {
  262. expect(OC.generateUrl('csrftoken')).toEqual(OC.getRootPath() + '/index.php/csrftoken');
  263. expect(OC.generateUrl('/csrftoken')).toEqual(OC.getRootPath() + '/index.php/csrftoken');
  264. });
  265. it('substitutes parameters which are escaped by default', function() {
  266. expect(OC.generateUrl('apps/files/download/{file}', {file: '<">ImAnUnescapedString/!'})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/%3C%22%3EImAnUnescapedString%2F!');
  267. });
  268. it('substitutes parameters which can also be unescaped via option flag', function() {
  269. expect(OC.generateUrl('apps/files/download/{file}', {file: 'subfolder/Welcome.txt'}, {escape: false})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/subfolder/Welcome.txt');
  270. });
  271. it('substitutes multiple parameters which are escaped by default', function() {
  272. expect(OC.generateUrl('apps/files/download/{file}/{id}', {file: '<">ImAnUnescapedString/!', id: 5})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/%3C%22%3EImAnUnescapedString%2F!/5');
  273. });
  274. it('substitutes multiple parameters which can also be unescaped via option flag', function() {
  275. expect(OC.generateUrl('apps/files/download/{file}/{id}', {file: 'subfolder/Welcome.txt', id: 5}, {escape: false})).toEqual(OC.getRootPath() + '/index.php/apps/files/download/subfolder/Welcome.txt/5');
  276. });
  277. it('doesnt error out with no params provided', function () {
  278. expect(OC.generateUrl('apps/files/download{file}')).toEqual(OC.getRootPath() + '/index.php/apps/files/download%7Bfile%7D');
  279. });
  280. });
  281. describe('Util', function() {
  282. describe('computerFileSize', function() {
  283. it('correctly parses file sizes from a human readable formated string', function() {
  284. var data = [
  285. ['125', 125],
  286. ['125.25', 125],
  287. ['125.25B', 125],
  288. ['125.25 B', 125],
  289. ['0 B', 0],
  290. ['99999999999999999999999999999999999999999999 B', 99999999999999999999999999999999999999999999],
  291. ['0 MB', 0],
  292. ['0 kB', 0],
  293. ['0kB', 0],
  294. ['125 B', 125],
  295. ['125b', 125],
  296. ['125 KB', 128000],
  297. ['125kb', 128000],
  298. ['122.1 MB', 128031130],
  299. ['122.1mb', 128031130],
  300. ['119.2 GB', 127990025421],
  301. ['119.2gb', 127990025421],
  302. ['116.4 TB', 127983153473126],
  303. ['116.4tb', 127983153473126],
  304. ['8776656778888777655.4tb', 9.650036181387265e+30],
  305. [1234, null],
  306. [-1234, null],
  307. ['-1234 B', null],
  308. ['B', null],
  309. ['40/0', null],
  310. ['40,30 kb', null],
  311. [' 122.1 MB ', 128031130],
  312. ['122.1 MB ', 128031130],
  313. [' 122.1 MB ', 128031130],
  314. [' 122.1 MB ', 128031130],
  315. ['122.1 MB ', 128031130],
  316. [' 125', 125],
  317. [' 125 ', 125],
  318. ];
  319. for (var i = 0; i < data.length; i++) {
  320. expect(OC.Util.computerFileSize(data[i][0])).toEqual(data[i][1]);
  321. }
  322. });
  323. it('returns null if the parameter is not a string', function() {
  324. expect(OC.Util.computerFileSize(NaN)).toEqual(null);
  325. expect(OC.Util.computerFileSize(125)).toEqual(null);
  326. });
  327. it('returns null if the string is unparsable', function() {
  328. expect(OC.Util.computerFileSize('')).toEqual(null);
  329. expect(OC.Util.computerFileSize('foobar')).toEqual(null);
  330. });
  331. });
  332. describe('stripTime', function() {
  333. it('strips time from dates', function() {
  334. expect(OC.Util.stripTime(new Date(2014, 2, 24, 15, 4, 45, 24)))
  335. .toEqual(new Date(2014, 2, 24, 0, 0, 0, 0));
  336. });
  337. });
  338. });
  339. describe('naturalSortCompare', function() {
  340. // cit() will skip tests if running on PhantomJS because it has issues with
  341. // localeCompare(). See https://github.com/ariya/phantomjs/issues/11063
  342. //
  343. // Please make sure to run these tests in Chrome/Firefox manually
  344. // to make sure they run.
  345. var cit = window.isPhantom?xit:it;
  346. // must provide the same results as \OC_Util::naturalSortCompare
  347. it('sorts alphabetically', function() {
  348. var a = [
  349. 'def',
  350. 'xyz',
  351. 'abc'
  352. ];
  353. a.sort(OC.Util.naturalSortCompare);
  354. expect(a).toEqual([
  355. 'abc',
  356. 'def',
  357. 'xyz'
  358. ]);
  359. });
  360. cit('sorts with different casing', function() {
  361. var a = [
  362. 'aaa',
  363. 'bbb',
  364. 'BBB',
  365. 'AAA'
  366. ];
  367. a.sort(OC.Util.naturalSortCompare);
  368. expect(a).toEqual([
  369. 'aaa',
  370. 'AAA',
  371. 'bbb',
  372. 'BBB'
  373. ]);
  374. });
  375. it('sorts with numbers', function() {
  376. var a = [
  377. '124.txt',
  378. 'abc1',
  379. '123.txt',
  380. 'abc',
  381. 'abc2',
  382. 'def (2).txt',
  383. 'ghi 10.txt',
  384. 'abc12',
  385. 'def.txt',
  386. 'def (1).txt',
  387. 'ghi 2.txt',
  388. 'def (10).txt',
  389. 'abc10',
  390. 'def (12).txt',
  391. 'z',
  392. 'ghi.txt',
  393. 'za',
  394. 'ghi 1.txt',
  395. 'ghi 12.txt',
  396. 'zz',
  397. '15.txt',
  398. '15b.txt'
  399. ];
  400. a.sort(OC.Util.naturalSortCompare);
  401. expect(a).toEqual([
  402. '15.txt',
  403. '15b.txt',
  404. '123.txt',
  405. '124.txt',
  406. 'abc',
  407. 'abc1',
  408. 'abc2',
  409. 'abc10',
  410. 'abc12',
  411. 'def.txt',
  412. 'def (1).txt',
  413. 'def (2).txt',
  414. 'def (10).txt',
  415. 'def (12).txt',
  416. 'ghi.txt',
  417. 'ghi 1.txt',
  418. 'ghi 2.txt',
  419. 'ghi 10.txt',
  420. 'ghi 12.txt',
  421. 'z',
  422. 'za',
  423. 'zz'
  424. ]);
  425. });
  426. it('sorts with chinese characters', function() {
  427. var a = [
  428. '十.txt',
  429. '一.txt',
  430. '二.txt',
  431. '十 2.txt',
  432. '三.txt',
  433. '四.txt',
  434. 'abc.txt',
  435. '五.txt',
  436. '七.txt',
  437. '八.txt',
  438. '九.txt',
  439. '六.txt',
  440. '十一.txt',
  441. '波.txt',
  442. '破.txt',
  443. '莫.txt',
  444. '啊.txt',
  445. '123.txt'
  446. ];
  447. a.sort(OC.Util.naturalSortCompare);
  448. expect(a).toEqual([
  449. '123.txt',
  450. 'abc.txt',
  451. '一.txt',
  452. '七.txt',
  453. '三.txt',
  454. '九.txt',
  455. '二.txt',
  456. '五.txt',
  457. '八.txt',
  458. '六.txt',
  459. '十.txt',
  460. '十 2.txt',
  461. '十一.txt',
  462. '啊.txt',
  463. '四.txt',
  464. '波.txt',
  465. '破.txt',
  466. '莫.txt'
  467. ]);
  468. });
  469. cit('sorts with umlauts', function() {
  470. var a = [
  471. 'öh.txt',
  472. 'Äh.txt',
  473. 'oh.txt',
  474. 'Üh 2.txt',
  475. 'Üh.txt',
  476. 'ah.txt',
  477. 'Öh.txt',
  478. 'uh.txt',
  479. 'üh.txt',
  480. 'äh.txt'
  481. ];
  482. a.sort(OC.Util.naturalSortCompare);
  483. expect(a).toEqual([
  484. 'ah.txt',
  485. 'äh.txt',
  486. 'Äh.txt',
  487. 'oh.txt',
  488. 'öh.txt',
  489. 'Öh.txt',
  490. 'uh.txt',
  491. 'üh.txt',
  492. 'Üh.txt',
  493. 'Üh 2.txt'
  494. ]);
  495. });
  496. });
  497. describe('Plugins', function() {
  498. var plugin;
  499. beforeEach(function() {
  500. plugin = {
  501. name: 'Some name',
  502. attach: function(obj) {
  503. obj.attached = true;
  504. },
  505. detach: function(obj) {
  506. obj.attached = false;
  507. }
  508. };
  509. OC.Plugins.register('OC.Test.SomeName', plugin);
  510. });
  511. it('attach plugin to object', function() {
  512. var obj = {something: true};
  513. OC.Plugins.attach('OC.Test.SomeName', obj);
  514. expect(obj.attached).toEqual(true);
  515. OC.Plugins.detach('OC.Test.SomeName', obj);
  516. expect(obj.attached).toEqual(false);
  517. });
  518. it('only call handler for target name', function() {
  519. var obj = {something: true};
  520. OC.Plugins.attach('OC.Test.SomeOtherName', obj);
  521. expect(obj.attached).not.toBeDefined();
  522. OC.Plugins.detach('OC.Test.SomeOtherName', obj);
  523. expect(obj.attached).not.toBeDefined();
  524. });
  525. });
  526. describe('Notifications', function() {
  527. var showHtmlSpy;
  528. var clock;
  529. /**
  530. * Returns the HTML or plain text of the given notification row.
  531. *
  532. * This is needed to ignore the close button that is added to the
  533. * notification row after the text.
  534. */
  535. var getNotificationText = function($node) {
  536. return $node.contents()[0].outerHTML ||
  537. $node.contents()[0].nodeValue;
  538. }
  539. beforeEach(function() {
  540. clock = sinon.useFakeTimers();
  541. });
  542. afterEach(function() {
  543. // jump past animations
  544. clock.tick(10000);
  545. clock.restore();
  546. $('body .toastify').remove();
  547. });
  548. describe('showTemporary', function() {
  549. it('shows a plain text notification with default timeout', function() {
  550. OC.Notification.showTemporary('My notification test');
  551. var $row = $('body .toastify');
  552. expect($row.length).toEqual(1);
  553. expect(getNotificationText($row)).toEqual('My notification test');
  554. });
  555. it('shows a HTML notification with default timeout', function() {
  556. OC.Notification.showTemporary('<a>My notification test</a>', { isHTML: true });
  557. var $row = $('body .toastify');
  558. expect($row.length).toEqual(1);
  559. expect(getNotificationText($row)).toEqual('<a>My notification test</a>');
  560. });
  561. it('hides itself after 7 seconds', function() {
  562. OC.Notification.showTemporary('');
  563. var $row = $('body .toastify');
  564. expect($row.length).toEqual(1);
  565. // travel in time +7000 milliseconds
  566. clock.tick(7500);
  567. $row = $('body .toastify');
  568. expect($row.length).toEqual(0);
  569. });
  570. it('hides itself after a given time', function() {
  571. OC.Notification.showTemporary('', {timeout: 10000});
  572. var $row = $('body .toastify');
  573. expect($row.length).toEqual(1);
  574. // travel in time +7000 milliseconds
  575. clock.tick(7500);
  576. $row = $('body .toastify');
  577. expect($row.length).toEqual(1);
  578. // travel in time another 4000 milliseconds
  579. clock.tick(4000);
  580. $row = $('body .toastify');
  581. expect($row.length).toEqual(0);
  582. });
  583. });
  584. describe('show', function() {
  585. it('hides itself after a given time', function() {
  586. OC.Notification.show('', {timeout: 10000});
  587. var $row = $('body .toastify');
  588. expect($row.length).toEqual(1);
  589. clock.tick(11500);
  590. $row = $('body .toastify');
  591. expect($row.length).toEqual(0);
  592. });
  593. it('does not hide itself if no timeout given to show', function() {
  594. OC.Notification.show('');
  595. var $row = $('body .toastify');
  596. expect($row.length).toEqual(1);
  597. // travel in time +1000 seconds
  598. clock.tick(1000000);
  599. $row = $('body .toastify');
  600. expect($row.length).toEqual(1);
  601. });
  602. });
  603. describe('showHtml', function() {
  604. it('hides itself after a given time', function() {
  605. OC.Notification.showHtml('<p></p>', {timeout: 10000});
  606. var $row = $('body .toastify');
  607. expect($row.length).toEqual(1);
  608. clock.tick(11500);
  609. $row = $('body .toastify');
  610. expect($row.length).toEqual(0);
  611. });
  612. it('does not hide itself if no timeout given to show', function() {
  613. OC.Notification.showHtml('<p></p>');
  614. var $row = $('body .toastify');
  615. expect($row.length).toEqual(1);
  616. // travel in time +1000 seconds
  617. clock.tick(1000000);
  618. $row = $('body .toastify');
  619. expect($row.length).toEqual(1);
  620. });
  621. });
  622. describe('hide', function() {
  623. it('hides a temporary notification before its timeout expires', function() {
  624. var hideCallback = sinon.spy();
  625. var notification = OC.Notification.showTemporary('');
  626. var $row = $('body .toastify');
  627. expect($row.length).toEqual(1);
  628. OC.Notification.hide(notification, hideCallback);
  629. // Give time to the hide animation to finish
  630. clock.tick(1000);
  631. $row = $('body .toastify');
  632. expect($row.length).toEqual(0);
  633. expect(hideCallback.calledOnce).toEqual(true);
  634. });
  635. it('hides a notification before its timeout expires', function() {
  636. var hideCallback = sinon.spy();
  637. var notification = OC.Notification.show('', {timeout: 10000});
  638. var $row = $('body .toastify');
  639. expect($row.length).toEqual(1);
  640. OC.Notification.hide(notification, hideCallback);
  641. // Give time to the hide animation to finish
  642. clock.tick(1000);
  643. $row = $('body .toastify');
  644. expect($row.length).toEqual(0);
  645. expect(hideCallback.calledOnce).toEqual(true);
  646. });
  647. it('hides a notification without timeout', function() {
  648. var hideCallback = sinon.spy();
  649. var notification = OC.Notification.show('');
  650. var $row = $('body .toastify');
  651. expect($row.length).toEqual(1);
  652. OC.Notification.hide(notification, hideCallback);
  653. // Give time to the hide animation to finish
  654. clock.tick(1000);
  655. $row = $('body .toastify');
  656. expect($row.length).toEqual(0);
  657. expect(hideCallback.calledOnce).toEqual(true);
  658. });
  659. });
  660. it('cumulates several notifications', function() {
  661. var $row1 = OC.Notification.showTemporary('One');
  662. var $row2 = OC.Notification.showTemporary('Two', {timeout: 2000});
  663. var $row3 = OC.Notification.showTemporary('Three');
  664. var $el = $('body');
  665. var $rows = $el.find('.toastify');
  666. expect($rows.length).toEqual(3);
  667. expect($rows.eq(0).is($row3)).toEqual(true);
  668. expect($rows.eq(1).is($row2)).toEqual(true);
  669. expect($rows.eq(2).is($row1)).toEqual(true);
  670. clock.tick(3000);
  671. $rows = $el.find('.toastify');
  672. expect($rows.length).toEqual(2);
  673. expect($rows.eq(0).is($row3)).toEqual(true);
  674. expect($rows.eq(1).is($row1)).toEqual(true);
  675. });
  676. it('hides the given notification when calling hide with argument', function() {
  677. var $row1 = OC.Notification.show('One');
  678. var $row2 = OC.Notification.show('Two');
  679. var $el = $('body');
  680. var $rows = $el.find('.toastify');
  681. expect($rows.length).toEqual(2);
  682. OC.Notification.hide($row2);
  683. clock.tick(3000);
  684. $rows = $el.find('.toastify');
  685. expect($rows.length).toEqual(1);
  686. expect($rows.eq(0).is($row1)).toEqual(true);
  687. });
  688. });
  689. describe('global ajax errors', function() {
  690. var reloadStub, ajaxErrorStub, clock;
  691. var notificationStub;
  692. var waitTimeMs = 6500;
  693. var oldCurrentUser;
  694. beforeEach(function() {
  695. oldCurrentUser = OC.currentUser;
  696. OC.currentUser = 'dummy';
  697. clock = sinon.useFakeTimers();
  698. reloadStub = sinon.stub(OC, 'reload');
  699. notificationStub = sinon.stub(OC.Notification, 'show');
  700. // unstub the error processing method
  701. ajaxErrorStub = OC._processAjaxError;
  702. ajaxErrorStub.restore();
  703. window.initCore();
  704. });
  705. afterEach(function() {
  706. OC.currentUser = oldCurrentUser;
  707. reloadStub.restore();
  708. notificationStub.restore();
  709. clock.restore();
  710. });
  711. it('reloads current page in case of auth error', function() {
  712. var dataProvider = [
  713. [200, false],
  714. [400, false],
  715. [0, false],
  716. [401, true],
  717. [302, true],
  718. [303, true],
  719. [307, true]
  720. ];
  721. for (var i = 0; i < dataProvider.length; i++) {
  722. var xhr = { status: dataProvider[i][0] };
  723. var expectedCall = dataProvider[i][1];
  724. reloadStub.reset();
  725. OC._reloadCalled = false;
  726. $(document).trigger(new $.Event('ajaxError'), xhr);
  727. // trigger timers
  728. clock.tick(waitTimeMs);
  729. if (expectedCall) {
  730. expect(reloadStub.calledOnce).toEqual(true);
  731. } else {
  732. expect(reloadStub.notCalled).toEqual(true);
  733. }
  734. }
  735. });
  736. it('reload only called once in case of auth error', function() {
  737. var xhr = { status: 401 };
  738. $(document).trigger(new $.Event('ajaxError'), xhr);
  739. $(document).trigger(new $.Event('ajaxError'), xhr);
  740. // trigger timers
  741. clock.tick(waitTimeMs);
  742. expect(reloadStub.calledOnce).toEqual(true);
  743. });
  744. it('does not reload the page if the user was navigating away', function() {
  745. var xhr = { status: 0 };
  746. OC._userIsNavigatingAway = true;
  747. clock.tick(100);
  748. $(document).trigger(new $.Event('ajaxError'), xhr);
  749. clock.tick(waitTimeMs);
  750. expect(reloadStub.notCalled).toEqual(true);
  751. });
  752. it('displays notification', function() {
  753. var xhr = { status: 401 };
  754. notificationUpdateStub = sinon.stub(OC.Notification, 'showUpdate');
  755. $(document).trigger(new $.Event('ajaxError'), xhr);
  756. clock.tick(waitTimeMs);
  757. expect(notificationUpdateStub.notCalled).toEqual(false);
  758. });
  759. it('shows a temporary notification if the connection is lost', function() {
  760. var xhr = { status: 0 };
  761. spyOn(OC, '_ajaxConnectionLostHandler');
  762. $(document).trigger(new $.Event('ajaxError'), xhr);
  763. clock.tick(101);
  764. expect(OC._ajaxConnectionLostHandler.calls.count()).toBe(1);
  765. });
  766. });
  767. describe('Snapper', function() {
  768. var snapConstructorStub;
  769. var snapperStub;
  770. var clock;
  771. beforeEach(function() {
  772. snapConstructorStub = sinon.stub(window, 'Snap');
  773. snapperStub = {};
  774. snapperStub.enable = sinon.stub();
  775. snapperStub.disable = sinon.stub();
  776. snapperStub.close = sinon.stub();
  777. snapperStub.on = sinon.stub();
  778. snapperStub.state = sinon.stub().returns({
  779. state: sinon.stub()
  780. });
  781. snapConstructorStub.returns(snapperStub);
  782. clock = sinon.useFakeTimers();
  783. // _.now could have been set to Date.now before Sinon replaced it
  784. // with a fake version, so _.now must be stubbed to ensure that the
  785. // fake Date.now will be called instead of the original one.
  786. _.now = sinon.stub(_, 'now').callsFake(function() {
  787. return new Date().getTime();
  788. });
  789. $('#testArea').append('<div id="app-navigation">The navigation bar</div><div id="app-content">Content</div>');
  790. });
  791. afterEach(function() {
  792. snapConstructorStub.restore();
  793. clock.restore();
  794. _.now.restore();
  795. // Remove the event handler for the resize event added to the window
  796. // due to calling window.initCore() when there is an #app-navigation
  797. // element.
  798. $(window).off('resize');
  799. viewport.reset();
  800. });
  801. it('is enabled on a narrow screen', function() {
  802. viewport.set(480);
  803. window.initCore();
  804. expect(snapConstructorStub.calledOnce).toBe(true);
  805. expect(snapperStub.enable.calledOnce).toBe(true);
  806. expect(snapperStub.disable.called).toBe(false);
  807. });
  808. it('is disabled when disallowing the gesture on a narrow screen', function() {
  809. viewport.set(480);
  810. window.initCore();
  811. expect(snapperStub.enable.calledOnce).toBe(true);
  812. expect(snapperStub.disable.called).toBe(false);
  813. expect(snapperStub.close.called).toBe(false);
  814. OC.disallowNavigationBarSlideGesture();
  815. expect(snapperStub.enable.calledOnce).toBe(true);
  816. expect(snapperStub.disable.calledOnce).toBe(true);
  817. expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
  818. expect(snapperStub.close.called).toBe(false);
  819. });
  820. it('is not disabled again when disallowing the gesture twice on a narrow screen', function() {
  821. viewport.set(480);
  822. window.initCore();
  823. expect(snapperStub.enable.calledOnce).toBe(true);
  824. expect(snapperStub.disable.called).toBe(false);
  825. expect(snapperStub.close.called).toBe(false);
  826. OC.disallowNavigationBarSlideGesture();
  827. expect(snapperStub.enable.calledOnce).toBe(true);
  828. expect(snapperStub.disable.calledOnce).toBe(true);
  829. expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
  830. expect(snapperStub.close.called).toBe(false);
  831. OC.disallowNavigationBarSlideGesture();
  832. expect(snapperStub.enable.calledOnce).toBe(true);
  833. expect(snapperStub.disable.calledOnce).toBe(true);
  834. expect(snapperStub.close.called).toBe(false);
  835. });
  836. it('is enabled when allowing the gesture after disallowing it on a narrow screen', function() {
  837. viewport.set(480);
  838. window.initCore();
  839. expect(snapperStub.enable.calledOnce).toBe(true);
  840. expect(snapperStub.disable.called).toBe(false);
  841. expect(snapperStub.close.called).toBe(false);
  842. OC.disallowNavigationBarSlideGesture();
  843. expect(snapperStub.enable.calledOnce).toBe(true);
  844. expect(snapperStub.disable.calledOnce).toBe(true);
  845. expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
  846. expect(snapperStub.close.called).toBe(false);
  847. OC.allowNavigationBarSlideGesture();
  848. expect(snapperStub.enable.calledTwice).toBe(true);
  849. expect(snapperStub.disable.calledOnce).toBe(true);
  850. expect(snapperStub.close.called).toBe(false);
  851. });
  852. it('is not enabled again when allowing the gesture twice after disallowing it on a narrow screen', function() {
  853. viewport.set(480);
  854. window.initCore();
  855. expect(snapperStub.enable.calledOnce).toBe(true);
  856. expect(snapperStub.disable.called).toBe(false);
  857. expect(snapperStub.close.called).toBe(false);
  858. OC.disallowNavigationBarSlideGesture();
  859. expect(snapperStub.enable.calledOnce).toBe(true);
  860. expect(snapperStub.disable.calledOnce).toBe(true);
  861. expect(snapperStub.disable.alwaysCalledWithExactly(true)).toBe(true);
  862. expect(snapperStub.close.called).toBe(false);
  863. OC.allowNavigationBarSlideGesture();
  864. expect(snapperStub.enable.calledTwice).toBe(true);
  865. expect(snapperStub.disable.calledOnce).toBe(true);
  866. expect(snapperStub.close.called).toBe(false);
  867. OC.allowNavigationBarSlideGesture();
  868. expect(snapperStub.enable.calledTwice).toBe(true);
  869. expect(snapperStub.disable.calledOnce).toBe(true);
  870. expect(snapperStub.close.called).toBe(false);
  871. });
  872. it('is disabled on a wide screen', function() {
  873. viewport.set(1280);
  874. window.initCore();
  875. expect(snapConstructorStub.calledOnce).toBe(true);
  876. expect(snapperStub.enable.called).toBe(false);
  877. expect(snapperStub.disable.calledOnce).toBe(true);
  878. });
  879. it('is not disabled again when disallowing the gesture on a wide screen', function() {
  880. viewport.set(1280);
  881. window.initCore();
  882. expect(snapperStub.enable.called).toBe(false);
  883. expect(snapperStub.disable.calledOnce).toBe(true);
  884. expect(snapperStub.close.calledOnce).toBe(true);
  885. OC.disallowNavigationBarSlideGesture();
  886. expect(snapperStub.enable.called).toBe(false);
  887. expect(snapperStub.disable.calledOnce).toBe(true);
  888. expect(snapperStub.close.calledOnce).toBe(true);
  889. });
  890. it('is not enabled when allowing the gesture after disallowing it on a wide screen', function() {
  891. viewport.set(1280);
  892. window.initCore();
  893. expect(snapperStub.enable.called).toBe(false);
  894. expect(snapperStub.disable.calledOnce).toBe(true);
  895. expect(snapperStub.close.calledOnce).toBe(true);
  896. OC.disallowNavigationBarSlideGesture();
  897. expect(snapperStub.enable.called).toBe(false);
  898. expect(snapperStub.disable.calledOnce).toBe(true);
  899. expect(snapperStub.close.calledOnce).toBe(true);
  900. OC.allowNavigationBarSlideGesture();
  901. expect(snapperStub.enable.called).toBe(false);
  902. expect(snapperStub.disable.calledOnce).toBe(true);
  903. expect(snapperStub.close.calledOnce).toBe(true);
  904. });
  905. it('is enabled when resizing to a narrow screen', function() {
  906. viewport.set(1280);
  907. window.initCore();
  908. expect(snapperStub.enable.called).toBe(false);
  909. expect(snapperStub.disable.calledOnce).toBe(true);
  910. viewport.set(480);
  911. // Setting the viewport width does not automatically trigger a
  912. // resize.
  913. $(window).resize();
  914. // The resize handler is debounced to be executed a few milliseconds
  915. // after the resize event.
  916. clock.tick(1000);
  917. expect(snapperStub.enable.calledOnce).toBe(true);
  918. expect(snapperStub.disable.calledOnce).toBe(true);
  919. });
  920. it('is not enabled when resizing to a narrow screen after disallowing the gesture', function() {
  921. viewport.set(1280);
  922. window.initCore();
  923. expect(snapperStub.enable.called).toBe(false);
  924. expect(snapperStub.disable.calledOnce).toBe(true);
  925. OC.disallowNavigationBarSlideGesture();
  926. expect(snapperStub.enable.called).toBe(false);
  927. expect(snapperStub.disable.calledOnce).toBe(true);
  928. viewport.set(480);
  929. // Setting the viewport width does not automatically trigger a
  930. // resize.
  931. $(window).resize();
  932. // The resize handler is debounced to be executed a few milliseconds
  933. // after the resize event.
  934. clock.tick(1000);
  935. expect(snapperStub.enable.called).toBe(false);
  936. expect(snapperStub.disable.calledOnce).toBe(true);
  937. });
  938. it('is enabled when resizing to a narrow screen after disallowing the gesture and allowing it', function() {
  939. viewport.set(1280);
  940. window.initCore();
  941. expect(snapperStub.enable.called).toBe(false);
  942. expect(snapperStub.disable.calledOnce).toBe(true);
  943. OC.disallowNavigationBarSlideGesture();
  944. expect(snapperStub.enable.called).toBe(false);
  945. expect(snapperStub.disable.calledOnce).toBe(true);
  946. OC.allowNavigationBarSlideGesture();
  947. expect(snapperStub.enable.called).toBe(false);
  948. expect(snapperStub.disable.calledOnce).toBe(true);
  949. viewport.set(480);
  950. // Setting the viewport width does not automatically trigger a
  951. // resize.
  952. $(window).resize();
  953. // The resize handler is debounced to be executed a few milliseconds
  954. // after the resize event.
  955. clock.tick(1000);
  956. expect(snapperStub.enable.calledOnce).toBe(true);
  957. expect(snapperStub.disable.calledOnce).toBe(true);
  958. });
  959. it('is enabled when allowing the gesture after disallowing it and resizing to a narrow screen', function() {
  960. viewport.set(1280);
  961. window.initCore();
  962. expect(snapperStub.enable.called).toBe(false);
  963. expect(snapperStub.disable.calledOnce).toBe(true);
  964. OC.disallowNavigationBarSlideGesture();
  965. expect(snapperStub.enable.called).toBe(false);
  966. expect(snapperStub.disable.calledOnce).toBe(true);
  967. viewport.set(480);
  968. // Setting the viewport width does not automatically trigger a
  969. // resize.
  970. $(window).resize();
  971. // The resize handler is debounced to be executed a few milliseconds
  972. // after the resize event.
  973. clock.tick(1000);
  974. expect(snapperStub.enable.called).toBe(false);
  975. expect(snapperStub.disable.calledOnce).toBe(true);
  976. OC.allowNavigationBarSlideGesture();
  977. expect(snapperStub.enable.calledOnce).toBe(true);
  978. expect(snapperStub.disable.calledOnce).toBe(true);
  979. });
  980. it('is disabled when disallowing the gesture after disallowing it, resizing to a narrow screen and allowing it', function() {
  981. viewport.set(1280);
  982. window.initCore();
  983. expect(snapperStub.enable.called).toBe(false);
  984. expect(snapperStub.disable.calledOnce).toBe(true);
  985. OC.disallowNavigationBarSlideGesture();
  986. expect(snapperStub.enable.called).toBe(false);
  987. expect(snapperStub.disable.calledOnce).toBe(true);
  988. viewport.set(480);
  989. // Setting the viewport width does not automatically trigger a
  990. // resize.
  991. $(window).resize();
  992. // The resize handler is debounced to be executed a few milliseconds
  993. // after the resize event.
  994. clock.tick(1000);
  995. expect(snapperStub.enable.called).toBe(false);
  996. expect(snapperStub.disable.calledOnce).toBe(true);
  997. OC.allowNavigationBarSlideGesture();
  998. expect(snapperStub.enable.calledOnce).toBe(true);
  999. expect(snapperStub.disable.calledOnce).toBe(true);
  1000. OC.disallowNavigationBarSlideGesture();
  1001. expect(snapperStub.enable.calledOnce).toBe(true);
  1002. expect(snapperStub.disable.calledTwice).toBe(true);
  1003. expect(snapperStub.disable.getCall(1).calledWithExactly(true)).toBe(true);
  1004. });
  1005. it('is disabled when resizing to a wide screen', function() {
  1006. viewport.set(480);
  1007. window.initCore();
  1008. expect(snapperStub.enable.calledOnce).toBe(true);
  1009. expect(snapperStub.disable.called).toBe(false);
  1010. expect(snapperStub.close.called).toBe(false);
  1011. viewport.set(1280);
  1012. // Setting the viewport width does not automatically trigger a
  1013. // resize.
  1014. $(window).resize();
  1015. // The resize handler is debounced to be executed a few milliseconds
  1016. // after the resize event.
  1017. clock.tick(1000);
  1018. expect(snapperStub.enable.calledOnce).toBe(true);
  1019. expect(snapperStub.disable.calledOnce).toBe(true);
  1020. expect(snapperStub.close.calledOnce).toBe(true);
  1021. });
  1022. it('is not disabled again when disallowing the gesture after resizing to a wide screen', function() {
  1023. viewport.set(480);
  1024. window.initCore();
  1025. expect(snapperStub.enable.calledOnce).toBe(true);
  1026. expect(snapperStub.disable.called).toBe(false);
  1027. expect(snapperStub.close.called).toBe(false);
  1028. viewport.set(1280);
  1029. // Setting the viewport width does not automatically trigger a
  1030. // resize.
  1031. $(window).resize();
  1032. // The resize handler is debounced to be executed a few milliseconds
  1033. // after the resize event.
  1034. clock.tick(1000);
  1035. expect(snapperStub.enable.calledOnce).toBe(true);
  1036. expect(snapperStub.disable.calledOnce).toBe(true);
  1037. expect(snapperStub.close.calledOnce).toBe(true);
  1038. OC.disallowNavigationBarSlideGesture();
  1039. expect(snapperStub.enable.calledOnce).toBe(true);
  1040. expect(snapperStub.disable.calledOnce).toBe(true);
  1041. expect(snapperStub.close.calledOnce).toBe(true);
  1042. });
  1043. it('is not enabled when allowing the gesture after disallowing it, resizing to a narrow screen and resizing to a wide screen', function() {
  1044. viewport.set(1280);
  1045. window.initCore();
  1046. expect(snapperStub.enable.called).toBe(false);
  1047. expect(snapperStub.disable.calledOnce).toBe(true);
  1048. expect(snapperStub.close.calledOnce).toBe(true);
  1049. OC.disallowNavigationBarSlideGesture();
  1050. expect(snapperStub.enable.called).toBe(false);
  1051. expect(snapperStub.disable.calledOnce).toBe(true);
  1052. expect(snapperStub.close.calledOnce).toBe(true);
  1053. viewport.set(480);
  1054. // Setting the viewport width does not automatically trigger a
  1055. // resize.
  1056. $(window).resize();
  1057. // The resize handler is debounced to be executed a few milliseconds
  1058. // after the resize event.
  1059. clock.tick(1000);
  1060. expect(snapperStub.enable.called).toBe(false);
  1061. expect(snapperStub.disable.calledOnce).toBe(true);
  1062. expect(snapperStub.close.calledOnce).toBe(true);
  1063. viewport.set(1280);
  1064. $(window).resize();
  1065. clock.tick(1000);
  1066. expect(snapperStub.enable.called).toBe(false);
  1067. expect(snapperStub.disable.calledTwice).toBe(true);
  1068. expect(snapperStub.close.calledTwice).toBe(true);
  1069. OC.allowNavigationBarSlideGesture();
  1070. expect(snapperStub.enable.called).toBe(false);
  1071. expect(snapperStub.disable.calledTwice).toBe(true);
  1072. expect(snapperStub.close.calledTwice).toBe(true);
  1073. });
  1074. });
  1075. });