TopNav.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. 'use strict';
  2. require('../common');
  3. const fc = require('fast-check');
  4. function query(selector) {
  5. if (selector.startsWith('#')) {
  6. return document.getElementById(selector.slice(1));
  7. }
  8. return document.querySelector(selector);
  9. }
  10. describe('TopNav', function () {
  11. describe('showViewButtons & hideViewButtons', function () {
  12. before(function () {
  13. cleanup();
  14. });
  15. it(
  16. 'displays & hides navigation elements for viewing an existing document',
  17. function () {
  18. let results = [];
  19. document.documentElement.innerHTML =
  20. `<nav class="navbar navbar-inverse navbar-static-top">
  21. <div id="navbar" class="navbar-collapse collapse">
  22. <ul class="nav navbar-nav"><li><button id="newbutton" type="button" class="hidden btn btn-warning navbar-btn">
  23. <span class="glyphicon glyphicon-file" aria-hidden="true">
  24. </span> New</button><button id="clonebutton" type="button" class="hidden btn btn-warning navbar-btn">
  25. <span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> Clone</button>
  26. <button id="rawtextbutton" type="button" class="hidden btn btn-warning navbar-btn">
  27. <span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> Raw text</button>
  28. <button id="downloadtextbutton" type="button" class="hidden btn btn-default navbar-btn"></button>
  29. <button id="qrcodelink" type="button" data-toggle="modal" data-target="#qrcodemodal" class="hidden btn btn-warning navbar-btn"/>
  30. <span class="glyphicon glyphicon-qrcode" aria-hidden="true"></span> QR code</button></li></ul></div>
  31. </nav>`;
  32. PrivateBin.TopNav.init();
  33. results.push(
  34. query('#newbutton').classList.contains('hidden') &&
  35. query('#clonebutton').classList.contains('hidden') &&
  36. query('#rawtextbutton').classList.contains('hidden') &&
  37. query('#qrcodelink').classList.contains('hidden')
  38. );
  39. PrivateBin.TopNav.showViewButtons();
  40. results.push(
  41. !query('#newbutton').classList.contains('hidden') &&
  42. !query('#clonebutton').classList.contains('hidden') &&
  43. !query('#rawtextbutton').classList.contains('hidden') &&
  44. !query('#qrcodelink').classList.contains('hidden')
  45. );
  46. PrivateBin.TopNav.hideViewButtons();
  47. results.push(
  48. query('#newbutton').classList.contains('hidden') &&
  49. query('#clonebutton').classList.contains('hidden') &&
  50. query('#rawtextbutton').classList.contains('hidden') &&
  51. query('#qrcodelink').classList.contains('hidden')
  52. );
  53. cleanup();
  54. const result = results.every(element => element);
  55. if (!result) {
  56. console.log(results);
  57. }
  58. assert.ok(result);
  59. }
  60. );
  61. });
  62. describe('showCreateButtons & hideCreateButtons', function () {
  63. before(function () {
  64. cleanup();
  65. });
  66. it(
  67. 'displays & hides navigation elements for creating a document',
  68. function () { // eslint-disable-line complexity
  69. let results = [];
  70. document.documentElement.innerHTML =
  71. '<nav><div id="navbar"><ul><li><button id="newbutton" ' +
  72. 'type="button" class="hidden">New</button></li><li><a ' +
  73. 'id="expiration" href="#" class="hidden">Expiration</a>' +
  74. '</li><li><div id="burnafterreadingoption" class="hidden">' +
  75. 'Burn after reading</div></li><li><div id="opendiscussion' +
  76. 'option" class="hidden">Open discussion</div></li><li>' +
  77. '<div id="password" class="hidden">Password</div></li>' +
  78. '<li id="attach" class="hidden">Attach a file</li><li>' +
  79. '<a id="formatter" href="#" class="hidden">Format</a>' +
  80. '</li><li><button id="sendbutton" type="button" ' +
  81. 'class="hidden">Create</button></li></ul></div></nav>';
  82. PrivateBin.TopNav.init();
  83. results.push(
  84. query('#sendbutton').classList.contains('hidden') &&
  85. query('#expiration').classList.contains('hidden') &&
  86. query('#formatter').classList.contains('hidden') &&
  87. query('#burnafterreadingoption').classList.contains('hidden') &&
  88. query('#opendiscussionoption').classList.contains('hidden') &&
  89. query('#newbutton').classList.contains('hidden') &&
  90. query('#password').classList.contains('hidden') &&
  91. query('#attach').classList.contains('hidden')
  92. );
  93. PrivateBin.TopNav.showCreateButtons();
  94. results.push(
  95. !query('#sendbutton').classList.contains('hidden') &&
  96. !query('#expiration').classList.contains('hidden') &&
  97. !query('#formatter').classList.contains('hidden') &&
  98. !query('#burnafterreadingoption').classList.contains('hidden') &&
  99. !query('#opendiscussionoption').classList.contains('hidden') &&
  100. !query('#newbutton').classList.contains('hidden') &&
  101. !query('#password').classList.contains('hidden') &&
  102. !query('#attach').classList.contains('hidden')
  103. );
  104. PrivateBin.TopNav.hideCreateButtons();
  105. results.push(
  106. query('#sendbutton').classList.contains('hidden') &&
  107. query('#expiration').classList.contains('hidden') &&
  108. query('#formatter').classList.contains('hidden') &&
  109. query('#burnafterreadingoption').classList.contains('hidden') &&
  110. query('#opendiscussionoption').classList.contains('hidden') &&
  111. query('#newbutton').classList.contains('hidden') &&
  112. query('#password').classList.contains('hidden') &&
  113. query('#attach').classList.contains('hidden')
  114. );
  115. cleanup();
  116. const result = results.every(element => element);
  117. if (!result) {
  118. console.log(results);
  119. }
  120. assert.ok(result);
  121. }
  122. );
  123. });
  124. describe('send shortcut', function () {
  125. let sendCount;
  126. // builds the create-document navbar and stubs the actual send, so no
  127. // server interaction happens
  128. function setUpCreateMode() {
  129. cleanup();
  130. sendCount = 0;
  131. document.documentElement.innerHTML =
  132. '<nav><div id="navbar"><ul><li><button id="newbutton" ' +
  133. 'type="button" class="hidden">New</button></li><li><a ' +
  134. 'id="expiration" href="#" class="hidden">Expiration</a>' +
  135. '</li><li><div id="burnafterreadingoption" class="hidden">' +
  136. 'Burn after reading</div></li><li><div id="opendiscussion' +
  137. 'option" class="hidden">Open discussion</div></li><li>' +
  138. '<div id="password" class="hidden">Password</div></li>' +
  139. '<li id="attach" class="hidden">Attach a file</li><li>' +
  140. '<a id="formatter" href="#" class="hidden">Format</a>' +
  141. '</li><li><button id="sendbutton" type="button" ' +
  142. 'class="hidden">Create</button></li></ul></div></nav>';
  143. PrivateBin.PasteEncrypter.sendPaste = function () {
  144. sendCount++;
  145. };
  146. PrivateBin.TopNav.init();
  147. PrivateBin.TopNav.showCreateButtons();
  148. }
  149. // dispatches a keydown on the document, returns whether the default was prevented
  150. function pressKey(options) {
  151. const event = new window.KeyboardEvent('keydown', Object.assign(
  152. {bubbles: true, cancelable: true}, options
  153. ));
  154. document.dispatchEvent(event);
  155. return event.defaultPrevented;
  156. }
  157. afterEach(function () {
  158. cleanup();
  159. });
  160. it('sends the document on Ctrl+S and suppresses the browser save dialog', function () {
  161. setUpCreateMode();
  162. const prevented = pressKey({key: 's', code: 'KeyS', ctrlKey: true});
  163. assert.strictEqual(sendCount, 1);
  164. assert.ok(prevented);
  165. });
  166. it('sends the document on Cmd+S (macOS)', function () {
  167. setUpCreateMode();
  168. const prevented = pressKey({key: 's', code: 'KeyS', metaKey: true});
  169. assert.strictEqual(sendCount, 1);
  170. assert.ok(prevented);
  171. });
  172. it('sends on the physical S key of non-Latin keyboard layouts', function () {
  173. setUpCreateMode();
  174. // on a Russian layout the S key produces "ы"
  175. pressKey({key: 'ы', code: 'KeyS', ctrlKey: true});
  176. assert.strictEqual(sendCount, 1);
  177. });
  178. it('leaves Ctrl+S to the browser when viewing a document', function () {
  179. setUpCreateMode();
  180. PrivateBin.TopNav.hideCreateButtons();
  181. const prevented = pressKey({key: 's', code: 'KeyS', ctrlKey: true});
  182. assert.strictEqual(sendCount, 0);
  183. assert.ok(!prevented);
  184. });
  185. it('ignores S without Ctrl/Cmd, with AltGr (Ctrl+Alt), with Shift, or other keys', function () {
  186. setUpCreateMode();
  187. const prevented = [
  188. pressKey({key: 's', code: 'KeyS'}),
  189. pressKey({key: 's', code: 'KeyS', ctrlKey: true, altKey: true}),
  190. pressKey({key: 'S', code: 'KeyS', ctrlKey: true, shiftKey: true}),
  191. pressKey({key: 'd', code: 'KeyD', ctrlKey: true})
  192. ];
  193. assert.strictEqual(sendCount, 0);
  194. assert.deepStrictEqual(prevented, [false, false, false, false]);
  195. });
  196. });
  197. describe('showNewPasteButton', function () {
  198. before(function () {
  199. cleanup();
  200. });
  201. it(
  202. 'displays the button for creating a document',
  203. function () {
  204. let results = [];
  205. document.documentElement.innerHTML =
  206. '<nav><div id="navbar"><ul><li><button id="newbutton" type=' +
  207. '"button" class="hidden">New</button></li></ul></div></nav>';
  208. PrivateBin.TopNav.init();
  209. results.push(
  210. query('#newbutton').classList.contains('hidden')
  211. );
  212. PrivateBin.TopNav.showNewPasteButton();
  213. results.push(
  214. !query('#newbutton').classList.contains('hidden')
  215. );
  216. cleanup();
  217. const result = results.every(element => element);
  218. if (!result) {
  219. console.log(results);
  220. }
  221. assert.ok(result);
  222. }
  223. );
  224. });
  225. describe('hideCloneButton', function () {
  226. before(function () {
  227. cleanup();
  228. });
  229. it(
  230. 'hides the button for cloning a document',
  231. function () {
  232. let results = [];
  233. document.documentElement.innerHTML =
  234. '<nav><div id="navbar"><ul><li><button id="clonebutton" ' +
  235. 'type="button" class="btn btn-warning navbar-btn">' +
  236. '<span class="glyphicon glyphicon-duplicate" aria-hidden=' +
  237. '"true"></span> Clone</button></li></ul></div></nav>';
  238. PrivateBin.TopNav.init();
  239. results.push(
  240. !query('#clonebutton').classList.contains('hidden')
  241. );
  242. PrivateBin.TopNav.hideCloneButton();
  243. results.push(
  244. query('#clonebutton').classList.contains('hidden')
  245. );
  246. cleanup();
  247. const result = results.every(element => element);
  248. if (!result) {
  249. console.log(results);
  250. }
  251. assert.ok(result);
  252. }
  253. );
  254. });
  255. describe('hideRawButton', function () {
  256. before(function () {
  257. cleanup();
  258. });
  259. it(
  260. 'hides the raw text button',
  261. function () {
  262. let results = [];
  263. document.documentElement.innerHTML =
  264. '<nav><div id="navbar"><ul><li><button ' +
  265. 'id="rawtextbutton" type="button" class="btn ' +
  266. 'btn-warning navbar-btn"><span class="glyphicon ' +
  267. 'glyphicon-text-background" aria-hidden="true"></span> ' +
  268. 'Raw text</button></li></ul></div></nav>';
  269. PrivateBin.TopNav.init();
  270. results.push(
  271. !query('#rawtextbutton').classList.contains('hidden')
  272. );
  273. PrivateBin.TopNav.hideRawButton();
  274. results.push(
  275. query('#rawtextbutton').classList.contains('hidden')
  276. );
  277. cleanup();
  278. const result = results.every(element => element);
  279. if (!result) {
  280. console.log(results);
  281. }
  282. assert.ok(result);
  283. }
  284. );
  285. });
  286. describe('hideFileSelector', function () {
  287. before(function () {
  288. cleanup();
  289. });
  290. it(
  291. 'hides the file attachment selection button',
  292. function () {
  293. let results = [];
  294. document.documentElement.innerHTML =
  295. '<nav><div id="navbar"><ul><li id="attach" class="hidden ' +
  296. 'dropdown"><a href="#" class="dropdown-toggle" data-' +
  297. 'toggle="dropdown" role="button" aria-haspopup="true" ' +
  298. 'aria-expanded="false">Attach a file <span class="caret">' +
  299. '</span></a><ul class="dropdown-menu"><li id="filewrap">' +
  300. '<div><input type="file" id="file" name="file" /></div>' +
  301. '</li><li id="customattachment" class="hidden"></li><li>' +
  302. '<a id="fileremovebutton" href="#">Remove attachment</a>' +
  303. '</li></ul></li></ul></div></nav>';
  304. PrivateBin.TopNav.init();
  305. results.push(
  306. !query('#filewrap').classList.contains('hidden')
  307. );
  308. PrivateBin.TopNav.hideFileSelector();
  309. results.push(
  310. query('#filewrap').classList.contains('hidden')
  311. );
  312. cleanup();
  313. const result = results.every(element => element);
  314. if (!result) {
  315. console.log(results);
  316. }
  317. assert.ok(result);
  318. }
  319. );
  320. });
  321. describe('showCustomAttachment', function () {
  322. before(function () {
  323. cleanup();
  324. });
  325. it(
  326. 'display the custom file attachment',
  327. function () {
  328. let results = [];
  329. document.documentElement.innerHTML =
  330. '<nav><div id="navbar"><ul><li id="attach" class="hidden ' +
  331. 'dropdown"><a href="#" class="dropdown-toggle" data-' +
  332. 'toggle="dropdown" role="button" aria-haspopup="true" ' +
  333. 'aria-expanded="false">Attach a file <span class="caret">' +
  334. '</span></a><ul class="dropdown-menu"><li id="filewrap">' +
  335. '<div><input type="file" id="file" name="file" /></div>' +
  336. '</li><li id="customattachment" class="hidden"></li><li>' +
  337. '<a id="fileremovebutton" href="#">Remove attachment</a>' +
  338. '</li></ul></li></ul></div></nav>';
  339. PrivateBin.TopNav.init();
  340. results.push(
  341. query('#customattachment').classList.contains('hidden')
  342. );
  343. PrivateBin.TopNav.showCustomAttachment();
  344. results.push(
  345. !query('#customattachment').classList.contains('hidden')
  346. );
  347. cleanup();
  348. const result = results.every(element => element);
  349. if (!result) {
  350. console.log(results);
  351. }
  352. assert.ok(result);
  353. }
  354. );
  355. });
  356. describe('collapseBar', function () {
  357. before(function () {
  358. cleanup();
  359. });
  360. it(
  361. 'collapses the navigation when displayed on a small screen',
  362. function () {
  363. let results = [];
  364. document.documentElement.innerHTML =
  365. '<nav><div class="navbar-header"><button type="button" ' +
  366. 'class="navbar-toggle collapsed" data-toggle="collapse" ' +
  367. 'data-target="#navbar" aria-expanded="false" aria-controls' +
  368. '="navbar">Toggle navigation</button><a class="reloadlink ' +
  369. 'navbar-brand" href=""><img alt="PrivateBin" ' +
  370. 'src="img/icon.svg" width="38" /></a></div><div ' +
  371. 'id="navbar"><ul><li><button id="newbutton" type=' +
  372. '"button" class="hidden">New</button></li></ul></div></nav>';
  373. PrivateBin.TopNav.init();
  374. results.push(
  375. query('.navbar-toggle').classList.contains('collapsed') &&
  376. query('#navbar').getAttribute('aria-expanded') !== 'true'
  377. );
  378. PrivateBin.TopNav.collapseBar();
  379. results.push(
  380. query('.navbar-toggle').classList.contains('collapsed') &&
  381. query('#navbar').getAttribute('aria-expanded') !== 'true'
  382. );
  383. /*
  384. with the upgrade for bootstrap-3.3.7.js to bootstrap-3.4.1.js
  385. the mobile interface detection changed to check if the
  386. ontouchstart event exists, which broke this section of the test
  387. const toggleBtn = document.querySelector('.navbar-toggle');
  388. toggleBtn.dispatchEvent(new MouseEvent('click'));
  389. results.push(
  390. !toggleBtn.classList.contains('collapsed') &&
  391. document.getElementById('navbar').getAttribute('aria-expanded') == 'true'
  392. );
  393. PrivateBin.TopNav.collapseBar();
  394. results.push(
  395. document.querySelector('.navbar-toggle').classList.contains('collapsed') &&
  396. document.getElementById('navbar').getAttribute('aria-expanded') == 'false'
  397. );
  398. */
  399. const result = results.every(element => element);
  400. if (!result) {
  401. console.log(results);
  402. }
  403. assert.ok(result);
  404. }
  405. );
  406. });
  407. describe('resetInput', function () {
  408. before(function () {
  409. cleanup();
  410. });
  411. it(
  412. 'reset inputs to defaults (options off)',
  413. function () {
  414. let results = [];
  415. document.documentElement.innerHTML =
  416. '<nav><div id="navbar"><ul><li id="burnafterreadingoption" ' +
  417. 'class="hidden"><label><input type="checkbox" ' +
  418. 'id="burnafterreading" name="burnafterreading" /> ' +
  419. 'Burn after reading</label></li><li id="opendiscussionoption" ' +
  420. 'class="hidden"><label><input type="checkbox" ' +
  421. 'id="opendiscussion" name="opendiscussion" /> ' +
  422. 'Open discussion</label></li></ul></div></nav>';
  423. PrivateBin.TopNav.init();
  424. results.push(
  425. !PrivateBin.TopNav.getBurnAfterReading()
  426. );
  427. results.push(
  428. !PrivateBin.TopNav.getOpenDiscussion()
  429. );
  430. query('#burnafterreading').checked = true;
  431. query('#opendiscussion').checked = true;
  432. results.push(
  433. PrivateBin.TopNav.getBurnAfterReading()
  434. );
  435. results.push(
  436. PrivateBin.TopNav.getOpenDiscussion()
  437. );
  438. PrivateBin.TopNav.resetInput();
  439. results.push(
  440. !PrivateBin.TopNav.getBurnAfterReading()
  441. );
  442. results.push(
  443. !PrivateBin.TopNav.getOpenDiscussion()
  444. );
  445. cleanup();
  446. const result = results.every(element => element);
  447. if (!result) {
  448. console.log(results);
  449. }
  450. assert.ok(result);
  451. }
  452. );
  453. it(
  454. 'reset inputs to defaults (burnafterreading on)',
  455. function () {
  456. let results = [];
  457. document.documentElement.innerHTML =
  458. '<nav><div id="navbar"><ul><li id="burnafterreadingoption" ' +
  459. 'class="hidden"><label><input type="checkbox" ' +
  460. 'id="burnafterreading" name="burnafterreading" checked="checked" /> ' +
  461. 'Burn after reading</label></li><li id="opendiscussionoption" ' +
  462. 'class="hidden"><label><input type="checkbox" ' +
  463. 'id="opendiscussion" name="opendiscussion" checked="checked" /> ' +
  464. 'Open discussion</label></li></ul></div></nav>';
  465. PrivateBin.TopNav.init();
  466. results.push(
  467. PrivateBin.TopNav.getBurnAfterReading()
  468. );
  469. results.push(
  470. !PrivateBin.TopNav.getOpenDiscussion()
  471. );
  472. query('#burnafterreading').checked = false;
  473. query('#burnafterreading').removeAttribute('checked');
  474. results.push(
  475. !PrivateBin.TopNav.getBurnAfterReading()
  476. );
  477. results.push(
  478. !PrivateBin.TopNav.getOpenDiscussion()
  479. );
  480. PrivateBin.TopNav.resetInput();
  481. results.push(
  482. PrivateBin.TopNav.getBurnAfterReading()
  483. );
  484. results.push(
  485. !PrivateBin.TopNav.getOpenDiscussion()
  486. );
  487. cleanup();
  488. const result = results.every(element => element);
  489. if (!result) {
  490. console.log(results);
  491. }
  492. assert.ok(result);
  493. }
  494. );
  495. it(
  496. 'reset inputs to defaults (opendiscussion on)',
  497. function () {
  498. let results = [];
  499. document.documentElement.innerHTML =
  500. '<nav><div id="navbar"><ul><li id="burnafterreadingoption" ' +
  501. 'class="hidden"><label><input type="checkbox" ' +
  502. 'id="burnafterreading" name="burnafterreading" /> ' +
  503. 'Burn after reading</label></li><li id="opendiscussionoption" ' +
  504. 'class="hidden"><label><input type="checkbox" ' +
  505. 'id="opendiscussion" name="opendiscussion" checked="checked" /> ' +
  506. 'Open discussion</label></li></ul></div></nav>';
  507. PrivateBin.TopNav.init();
  508. results.push(
  509. !PrivateBin.TopNav.getBurnAfterReading()
  510. );
  511. results.push(
  512. PrivateBin.TopNav.getOpenDiscussion()
  513. );
  514. query('#opendiscussion').checked = false;
  515. query('#opendiscussion').removeAttribute('checked');
  516. query('#burnafterreading').checked = true;
  517. query('#burnafterreading').setAttribute('checked', 'checked');
  518. results.push(
  519. PrivateBin.TopNav.getBurnAfterReading()
  520. );
  521. results.push(
  522. !PrivateBin.TopNav.getOpenDiscussion()
  523. );
  524. PrivateBin.TopNav.resetInput();
  525. results.push(
  526. !PrivateBin.TopNav.getBurnAfterReading()
  527. );
  528. results.push(
  529. PrivateBin.TopNav.getOpenDiscussion()
  530. );
  531. cleanup();
  532. const result = results.every(element => element);
  533. if (!result) {
  534. console.log(results);
  535. }
  536. assert.ok(result);
  537. }
  538. );
  539. });
  540. describe('getExpiration', function () {
  541. before(function () {
  542. cleanup();
  543. });
  544. it(
  545. 'returns the currently selected expiration date',
  546. function () {
  547. document.documentElement.innerHTML =
  548. '<select id="pasteExpiration" name="pasteExpiration">' +
  549. '<option value="1day">1 day</option>' +
  550. '<option value="never">Never</option></select>';
  551. PrivateBin.TopNav.init();
  552. assert.strictEqual(PrivateBin.TopNav.getExpiration(), '1day');
  553. cleanup();
  554. }
  555. );
  556. });
  557. describe('getFileList', function () {
  558. before(function () {
  559. cleanup();
  560. });
  561. const File = window.File,
  562. FileList = window.FileList,
  563. path = require('path'), // eslint-disable-line global-require
  564. mime = require('mime-types'); // eslint-disable-line global-require
  565. // mocking file input as per https://github.com/jsdom/jsdom/issues/1272
  566. function createFile(file_path) {
  567. const lastModified = fs.statSync(file_path).mtimeMs;
  568. return new File(
  569. [new fs.readFileSync(file_path)],
  570. path.basename(file_path),
  571. {
  572. lastModified,
  573. type: mime.lookup(file_path) || ''
  574. }
  575. );
  576. }
  577. function addFileList(input, file_paths) {
  578. if (typeof file_paths === 'string') {
  579. file_paths = [file_paths];
  580. } else if (!Array.isArray(file_paths)) {
  581. throw new Error('file_paths needs to be a file path string or an Array of file path strings');
  582. }
  583. const file_list = file_paths.map(fp => createFile(fp));
  584. Object.setPrototypeOf(file_list, Object.create(FileList.prototype));
  585. Object.defineProperty(input, 'files', {
  586. value: file_list,
  587. writeable: false
  588. });
  589. return input;
  590. }
  591. it(
  592. 'returns the selected files',
  593. function () {
  594. let results = [];
  595. document.documentElement.innerHTML =
  596. '<nav><div id="navbar"><ul><li id="attach" class="hidden ' +
  597. 'dropdown"><a href="#" class="dropdown-toggle" data-' +
  598. 'toggle="dropdown" role="button" aria-haspopup="true" ' +
  599. 'aria-expanded="false">Attach a file <span class="caret">' +
  600. '</span></a><ul class="dropdown-menu"><li id="filewrap">' +
  601. '<div><input type="file" id="file" name="file" /></div>' +
  602. '</li><li id="customattachment" class="hidden"></li><li>' +
  603. '<a id="fileremovebutton" href="#">Remove attachment</a>' +
  604. '</li></ul></li></ul></div></nav>';
  605. PrivateBin.TopNav.init();
  606. results.push(
  607. PrivateBin.TopNav.getFileList() === null
  608. );
  609. addFileList(query('#file'), [
  610. '../img/logo.svg',
  611. '../img/busy.gif'
  612. ]);
  613. const files = PrivateBin.TopNav.getFileList();
  614. results.push(
  615. files[0].name === 'logo.svg' &&
  616. files[1].name === 'busy.gif'
  617. );
  618. cleanup();
  619. const result = results.every(element => element);
  620. if (!result) {
  621. console.log(results);
  622. }
  623. assert.ok(result);
  624. }
  625. );
  626. });
  627. describe('getBurnAfterReading', function () {
  628. before(function () {
  629. cleanup();
  630. });
  631. it(
  632. 'returns if the burn-after-reading checkbox was ticked',
  633. function () {
  634. let results = [];
  635. document.documentElement.innerHTML =
  636. '<nav><div id="navbar"><ul><li id="burnafterreadingoption" ' +
  637. 'class="hidden"><label><input type="checkbox" ' +
  638. 'id="burnafterreading" name="burnafterreading" /> ' +
  639. 'Burn after reading</label></li></ul></div></nav>';
  640. PrivateBin.TopNav.init();
  641. results.push(
  642. !PrivateBin.TopNav.getBurnAfterReading()
  643. );
  644. query('#burnafterreading').checked = true;
  645. results.push(
  646. PrivateBin.TopNav.getBurnAfterReading()
  647. );
  648. query('#burnafterreading').checked = false;
  649. query('#burnafterreading').removeAttribute('checked');
  650. results.push(
  651. !PrivateBin.TopNav.getBurnAfterReading()
  652. );
  653. cleanup();
  654. const result = results.every(element => element);
  655. if (!result) {
  656. console.log(results);
  657. }
  658. assert.ok(result);
  659. }
  660. );
  661. });
  662. describe('getOpenDiscussion', function () {
  663. before(function () {
  664. cleanup();
  665. });
  666. it(
  667. 'returns if the open-discussion checkbox was ticked',
  668. function () {
  669. let results = [];
  670. document.documentElement.innerHTML =
  671. '<nav><div id="navbar"><ul><li id="opendiscussionoption" ' +
  672. 'class="hidden"><label><input type="checkbox" ' +
  673. 'id="opendiscussion" name="opendiscussion" /> ' +
  674. 'Open discussion</label></li></ul></div></nav>';
  675. PrivateBin.TopNav.init();
  676. results.push(
  677. !PrivateBin.TopNav.getOpenDiscussion()
  678. );
  679. query('#opendiscussion').checked = true;
  680. results.push(
  681. PrivateBin.TopNav.getOpenDiscussion()
  682. );
  683. query('#opendiscussion').checked = false;
  684. query('#opendiscussion').removeAttribute('checked');
  685. results.push(
  686. !PrivateBin.TopNav.getOpenDiscussion()
  687. );
  688. cleanup();
  689. const result = results.every(element => element);
  690. if (!result) {
  691. console.log(results);
  692. }
  693. assert.ok(result);
  694. }
  695. );
  696. });
  697. describe('getPassword', function () {
  698. before(function () {
  699. globalThis.cleanup();
  700. });
  701. it('returns the contents of the password input', () => {
  702. fc.assert(fc.property(
  703. fc.string(),
  704. function (password) {
  705. password = password.replace(/\r+|\n+/g, '');
  706. let results = [];
  707. document.documentElement.innerHTML =
  708. '<nav><div id="navbar"><ul><li><div id="password" ' +
  709. 'class="navbar-form hidden"><input type="password" ' +
  710. 'id="passwordinput" placeholder="Password (recommended)" ' +
  711. 'class="form-control" size="23" /></div></li></ul></div></nav>';
  712. PrivateBin.TopNav.init();
  713. results.push(
  714. PrivateBin.TopNav.getPassword() === ''
  715. );
  716. query('#passwordinput').value = password;
  717. results.push(
  718. PrivateBin.TopNav.getPassword() === password
  719. );
  720. query('#passwordinput').value = '';
  721. results.push(
  722. PrivateBin.TopNav.getPassword() === ''
  723. );
  724. globalThis.cleanup();
  725. const result = results.every(element => element);
  726. if (!result) {
  727. console.log(results);
  728. }
  729. return result;
  730. }
  731. ));
  732. });
  733. });
  734. describe('getCustomAttachment', function () {
  735. before(function () {
  736. cleanup();
  737. });
  738. it(
  739. 'returns the custom attachment element',
  740. function () {
  741. let results = [];
  742. document.documentElement.innerHTML =
  743. '<nav><div id="navbar"><ul><li id="attach" class="hidden ' +
  744. 'dropdown"><a href="#" class="dropdown-toggle" data-' +
  745. 'toggle="dropdown" role="button" aria-haspopup="true" ' +
  746. 'aria-expanded="false">Attach a file <span class="caret">' +
  747. '</span></a><ul class="dropdown-menu"><li id="filewrap">' +
  748. '<div><input type="file" id="file" name="file" /></div>' +
  749. '</li><li id="customattachment" class="hidden"></li><li>' +
  750. '<a id="fileremovebutton" href="#">Remove attachment</a>' +
  751. '</li></ul></li></ul></div></nav>';
  752. PrivateBin.TopNav.init();
  753. results.push(
  754. !PrivateBin.TopNav.getCustomAttachment().classList.contains('test')
  755. );
  756. query('#customattachment').classList.add('test');
  757. results.push(
  758. PrivateBin.TopNav.getCustomAttachment().classList.contains('test')
  759. );
  760. cleanup();
  761. const result = results.every(element => element);
  762. if (!result) {
  763. console.log(results);
  764. }
  765. assert.ok(result);
  766. }
  767. );
  768. });
  769. describe('hideAllButtons', function () {
  770. before(function () {
  771. cleanup();
  772. });
  773. it(
  774. 'hides all buttons correctly',
  775. function () {
  776. // Insert any setup code needed for the hideAllButtons function
  777. // Example: Initialize the DOM elements required for testing
  778. document.body.innerHTML =
  779. '<nav class="navbar navbar-inverse navbar-static-top">' +
  780. '<div id="navbar" class="navbar-collapse collapse"><ul ' +
  781. 'class="nav navbar-nav"><li><button id="newbutton" ' +
  782. 'type="button" class="hidden btn btn-warning navbar-btn">' +
  783. '<span class="glyphicon glyphicon-file" aria-hidden="true">' +
  784. '</span> New</button><button id="clonebutton" type="button"' +
  785. ' class="hidden btn btn-warning navbar-btn">' +
  786. '<span class="glyphicon glyphicon-duplicate" ' +
  787. 'aria-hidden="true"></span> Clone</button><button ' +
  788. 'id="rawtextbutton" type="button" class="hidden btn ' +
  789. 'btn-warning navbar-btn"><span class="glyphicon ' +
  790. 'glyphicon-text-background" aria-hidden="true"></span> ' +
  791. 'Raw text</button><button id="qrcodelink" type="button" ' +
  792. 'data-toggle="modal" data-target="#qrcodemodal" ' +
  793. 'class="hidden btn btn-warning navbar-btn"><span ' +
  794. 'class="glyphicon glyphicon-qrcode" aria-hidden="true">' +
  795. '</span> QR code</button></li></ul></div></nav>';
  796. PrivateBin.TopNav.init();
  797. PrivateBin.TopNav.hideAllButtons();
  798. assert.ok(query('#newbutton').classList.contains('hidden'));
  799. assert.ok(query('#clonebutton').classList.contains('hidden'));
  800. assert.ok(query('#rawtextbutton').classList.contains('hidden'));
  801. assert.ok(query('#qrcodelink').classList.contains('hidden'));
  802. cleanup();
  803. }
  804. );
  805. });
  806. describe('rawText', function () {
  807. before(function () {
  808. cleanup();
  809. });
  810. // TODO triggers error messages in jsDOM since version 12, but passes
  811. it(
  812. 'displays raw text view correctly',
  813. function () {
  814. const clean = globalThis.cleanup('', {url: 'https://privatebin.net/?0123456789abcdef#1'});
  815. document.documentElement.innerHTML = `
  816. <li id="loadingindicator" class="hidden"></li>
  817. <button id="rawtextbutton"></button>`;
  818. const sample = 'example';
  819. PrivateBin.Alert.init(); // required because of locading indiator being used
  820. PrivateBin.TopNav.init();
  821. PrivateBin.PasteViewer.setText(sample);
  822. PrivateBin.Helper.reset();
  823. query('#rawtextbutton').click();
  824. assert.strictEqual(document.querySelector('pre').textContent, sample);
  825. clean();
  826. }
  827. );
  828. });
  829. });