passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
357 lines (305 loc) • 17.4 kB
JavaScript
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) 2020 Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) 2020 Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 2.11.0
*/
/**
* Unit tests on CreateResource in regard of specifications
*/
import {ActionFeedbackContext} from "../../../contexts/ActionFeedbackContext";
import PassboltApiFetchError from "../../../../shared/lib/Error/PassboltApiFetchError";
import {waitFor} from "@testing-library/react";
import {defaultAppContext, defaultProps} from "./CreateResource.test.data";
import CreateResourcePage from "./CreateResource.test.page";
import "../../../test/lib/crypto/cryptoGetRandomvalues";
import NotifyError from "../../Common/Error/NotifyError/NotifyError";
beforeEach(() => {
jest.resetModules();
});
describe("See the Create Resource", () => {
let page; // The page to test against
const context = defaultAppContext(); // The applicative context
const props = defaultProps(); // The props to pass
props.onClose = jest.fn();
props.dialogContext.open = jest.fn();
const resourceCreateDialogProps = {
folderParentId: null
};
const mockContextRequest = implementation => jest.spyOn(context.port, 'request').mockImplementationOnce(implementation);
const truncatedWarningMessage = "Warning: this is the maximum size for this field, make sure your data was not truncated.";
describe('As LU I can start adding a password', () => {
/**
* I should see the create password dialog
*/
beforeEach(() => {
context.setContext({resourceCreateDialogProps});
page = new CreateResourcePage(context, props);
jest.useFakeTimers();
});
afterEach(() => {
jest.clearAllTimers();
});
it('matches the styleguide', () => {
expect.assertions(19);
// Dialog title exists and correct
expect(page.passwordCreate.exists()).toBeTruthy();
expect(page.title.header.textContent).toBe("Create a password");
// Close button exists
expect(page.passwordCreate.dialogClose).not.toBeNull();
// Name input field exists.
expect(page.passwordCreate.name.value).toBe("");
// Uri input field exists.
expect(page.passwordCreate.uri.value).toBe("");
// Username input field exists.
expect(page.passwordCreate.username.value).toBe("");
// Password input field exists
expect(page.passwordCreate.password).not.toBeNull();
expect(page.passwordCreate.password.value).toBe("");
expect(page.passwordCreate.password.getAttribute("type")).toBe("password");
const passwordInputStyle = window.getComputedStyle(page.passwordCreate.password);
expect(passwordInputStyle.background).toBe("white");
expect(passwordInputStyle.color).toBe("");
// Complexity label exists but is not yet defined.
expect(page.passwordCreate.complexityText.textContent).toBe("Quality");
// Password view button exists.
expect(page.passwordCreate.passwordViewButton).not.toBeNull();
expect(page.passwordCreate.passwordViewButton.classList.contains("eye-open")).toBe(true);
expect(page.passwordCreate.passwordViewButton.classList.contains("eye-close")).toBe(false);
// Password generate button exists.
expect(page.passwordCreate.passwordGenerateButton).not.toBeNull();
// Description textarea field exists
expect(page.passwordCreate.description.value).toBe("");
// Save button exists
expect(page.passwordCreate.saveButton.textContent).toBe("Create");
// Cancel button exists
expect(page.passwordCreate.cancelButton.textContent).toBe("Cancel");
});
it('generates password when clicking on the generate button.', async() => {
expect.assertions(2);
page.passwordCreate.focusInput(page.passwordCreate.password);
await page.passwordCreate.click(page.passwordCreate.passwordGenerateButton);
expect(page.passwordCreate.complexityText.textContent).not.toBe("Quality");
expect(page.passwordCreate.progressBar.classList.contains("error")).toBe(false);
});
it('views password when clicking on the view button.', async() => {
expect.assertions(8);
mockContextRequest(() => Promise(0));
const passwordValue = "secret-decrypted";
await page.passwordCreate.fillInputPassword(passwordValue);
// View password
await page.passwordCreate.click(page.passwordCreate.passwordViewButton);
expect(page.passwordCreate.password.value).toBe(passwordValue);
let passwordInputType = page.passwordCreate.password.getAttribute("type");
expect(passwordInputType).toBe("text");
expect(page.passwordCreate.passwordViewButton.classList.contains("eye-open")).toBe(false);
expect(page.passwordCreate.passwordViewButton.classList.contains("eye-close")).toBe(true);
// Hide password
await page.passwordCreate.click(page.passwordCreate.passwordViewButton);
expect(page.passwordCreate.password.value).toBe(passwordValue);
passwordInputType = page.passwordCreate.password.getAttribute("type");
expect(passwordInputType).toBe("password");
expect(page.passwordCreate.passwordViewButton.classList.contains("eye-open")).toBe(true);
expect(page.passwordCreate.passwordViewButton.classList.contains("eye-close")).toBe(false);
});
it('requests the addon to create a resource with encrypted description when clicking on the submit button.', async() => {
expect.assertions(7);
expect(page.passwordCreate.exists()).toBeTruthy();
const createdResourceId = "f2b4047d-ab6d-4430-a1e2-3ab04a2f4fb9";
// create password
const resourceMeta = {
name: "Password name",
uri: "https://uri.dev",
username: "Password username",
password: "password-value",
description: "Password description"
};
// Fill the form
page.passwordCreate.fillInput(page.passwordCreate.name, resourceMeta.name);
page.passwordCreate.fillInput(page.passwordCreate.uri, resourceMeta.uri);
page.passwordCreate.fillInput(page.passwordCreate.username, resourceMeta.username);
await page.passwordCreate.fillInputPassword(resourceMeta.password);
expect(page.passwordCreate.complexityText.textContent).not.toBe("Complexity: n/aEntropy: NaN bits");
expect(page.passwordCreate.progressBar.classList.contains("not_available")).toBe(false);
page.passwordCreate.fillInput(page.passwordCreate.description, resourceMeta.description);
await page.passwordCreate.click(page.passwordCreate.descriptionEncryptedLock);
const requestMockImpl = jest.fn((message, data) => Object.assign({id: createdResourceId}, data));
mockContextRequest(requestMockImpl);
jest.spyOn(context.port, 'emit').mockImplementation(jest.fn());
jest.spyOn(ActionFeedbackContext._currentValue, 'displaySuccess').mockImplementation(() => {});
const onApiUpdateResourceMeta = {
folder_parent_id: null,
name: resourceMeta.name,
uri: resourceMeta.uri,
username: resourceMeta.username,
description: resourceMeta.description,
resource_type_id: "669f8c64-242a-59fb-92fc-81f660975fd3"
};
await page.passwordCreate.click(page.passwordCreate.saveButton);
expect(context.port.request).toHaveBeenCalledWith("passbolt.resources.create", onApiUpdateResourceMeta, resourceMeta.password);
expect(ActionFeedbackContext._currentValue.displaySuccess).toHaveBeenCalled();
expect(context.port.emit).toHaveBeenNthCalledWith(1, "passbolt.resources.select-and-scroll-to", createdResourceId);
expect(props.onClose).toBeCalled();
});
it('requests the addon to create a resource with non encrypted description when clicking on the submit button.', async() => {
expect.assertions(7);
expect(page.passwordCreate.exists()).toBeTruthy();
const createdResourceId = "f2b4047d-ab6d-4430-a1e2-3ab04a2f4fb9";
// create password
const resourceMeta = {
name: "Password name",
uri: "https://uri.dev",
username: "Password username",
password: "password-value",
description: "Password description"
};
// Fill the form
page.passwordCreate.fillInput(page.passwordCreate.name, resourceMeta.name);
page.passwordCreate.fillInput(page.passwordCreate.uri, resourceMeta.uri);
page.passwordCreate.fillInput(page.passwordCreate.username, resourceMeta.username);
await page.passwordCreate.fillInputPassword(resourceMeta.password);
expect(page.passwordCreate.complexityText.textContent).not.toBe("Complexity: n/aEntropy: NaN bits");
expect(page.passwordCreate.progressBar.classList.contains("not_available")).toBe(false);
page.passwordCreate.fillInput(page.passwordCreate.description, resourceMeta.description);
const requestMockImpl = jest.fn((message, data) => Object.assign({id: createdResourceId}, data));
mockContextRequest(requestMockImpl);
jest.spyOn(context.port, 'emit').mockImplementation(jest.fn());
jest.spyOn(ActionFeedbackContext._currentValue, 'displaySuccess').mockImplementation(() => {});
const onApiUpdateResourceDto = {
folder_parent_id: null,
name: resourceMeta.name,
uri: resourceMeta.uri,
username: resourceMeta.username,
resource_type_id: "a28a04cd-6f53-518a-967c-9963bf9cec51"
};
const onApiUpdateSecretDto = {
description: resourceMeta.description,
password: resourceMeta.password
};
await page.passwordCreate.click(page.passwordCreate.saveButton);
expect(context.port.request).toHaveBeenCalledWith("passbolt.resources.create", onApiUpdateResourceDto, onApiUpdateSecretDto);
expect(ActionFeedbackContext._currentValue.displaySuccess).toHaveBeenCalled();
expect(context.port.emit).toHaveBeenNthCalledWith(1, "passbolt.resources.select-and-scroll-to", createdResourceId);
expect(props.onClose).toBeCalled();
});
it('As LU I shouldn’t be able to submit the form if there is an invalid field', async() => {
expect.assertions(3);
expect(page.passwordCreate.exists()).toBeTruthy();
await page.passwordCreate.click(page.passwordCreate.saveButton);
// Throw error message
expect(page.passwordCreate.nameErrorMessage.textContent).toBe("A name is required.");
expect(page.passwordCreate.passwordErrorMessage.textContent).toBe("A password is required.");
});
it('As LU I can stop createing a password by clicking on the cancel button', async() => {
expect.assertions(2);
expect(page.passwordCreate.exists()).toBeTruthy();
await page.passwordCreate.click(page.passwordCreate.cancelButton);
expect(props.onClose).toBeCalled();
});
it('As LU I cannot update the form fields and I should see a processing feedback while submitting the form', async() => {
// Mock the request function to make it the expected result
let updateResolve;
const requestMockImpl = jest.fn(() => new Promise(resolve => {
updateResolve = resolve;
}));
page.passwordCreate.fillInput(page.passwordCreate.name, "name");
await page.passwordCreate.fillInputPassword("password");
// Mock the request function to make it the expected result
mockContextRequest(requestMockImpl);
page.passwordCreate.clickWithoutWaitFor(page.passwordCreate.saveButton);
// API calls are made on submit, wait they are resolved.
await waitFor(() => {
expect(page.passwordCreate.name.getAttribute("disabled")).not.toBeNull();
expect(page.passwordCreate.uri.getAttribute("disabled")).not.toBeNull();
expect(page.passwordCreate.username.getAttribute("disabled")).not.toBeNull();
expect(page.passwordCreate.password.getAttribute("disabled")).not.toBeNull();
expect(page.passwordCreate.saveButton.getAttribute("disabled")).not.toBeNull();
expect(page.passwordCreate.saveButton.className).toBe("button primary disabled processing");
expect(page.passwordCreate.cancelButton.className).toBe("cancel disabled");
updateResolve();
});
});
it('As LU I can stop createing a password by closing the dialog', async() => {
expect.assertions(2);
expect(page.passwordCreate.exists()).toBeTruthy();
await page.passwordCreate.click(page.passwordCreate.dialogClose);
expect(props.onClose).toBeCalled();
});
it('As LU I can stop adding a password with the keyboard (escape)', async() => {
expect.assertions(2);
expect(page.passwordCreate.exists()).toBeTruthy();
await page.passwordCreate.escapeKey(page.passwordCreate.dialogClose);
expect(props.onClose).toBeCalled();
});
it('As LU I should see an error dialog if the submit operation fails for an unexpected reason', async() => {
expect.assertions(1);
// Mock the request function to make it return an error.
page.passwordCreate.fillInput(page.passwordCreate.name, "name");
await page.passwordCreate.fillInputPassword("password");
const error = new PassboltApiFetchError("Jest simulate API error.");
jest.spyOn(context.port, 'request').mockImplementationOnce(() => {
throw error;
});
jest.spyOn(props.dialogContext, 'open').mockImplementationOnce(jest.fn);
await page.passwordCreate.click(page.passwordCreate.saveButton);
// Throw general error message
expect(props.dialogContext.open).toHaveBeenCalledWith(NotifyError, {error: error});
});
it('As LU I should access to the password generator dialog', async() => {
expect.assertions(1);
jest.spyOn(props.dialogContext, 'open').mockImplementationOnce(jest.fn);
await page.passwordCreate.openPasswordGenerator();
expect(props.dialogContext.open).toBeCalled();
});
it('As LU I should access to the password generator dialog', async() => {
expect.assertions(1);
await page.passwordCreate.openPasswordGenerator();
expect(page.passwordCreate.passwordGeneratorDialog).not.toBeNull();
});
it("As a user I should see a feedback when the password, descriptions, name, username or uri fields content is truncated by a field limit", async() => {
expect.assertions(5);
page.passwordCreate.fillInput(page.passwordCreate.password, 'a'.repeat(4097));
page.passwordCreate.fillInput(page.passwordCreate.description, 'a'.repeat(10000));
page.passwordCreate.fillInput(page.passwordCreate.name, 'a'.repeat(256));
page.passwordCreate.fillInput(page.passwordCreate.username, 'a'.repeat(255));
page.passwordCreate.fillInput(page.passwordCreate.uri, 'a'.repeat(1025));
await page.passwordCreate.keyUpInput(page.passwordCreate.password);
await page.passwordCreate.keyUpInput(page.passwordCreate.description);
await page.passwordCreate.keyUpInput(page.passwordCreate.username);
await page.passwordCreate.keyUpInput(page.passwordCreate.name);
await page.passwordCreate.keyUpInput(page.passwordCreate.uri);
expect(page.passwordCreate.passwordWarningMessage.textContent).toEqual(truncatedWarningMessage);
expect(page.passwordCreate.descriptionWarningMessage.textContent).toEqual(truncatedWarningMessage);
expect(page.passwordCreate.nameWarningMessage.textContent).toEqual(truncatedWarningMessage);
expect(page.passwordCreate.uriWarningMessage.textContent).toEqual(truncatedWarningMessage);
expect(page.passwordCreate.usernameWarningMessage.textContent).toEqual(truncatedWarningMessage);
});
it("As a signed-in user creating a password on the application, I should get warn when I enter a pwned password and not be blocked", async() => {
expect.assertions(2);
mockContextRequest(() => Promise.resolve(2));
await page.passwordCreate.fillInputPassword('hello-world');
// we expect a warning to inform about powned password
await waitFor(() => {});
expect(page.passwordCreate.pwnedWarningMessage.textContent).toEqual("The password is part of an exposed data breach.");
mockContextRequest(() => Promise.reject());
await page.passwordCreate.fillInputPassword('another test');
// we expect a warning to inform about a network issue
await waitFor(() => {});
expect(page.passwordCreate.pwnedWarningMessage.textContent).toEqual("The pwnedpasswords service is unavailable, your password might be part of an exposed data breach");
});
it("As a signed-in user creating a password on the application, I should see a complexity as Quality if the passphrase is empty", async() => {
expect.assertions(2);
page.passwordCreate.fillInput(page.passwordCreate.password, '');
await page.passwordCreate.keyUpInput(page.passwordCreate.password);
expect(page.passwordCreate.complexityText.textContent).toBe("Quality");
expect(page.passwordCreate.pwnedWarningMessage).toBeNull();
});
});
});