'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 (
'
' +
'' +
'
'
);
}
/**
* 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 = (
'' +
''
);
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 = (
'