Просмотр исходного кода

test: add PasswordPeek unit tests

The tests for the create form password input and the property test
binding every reveal button fail, as PasswordPeek.init only binds the
first .toggle-password button since the jQuery removal.
El RIDO 1 неделя назад
Родитель
Сommit
5906dc4a98
1 измененных файлов с 138 добавлено и 0 удалено
  1. 138 0
      js/test/PasswordPeek.js

+ 138 - 0
js/test/PasswordPeek.js

@@ -0,0 +1,138 @@
+'use strict';
+require('../common');
+const fc = require('fast-check');
+
+describe('PasswordPeek', function () {
+    const iconHref = 'img/bootstrap-icons.svg#eye';
+
+    /**
+     * input group with a password field and its reveal button, as in the
+     * bootstrap5 template
+     *
+     * @param {string} id
+     * @returns {string}
+     */
+    function passwordGroup(id) {
+        return (
+            '<div class="input-group">' +
+            '<input id="' + id + '" type="password" class="form-control input-password" />' +
+            '<button class="btn btn-outline-secondary toggle-password" type="button" ' +
+            'title="Show password" aria-label="Show password">' +
+            '<svg width="16" height="16" fill="currentColor" aria-hidden="true">' +
+            '<use href="' + iconHref + '" /></svg>' +
+            '</button></div>'
+        );
+    }
+
+    /**
+     * asserts that the input and its reveal button are in the expected state
+     *
+     * @param {string} id
+     * @param {boolean} isRevealed
+     */
+    function assertState(id, isRevealed) {
+        const input = document.getElementById(id);
+        const button = input.closest('.input-group').querySelector('.toggle-password');
+        const label = isRevealed ? 'Hide password' : 'Show password';
+        assert.strictEqual(input.getAttribute('type'), isRevealed ? 'text' : 'password', id + ' type');
+        assert.strictEqual(button.getAttribute('title'), label, id + ' title');
+        assert.strictEqual(button.getAttribute('aria-label'), label, id + ' aria-label');
+        assert.strictEqual(
+            button.querySelector('use').getAttribute('href'),
+            isRevealed ? iconHref + '-slash' : iconHref,
+            id + ' icon'
+        );
+    }
+
+    /**
+     * clicks the reveal button belonging to the given input
+     *
+     * @param {string} id
+     */
+    function clickButton(id) {
+        document.getElementById(id).closest('.input-group')
+            .querySelector('.toggle-password').click();
+    }
+
+    beforeEach(function () {
+        globalThis.cleanup();
+        // same order as in the template: the decryption prompt comes before
+        // the password input of the create form
+        document.body.innerHTML = (
+            '<div id="passwordmodal"><form id="passwordform">' +
+            passwordGroup('passworddecrypt') +
+            '</form></div>' +
+            '<nav><div id="password">' +
+            passwordGroup('passwordinput') +
+            '</div></nav>'
+        );
+        PrivateBin.PasswordPeek.init();
+    });
+
+    after(function () {
+        globalThis.cleanup();
+    });
+
+    it('toggles the password prompt when opening a document', function () {
+        clickButton('passworddecrypt');
+        assertState('passworddecrypt', true);
+        assertState('passwordinput', false);
+        clickButton('passworddecrypt');
+        assertState('passworddecrypt', false);
+        assertState('passwordinput', false);
+    });
+
+    it('toggles the password input when creating a document', function () {
+        clickButton('passwordinput');
+        assertState('passwordinput', true);
+        assertState('passworddecrypt', false);
+        clickButton('passwordinput');
+        assertState('passwordinput', false);
+        assertState('passworddecrypt', false);
+    });
+
+    it('toggles each input independently, in any click order', function () {
+        fc.assert(fc.property(
+            fc.integer({min: 1, max: 5}).chain(function (count) {
+                return fc.tuple(
+                    fc.constant(count),
+                    fc.array(fc.nat({max: count - 1}), {maxLength: 20})
+                );
+            }),
+            function ([count, clicks]) {
+                globalThis.cleanup();
+                const ids = [];
+                for (let i = 0; i < count; ++i) {
+                    ids.push('password' + i);
+                }
+                document.body.innerHTML = ids.map(passwordGroup).join('');
+                PrivateBin.PasswordPeek.init();
+
+                const revealed = ids.map(() => false);
+                for (const index of clicks) {
+                    clickButton(ids[index]);
+                    revealed[index] = !revealed[index];
+                }
+                ids.forEach(function (id, index) {
+                    assertState(id, revealed[index]);
+                });
+            }
+        ), {numRuns: 50});
+    });
+
+    it('ignores a reveal button without a password input', function () {
+        globalThis.cleanup();
+        document.body.innerHTML = (
+            '<div class="input-group">' +
+            '<button class="toggle-password" type="button" title="Show password">' +
+            '<svg><use href="' + iconHref + '" /></svg></button></div>' +
+            '<button class="toggle-password" id="orphan" type="button" title="Show password"></button>'
+        );
+        PrivateBin.PasswordPeek.init();
+        document.querySelectorAll('.toggle-password').forEach(button => button.click());
+        document.querySelectorAll('.toggle-password').forEach(function (button) {
+            assert.strictEqual(button.getAttribute('title'), 'Show password');
+        });
+        assert.strictEqual(document.querySelector('use').getAttribute('href'), iconHref);
+    });
+});