TopNav.js 38 KB

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