Alert.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. 'use strict';
  2. const common = require('../common');
  3. const fc = require('fast-check');
  4. const path = require('path');
  5. /**
  6. * icon markup of the bootstrap5 template
  7. *
  8. * @param {string} name - bootstrap icon name
  9. * @returns {string}
  10. */
  11. function icon(name) {
  12. return '<svg width="16" height="16" fill="currentColor" aria-hidden="true">' +
  13. '<use href="img/bootstrap-icons.svg#' + name + '"></use></svg>';
  14. }
  15. describe('Alert', function () {
  16. describe('showStatus', function () {
  17. it('shows a status message (basic)', () => {
  18. fc.assert(fc.property(
  19. fc.array(common.fcAlnumString()),
  20. fc.array(common.fcAlnumString()),
  21. function (icon, message) {
  22. icon = icon.join('');
  23. message = message.join('');
  24. const expected = '<div id="status">' + message + '</div>';
  25. document.body.innerHTML =
  26. '<div id="status"></div>';
  27. PrivateBin.Alert.init();
  28. PrivateBin.Alert.showStatus(message, icon);
  29. const result = document.body.innerHTML;
  30. return expected === result;
  31. }
  32. ));
  33. });
  34. it('shows a status message (bootstrap)', () => {
  35. fc.assert(fc.property(
  36. fc.array(common.fcAlnumString()),
  37. function (message) {
  38. message = message.join('');
  39. const expected = '<div id="status" role="alert" ' +
  40. 'class="d-flex align-items-center gap-2 alert alert-info">' +
  41. '<div>' + icon('info-circle') + ' </div> <span>' +
  42. message + '</span></div>';
  43. document.body.innerHTML =
  44. '<div id="status" role="alert" class="d-flex ' +
  45. 'align-items-center gap-2 alert alert-info hidden">' +
  46. '<div>' + icon('info-circle') + ' </div></div>';
  47. PrivateBin.Alert.init();
  48. PrivateBin.Alert.showStatus(message);
  49. const result = document.body.innerHTML;
  50. return expected === result;
  51. }
  52. ));
  53. });
  54. it('shows a status message (bootstrap, custom icon)', () => {
  55. fc.assert(fc.property(
  56. fc.array(common.fcAlnumString()),
  57. fc.array(common.fcAlnumString()),
  58. function (customIcon, message) {
  59. customIcon = customIcon.join('');
  60. message = message.join('');
  61. const expected = '<div id="status" role="alert" ' +
  62. 'class="d-flex align-items-center gap-2 alert alert-info">' +
  63. '<div>' + icon(customIcon || 'info-circle') + ' </div> <span>' +
  64. message + '</span></div>';
  65. document.body.innerHTML =
  66. '<div id="status" role="alert" class="d-flex ' +
  67. 'align-items-center gap-2 alert alert-info hidden">' +
  68. '<div>' + icon('info-circle') + ' </div></div>';
  69. PrivateBin.Alert.init();
  70. PrivateBin.Alert.showStatus(message, customIcon);
  71. const result = document.body.innerHTML;
  72. return expected === result;
  73. }
  74. ));
  75. });
  76. });
  77. describe('showWarning', function () {
  78. it('shows a warning message (basic)', () => {
  79. fc.assert(fc.property(
  80. fc.array(common.fcAlnumString()),
  81. fc.array(common.fcAlnumString()),
  82. function (icon, message) {
  83. icon = icon.join('');
  84. message = message.join('');
  85. const expected = '<div id="errormessage">' + message + '</div>';
  86. document.body.innerHTML =
  87. '<div id="errormessage"></div>';
  88. PrivateBin.Alert.init();
  89. PrivateBin.Alert.showWarning(message, icon);
  90. const result = document.body.innerHTML;
  91. return expected === result;
  92. }
  93. ));
  94. });
  95. it('shows a warning message (bootstrap)', () => {
  96. fc.assert(fc.property(
  97. fc.array(common.fcAlnumString()),
  98. function (message) {
  99. message = message.join('');
  100. const expected = '<div id="errormessage" role="alert" ' +
  101. 'class="alert alert-danger">' + icon('exclamation-triangle') +
  102. ' <span>' + message + '</span></div>';
  103. document.body.innerHTML =
  104. '<div id="errormessage" role="alert" class="hidden ' +
  105. 'alert alert-danger">' + icon('exclamation-triangle') + ' </div>';
  106. PrivateBin.Alert.init();
  107. PrivateBin.Alert.showWarning(message);
  108. const result = document.body.innerHTML;
  109. return expected === result;
  110. }
  111. ));
  112. });
  113. it('shows a warning message (bootstrap, custom icon)', () => {
  114. fc.assert(fc.property(
  115. fc.array(common.fcAlnumString()),
  116. fc.array(common.fcAlnumString()),
  117. function (customIcon, message) {
  118. customIcon = customIcon.join('');
  119. message = message.join('');
  120. const expected = '<div id="errormessage" role="alert" ' +
  121. 'class="alert alert-danger">' +
  122. icon(customIcon || 'exclamation-triangle') +
  123. ' <span>' + message + '</span></div>';
  124. document.body.innerHTML =
  125. '<div id="errormessage" role="alert" class="hidden ' +
  126. 'alert alert-danger">' + icon('exclamation-triangle') + ' </div>';
  127. PrivateBin.Alert.init();
  128. PrivateBin.Alert.showWarning(message, customIcon);
  129. const result = document.body.innerHTML;
  130. return expected === result;
  131. }
  132. ));
  133. });
  134. });
  135. describe('showError', function () {
  136. it('shows an error message (basic)', () => {
  137. fc.assert(fc.property(
  138. fc.array(common.fcAlnumString()),
  139. fc.array(common.fcAlnumString()),
  140. function (icon, message) {
  141. icon = icon.join('');
  142. message = message.join('');
  143. const expected = '<div id="errormessage">' + message + '</div>';
  144. document.body.innerHTML =
  145. '<div id="errormessage"></div>';
  146. PrivateBin.Alert.init();
  147. PrivateBin.Alert.showError(message, icon);
  148. const result = document.body.innerHTML;
  149. return expected === result;
  150. }
  151. ));
  152. });
  153. it('shows an error message (bootstrap)', () => {
  154. fc.assert(fc.property(
  155. fc.array(common.fcAlnumString()),
  156. function (message) {
  157. message = message.join('');
  158. const expected = '<div id="errormessage" role="alert" ' +
  159. 'class="alert alert-danger">' + icon('exclamation-triangle') +
  160. ' <span>' + message + '</span></div>';
  161. document.body.innerHTML =
  162. '<div id="errormessage" role="alert" class="hidden ' +
  163. 'alert alert-danger">' + icon('exclamation-triangle') + ' </div>';
  164. PrivateBin.Alert.init();
  165. PrivateBin.Alert.showError(message);
  166. const result = document.body.innerHTML;
  167. return expected === result;
  168. }
  169. ));
  170. });
  171. it('shows an error message (bootstrap, custom icon)', () => {
  172. fc.assert(fc.property(
  173. fc.array(common.fcAlnumString()),
  174. fc.array(common.fcAlnumString()),
  175. function (customIcon, message) {
  176. customIcon = customIcon.join('');
  177. message = message.join('');
  178. const expected = '<div id="errormessage" role="alert" ' +
  179. 'class="alert alert-danger">' +
  180. icon(customIcon || 'exclamation-triangle') +
  181. ' <span>' + message + '</span></div>';
  182. document.body.innerHTML =
  183. '<div id="errormessage" role="alert" class="hidden ' +
  184. 'alert alert-danger">' + icon('exclamation-triangle') + ' </div>';
  185. PrivateBin.Alert.init();
  186. PrivateBin.Alert.showError(message, customIcon);
  187. const result = document.body.innerHTML;
  188. return expected === result;
  189. }
  190. ));
  191. });
  192. });
  193. describe('showRemaining', function () {
  194. it('shows remaining time (basic)', () => {
  195. fc.assert(fc.property(
  196. fc.array(common.fcAlnumString()),
  197. fc.array(common.fcAlnumString()),
  198. fc.integer(),
  199. function (message, string, number) {
  200. message = message.join('');
  201. string = string.join('');
  202. const expected = '<div id="remainingtime" class="">' + string + message + number + '</div>';
  203. document.body.innerHTML =
  204. '<div id="remainingtime" class="hidden"></div>';
  205. PrivateBin.Alert.init();
  206. PrivateBin.Alert.showRemaining(['%s' + message + '%d', string, number]);
  207. const result = document.body.innerHTML;
  208. return expected === result;
  209. }
  210. ));
  211. });
  212. it('shows remaining time (bootstrap)', () => {
  213. fc.assert(fc.property(
  214. fc.array(common.fcAlnumString()),
  215. fc.array(common.fcAlnumString()),
  216. fc.integer(),
  217. function (message, string, number) {
  218. message = message.join('');
  219. string = string.join('');
  220. const expected = '<div id="remainingtime" role="alert" ' +
  221. 'class="alert alert-info">' + icon('fire') +
  222. ' <span>' + string + message + number + '</span></div>';
  223. document.body.innerHTML =
  224. '<div id="remainingtime" role="alert" class="hidden ' +
  225. 'alert alert-info">' + icon('fire') + ' </div>';
  226. PrivateBin.Alert.init();
  227. PrivateBin.Alert.showRemaining(['%s' + message + '%d', string, number]);
  228. const result = document.body.innerHTML;
  229. return expected === result;
  230. }
  231. ));
  232. });
  233. });
  234. describe('showLoading', function () {
  235. it('shows a loading message (basic)', () => {
  236. fc.assert(fc.property(
  237. fc.array(common.fcAlnumString()),
  238. fc.array(common.fcAlnumString()),
  239. function (message, icon) {
  240. message = message.join('');
  241. icon = icon.join('');
  242. const defaultMessage = 'Loading…';
  243. if (message.length === 0) {
  244. message = defaultMessage;
  245. }
  246. const expected = '<div id="loadingindicator" class="">' + message + '</div>';
  247. document.body.innerHTML =
  248. '<div id="loadingindicator" class="hidden">' + defaultMessage + '</div>';
  249. PrivateBin.Alert.init();
  250. PrivateBin.Alert.showLoading(message, icon);
  251. const result = document.body.innerHTML;
  252. return expected === result;
  253. }
  254. ));
  255. });
  256. it('shows a loading message (bootstrap)', () => {
  257. fc.assert(fc.property(
  258. fc.array(common.fcAlnumString()),
  259. fc.array(common.fcAlnumString()),
  260. function (message, customIcon) {
  261. message = message.join('');
  262. customIcon = customIcon.join('');
  263. const defaultMessage = 'Loading…';
  264. if (message.length === 0) {
  265. message = defaultMessage;
  266. }
  267. const expected = '<ul class="navbar-nav"><li ' +
  268. 'id="loadingindicator" class="navbar-text me-auto">' +
  269. icon(customIcon || 'clock') + ' <span>' + message +
  270. '</span></li></ul>';
  271. document.body.innerHTML =
  272. '<ul class="navbar-nav"><li id="loadingindicator" ' +
  273. 'class="navbar-text hidden me-auto">' + icon('clock') +
  274. ' ' + defaultMessage + '</li></ul>';
  275. PrivateBin.Alert.init();
  276. PrivateBin.Alert.showLoading(message, customIcon);
  277. const result = document.body.innerHTML;
  278. return expected === result;
  279. }
  280. ));
  281. });
  282. it('keeps the previous icon, if none is given (bootstrap)', () => {
  283. fc.assert(fc.property(
  284. fc.constantFrom('clock', 'cloud-download', 'cloud-upload'),
  285. fc.constantFrom(undefined, null, ''),
  286. function (customIcon, noIcon) {
  287. document.body.innerHTML =
  288. '<ul class="navbar-nav"><li id="loadingindicator" ' +
  289. 'class="navbar-text hidden me-auto">' + icon('clock') +
  290. ' Loading…</li></ul>';
  291. PrivateBin.Alert.init();
  292. PrivateBin.Alert.showLoading('first', customIcon);
  293. PrivateBin.Alert.hideLoading();
  294. PrivateBin.Alert.showLoading('second', noIcon);
  295. const use = document.querySelector('#loadingindicator use');
  296. return use.getAttribute('href') === 'img/bootstrap-icons.svg#' + customIcon &&
  297. document.querySelectorAll('#loadingindicator svg').length === 1 &&
  298. document.querySelector('#loadingindicator span').textContent === 'second';
  299. }
  300. ));
  301. });
  302. });
  303. describe('hideLoading', function () {
  304. it(
  305. 'hides the loading message',
  306. function() {
  307. document.body.innerHTML =
  308. '<ul class="navbar-nav"><li id="loadingindicator" ' +
  309. 'class="navbar-text me-auto">' + icon('clock') +
  310. ' Loading…</li></ul>';
  311. document.body.classList.add('loading');
  312. PrivateBin.Alert.init();
  313. PrivateBin.Alert.hideLoading();
  314. assert.ok(!document.body.classList.contains('loading'));
  315. assert.ok(document.getElementById('loadingindicator').classList.contains('hidden'));
  316. }
  317. );
  318. });
  319. describe('hideMessages', function () {
  320. it(
  321. 'hides all messages',
  322. function() {
  323. document.body.innerHTML =
  324. '<div id="status" role="alert" class="d-flex ' +
  325. 'align-items-center gap-2 alert alert-info"><div>' +
  326. icon('info-circle') + ' </div></div>' +
  327. '<div id="errormessage" role="alert" class="alert ' +
  328. 'alert-danger">' + icon('exclamation-triangle') + ' </div>';
  329. PrivateBin.Alert.init();
  330. PrivateBin.Alert.hideMessages();
  331. assert.ok(document.getElementById('status').classList.contains('hidden'));
  332. assert.ok(document.getElementById('errormessage').classList.contains('hidden'));
  333. }
  334. );
  335. });
  336. describe('setCustomHandler', function () {
  337. it('calls a given handler function', () => {
  338. fc.assert(fc.property(
  339. fc.integer({min: 0, max: 3}),
  340. fc.array(common.fcAlnumString()),
  341. function (trigger, message) {
  342. message = message.join('');
  343. let handlerCalled = false,
  344. defaultMessage = 'Loading…',
  345. functions = [
  346. PrivateBin.Alert.showStatus,
  347. PrivateBin.Alert.showError,
  348. PrivateBin.Alert.showRemaining,
  349. PrivateBin.Alert.showLoading
  350. ];
  351. if (message.length === 0) {
  352. message = defaultMessage;
  353. }
  354. document.body.innerHTML =
  355. '<ul class="navbar-nav"><li id="loadingindicator" ' +
  356. 'class="navbar-text hidden me-auto">' + icon('clock') +
  357. ' ' + defaultMessage + '</li></ul>' +
  358. '<div id="remainingtime" role="alert" class="hidden ' +
  359. 'alert alert-info">' + icon('fire') + ' </div>' +
  360. '<div id="status" role="alert" class="d-flex ' +
  361. 'align-items-center gap-2 alert alert-info"><div>' +
  362. icon('info-circle') + ' </div></div>' +
  363. '<div id="errormessage" role="alert" class="alert ' +
  364. 'alert-danger">' + icon('exclamation-triangle') + ' </div>';
  365. PrivateBin.Alert.init();
  366. PrivateBin.Alert.setCustomHandler(function(id, element) {
  367. handlerCalled = true;
  368. return Math.random() > 0.5 ? true : element;
  369. });
  370. functions[trigger](message);
  371. PrivateBin.Alert.setCustomHandler(null);
  372. return handlerCalled;
  373. }
  374. ));
  375. });
  376. });
  377. describe('icons', function () {
  378. it('only uses icons that exist in the icon sprite', function () {
  379. const source = fs.readFileSync(path.join(__dirname, '..', 'privatebin.js'), 'utf8');
  380. const sprite = fs.readFileSync(path.join(__dirname, '..', '..', 'img', 'bootstrap-icons.svg'), 'utf8');
  381. const calls = source.match(/(Alert\.show(Status|Warning|Error|Loading)|Helper\.setIcon)\([^;]*\);/g);
  382. assert.ok(calls.length > 0, 'found icon setting calls');
  383. let names = [];
  384. calls.forEach(function (call) {
  385. // skip the first argument, the message or element
  386. const icons = call.substring(call.indexOf(',')).match(/'[a-z][a-z0-9-]*'/g) || [];
  387. names = names.concat(icons.map(name => name.slice(1, -1)));
  388. });
  389. assert.ok(names.length > 0, 'found icon names');
  390. names.forEach(function (name) {
  391. assert.ok(sprite.includes('id="' + name + '"'), 'icon ' + name + ' exists in sprite');
  392. });
  393. });
  394. });
  395. });