test(user-interface-configuration): write initial tests (#1242)
* test(user-interface): add tests with playwright * test: refactor UI configuration tests --------- Co-authored-by: Ali BARIN <ali.barin53@gmail.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { BasePage } = require('./base-page');
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
|
|
||||||
|
export class ApplicationsPage extends AuthenticatedPage {
|
||||||
|
screenshotPath = '/applications';
|
||||||
|
|
||||||
export class ApplicationsPage extends BasePage {
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
@@ -11,12 +13,4 @@ export class ApplicationsPage extends BasePage {
|
|||||||
this.drawerLink = this.page.getByTestId('apps-page-drawer-link');
|
this.drawerLink = this.page.getByTestId('apps-page-drawer-link');
|
||||||
this.addConnectionButton = this.page.getByTestId('add-connection-button');
|
this.addConnectionButton = this.page.getByTestId('add-connection-button');
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options = {}) {
|
|
||||||
const { path: plainPath, ...restOptions } = options;
|
|
||||||
|
|
||||||
const computedPath = path.join('applications', plainPath);
|
|
||||||
|
|
||||||
return await super.screenshot({ path: computedPath, ...restOptions });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
21
packages/e2e-tests/fixtures/authenticated-page.js
Normal file
21
packages/e2e-tests/fixtures/authenticated-page.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
const { BasePage } = require('./base-page');
|
||||||
|
const { LoginPage } = require('./login-page');
|
||||||
|
|
||||||
|
export class AuthenticatedPage extends BasePage {
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.profileMenuButton = this.page.getByTestId('profile-menu-button');
|
||||||
|
this.adminMenuItem = this.page.getByRole('menuitem', { name: 'Admin' });
|
||||||
|
this.userInterfaceDrawerItem = this.page.getByTestId('user-interface-drawer-link');
|
||||||
|
this.appBar = this.page.getByTestId('app-bar');
|
||||||
|
this.goToDashboardButton = this.page.getByTestId('go-back-drawer-link');
|
||||||
|
this.typographyLogo = this.page.getByTestId('typography-logo');
|
||||||
|
this.customLogo = this.page.getByTestId('custom-logo');
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,14 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
|
||||||
export class BasePage {
|
export class BasePage {
|
||||||
|
screenshotPath = '/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
constructor(page) {
|
constructor(page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
|
this.snackbar = this.page.locator('#notistack-snackbar');
|
||||||
}
|
}
|
||||||
|
|
||||||
async clickAway() {
|
async clickAway() {
|
||||||
@@ -15,7 +18,11 @@ export class BasePage {
|
|||||||
async screenshot(options = {}) {
|
async screenshot(options = {}) {
|
||||||
const { path: plainPath, ...restOptions } = options;
|
const { path: plainPath, ...restOptions } = options;
|
||||||
|
|
||||||
const computedPath = path.join('output/screenshots', plainPath);
|
const computedPath = path.join(
|
||||||
|
'output/screenshots',
|
||||||
|
this.screenshotPath,
|
||||||
|
plainPath
|
||||||
|
);
|
||||||
|
|
||||||
return await this.page.screenshot({ path: computedPath, ...restOptions });
|
return await this.page.screenshot({ path: computedPath, ...restOptions });
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,8 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { BasePage } = require('./base-page');
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
|
|
||||||
export class ConnectionsPage extends BasePage {
|
export class ConnectionsPage extends AuthenticatedPage {
|
||||||
async screenshot(options = {}) {
|
screenshotPath = '/connections';
|
||||||
const { path: plainPath, ...restOptions } = options;
|
|
||||||
|
|
||||||
const computedPath = path.join('connections', plainPath);
|
|
||||||
|
|
||||||
return await super.screenshot({ path: computedPath, ...restOptions });
|
|
||||||
}
|
|
||||||
|
|
||||||
async clickAddConnectionButton() {
|
async clickAddConnectionButton() {
|
||||||
await this.page.getByTestId('add-connection-button').click();
|
await this.page.getByTestId('add-connection-button').click();
|
||||||
|
@@ -1,12 +1,6 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { BasePage } = require('./base-page');
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
|
|
||||||
export class ExecutionsPage extends BasePage {
|
export class ExecutionsPage extends AuthenticatedPage {
|
||||||
async screenshot(options = {}) {
|
screenshotPath = '/executions';
|
||||||
const { path: plainPath, ...restOptions } = options;
|
|
||||||
|
|
||||||
const computedPath = path.join('executions', plainPath);
|
|
||||||
|
|
||||||
return await super.screenshot({ path: computedPath, ...restOptions });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { BasePage } = require('./base-page');
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
|
|
||||||
|
export class FlowEditorPage extends AuthenticatedPage {
|
||||||
|
screenshotPath = '/flow-editor';
|
||||||
|
|
||||||
export class FlowEditorPage extends BasePage {
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
@@ -21,12 +23,4 @@ export class FlowEditorPage extends BasePage {
|
|||||||
this.trigger = this.page.getByLabel('Trigger on weekends?');
|
this.trigger = this.page.getByLabel('Trigger on weekends?');
|
||||||
this.stepCircularLoader = this.page.getByTestId('step-circular-loader');
|
this.stepCircularLoader = this.page.getByTestId('step-circular-loader');
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options = {}) {
|
|
||||||
const { path: plainPath, ...restOptions } = options;
|
|
||||||
|
|
||||||
const computedPath = path.join('flow-editor', plainPath);
|
|
||||||
|
|
||||||
return await super.screenshot({ path: computedPath, ...restOptions });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ const { ApplicationsPage } = require('./applications-page');
|
|||||||
const { ConnectionsPage } = require('./connections-page');
|
const { ConnectionsPage } = require('./connections-page');
|
||||||
const { ExecutionsPage } = require('./executions-page');
|
const { ExecutionsPage } = require('./executions-page');
|
||||||
const { FlowEditorPage } = require('./flow-editor-page');
|
const { FlowEditorPage } = require('./flow-editor-page');
|
||||||
|
const { UserInterfacePage } = require('./user-interface-page');
|
||||||
const { LoginPage } = require('./login-page');
|
const { LoginPage } = require('./login-page');
|
||||||
|
|
||||||
exports.test = test.extend({
|
exports.test = test.extend({
|
||||||
@@ -23,6 +24,9 @@ exports.test = test.extend({
|
|||||||
flowEditorPage: async ({ page }, use) => {
|
flowEditorPage: async ({ page }, use) => {
|
||||||
await use(new FlowEditorPage(page));
|
await use(new FlowEditorPage(page));
|
||||||
},
|
},
|
||||||
|
userInterfacePage: async ({ page }, use) => {
|
||||||
|
await use(new UserInterfacePage(page));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect.extend({
|
expect.extend({
|
||||||
@@ -30,7 +34,7 @@ expect.extend({
|
|||||||
await expect(locator).not.toHaveAttribute('aria-disabled', 'true');
|
await expect(locator).not.toHaveAttribute('aria-disabled', 'true');
|
||||||
|
|
||||||
return { pass: true };
|
return { pass: true };
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.expect = expect;
|
exports.expect = expect;
|
||||||
|
@@ -3,6 +3,8 @@ const { expect } = require('@playwright/test');
|
|||||||
const { BasePage } = require('./base-page');
|
const { BasePage } = require('./base-page');
|
||||||
|
|
||||||
export class LoginPage extends BasePage {
|
export class LoginPage extends BasePage {
|
||||||
|
path = '/login';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
@@ -15,8 +17,6 @@ export class LoginPage extends BasePage {
|
|||||||
this.loginButton = this.page.getByTestId('login-button');
|
this.loginButton = this.page.getByTestId('login-button');
|
||||||
}
|
}
|
||||||
|
|
||||||
path = '/login';
|
|
||||||
|
|
||||||
async login() {
|
async login() {
|
||||||
await this.page.goto(this.path);
|
await this.page.goto(this.path);
|
||||||
await this.emailTextField.fill(process.env.LOGIN_EMAIL);
|
await this.emailTextField.fill(process.env.LOGIN_EMAIL);
|
||||||
|
53
packages/e2e-tests/fixtures/user-interface-page.js
Normal file
53
packages/e2e-tests/fixtures/user-interface-page.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
|
|
||||||
|
export class UserInterfacePage extends AuthenticatedPage {
|
||||||
|
screenshotPath = '/user-interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.flowRowCardActionArea = this.page
|
||||||
|
.getByTestId('flow-row')
|
||||||
|
.first()
|
||||||
|
.getByTestId('card-action-area');
|
||||||
|
this.updateButton = this.page.getByTestId('update-button');
|
||||||
|
this.primaryMainColorInput = this.page
|
||||||
|
.getByTestId('primary-main-color-input')
|
||||||
|
.getByTestId('color-text-field');
|
||||||
|
this.primaryDarkColorInput = this.page
|
||||||
|
.getByTestId('primary-dark-color-input')
|
||||||
|
.getByTestId('color-text-field');
|
||||||
|
this.primaryLightColorInput = this.page
|
||||||
|
.getByTestId('primary-light-color-input')
|
||||||
|
.getByTestId('color-text-field');
|
||||||
|
this.logoSvgCodeInput = this.page.getByTestId('logo-svg-data-text-field');
|
||||||
|
this.primaryMainColorButton = this.page
|
||||||
|
.getByTestId('primary-main-color-input')
|
||||||
|
.getByTestId('color-button');
|
||||||
|
this.primaryDarkColorButton = this.page
|
||||||
|
.getByTestId('primary-dark-color-input')
|
||||||
|
.getByTestId('color-button');
|
||||||
|
this.primaryLightColorButton = this.page
|
||||||
|
.getByTestId('primary-light-color-input')
|
||||||
|
.getByTestId('color-button');
|
||||||
|
}
|
||||||
|
|
||||||
|
hexToRgb(hexColor) {
|
||||||
|
hexColor = hexColor.replace('#', '');
|
||||||
|
const r = parseInt(hexColor.substring(0, 2), 16);
|
||||||
|
const g = parseInt(hexColor.substring(2, 4), 16);
|
||||||
|
const b = parseInt(hexColor.substring(4, 6), 16);
|
||||||
|
|
||||||
|
return `rgb(${r}, ${g}, ${b})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeSVG(svgCode) {
|
||||||
|
const encoded = encodeURIComponent(svgCode);
|
||||||
|
|
||||||
|
return `data:image/svg+xml;utf8,${encoded}`;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,176 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
|
test.describe('User interface page', () => {
|
||||||
|
test.beforeEach(async ({ userInterfacePage }) => {
|
||||||
|
await userInterfacePage.profileMenuButton.click();
|
||||||
|
await userInterfacePage.adminMenuItem.click();
|
||||||
|
await expect(userInterfacePage.page).toHaveURL(/\/admin-settings\/users/);
|
||||||
|
await userInterfacePage.userInterfaceDrawerItem.click();
|
||||||
|
await expect(userInterfacePage.page).toHaveURL(
|
||||||
|
/\/admin-settings\/user-interface/
|
||||||
|
);
|
||||||
|
await userInterfacePage.page.waitForURL(/\/admin-settings\/user-interface/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('checks if the shown values are used', async () => {
|
||||||
|
test('checks primary main color', async ({ userInterfacePage }) => {
|
||||||
|
await userInterfacePage.primaryMainColorInput.waitFor({
|
||||||
|
state: 'attached',
|
||||||
|
});
|
||||||
|
const initialPrimaryMainColor =
|
||||||
|
await userInterfacePage.primaryMainColorInput.inputValue();
|
||||||
|
const initialRgbColor = userInterfacePage.hexToRgb(
|
||||||
|
initialPrimaryMainColor
|
||||||
|
);
|
||||||
|
await expect(userInterfacePage.updateButton).toHaveCSS(
|
||||||
|
'background-color',
|
||||||
|
initialRgbColor
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checks primary dark color', async ({ userInterfacePage }) => {
|
||||||
|
await userInterfacePage.primaryDarkColorInput.waitFor({
|
||||||
|
state: 'attached',
|
||||||
|
});
|
||||||
|
const initialPrimaryDarkColor =
|
||||||
|
await userInterfacePage.primaryDarkColorInput.inputValue();
|
||||||
|
const initialRgbColor = userInterfacePage.hexToRgb(
|
||||||
|
initialPrimaryDarkColor
|
||||||
|
);
|
||||||
|
await expect(userInterfacePage.appBar).toHaveCSS(
|
||||||
|
'background-color',
|
||||||
|
initialRgbColor
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checks custom logo', async ({ userInterfacePage }) => {
|
||||||
|
const initialLogoSvgCode =
|
||||||
|
await userInterfacePage.logoSvgCodeInput.inputValue();
|
||||||
|
const logoSrcAttribute = await userInterfacePage.customLogo.getAttribute(
|
||||||
|
'src'
|
||||||
|
);
|
||||||
|
const svgCode = userInterfacePage.encodeSVG(initialLogoSvgCode);
|
||||||
|
expect(logoSrcAttribute).toMatch(svgCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe(
|
||||||
|
'fill fields and check if the inputs reflect them properly',
|
||||||
|
async () => {
|
||||||
|
test('fill primary main color and check the color input', async ({
|
||||||
|
userInterfacePage,
|
||||||
|
}) => {
|
||||||
|
await userInterfacePage.primaryMainColorInput.fill('#FF5733');
|
||||||
|
const rgbColor = userInterfacePage.hexToRgb('#FF5733');
|
||||||
|
const button = await userInterfacePage.primaryMainColorButton;
|
||||||
|
const styleAttribute = await button.getAttribute('style');
|
||||||
|
expect(styleAttribute).toEqual(`background-color: ${rgbColor};`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fill primary dark color and check the color input', async ({
|
||||||
|
userInterfacePage,
|
||||||
|
}) => {
|
||||||
|
await userInterfacePage.primaryDarkColorInput.fill('#12F63F');
|
||||||
|
const rgbColor = userInterfacePage.hexToRgb('#12F63F');
|
||||||
|
const button = await userInterfacePage.primaryDarkColorButton;
|
||||||
|
const styleAttribute = await button.getAttribute('style');
|
||||||
|
expect(styleAttribute).toEqual(`background-color: ${rgbColor};`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fill primary light color and check the color input', async ({
|
||||||
|
userInterfacePage,
|
||||||
|
}) => {
|
||||||
|
await userInterfacePage.primaryLightColorInput.fill('#1D0BF5');
|
||||||
|
const rgbColor = userInterfacePage.hexToRgb('#1D0BF5');
|
||||||
|
const button = await userInterfacePage.primaryLightColorButton;
|
||||||
|
const styleAttribute = await button.getAttribute('style');
|
||||||
|
expect(styleAttribute).toEqual(`background-color: ${rgbColor};`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.describe(
|
||||||
|
'update form based on input values and check if the inputs still reflect them',
|
||||||
|
async () => {
|
||||||
|
test('update primary main color and check color input', async ({
|
||||||
|
userInterfacePage,
|
||||||
|
}) => {
|
||||||
|
await userInterfacePage.primaryMainColorInput.fill('#00adef');
|
||||||
|
await userInterfacePage.updateButton.click();
|
||||||
|
const rgbColor = userInterfacePage.hexToRgb('#00adef');
|
||||||
|
const button = await userInterfacePage.primaryMainColorButton;
|
||||||
|
const styleAttribute = await button.getAttribute('style');
|
||||||
|
expect(styleAttribute).toBe(`background-color: ${rgbColor};`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update primary dark color and check color input', async ({
|
||||||
|
userInterfacePage,
|
||||||
|
}) => {
|
||||||
|
await userInterfacePage.primaryDarkColorInput.fill('#222222');
|
||||||
|
await userInterfacePage.updateButton.click();
|
||||||
|
const rgbColor = userInterfacePage.hexToRgb('#222222');
|
||||||
|
const button = await userInterfacePage.primaryDarkColorButton;
|
||||||
|
const styleAttribute = await button.getAttribute('style');
|
||||||
|
expect(styleAttribute).toBe(`background-color: ${rgbColor};`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update primary light color and check color input', async ({
|
||||||
|
userInterfacePage,
|
||||||
|
}) => {
|
||||||
|
await userInterfacePage.primaryLightColorInput.fill('#f90707');
|
||||||
|
await userInterfacePage.updateButton.click();
|
||||||
|
const rgbColor = userInterfacePage.hexToRgb('#f90707');
|
||||||
|
const button = await userInterfacePage.primaryLightColorButton;
|
||||||
|
const styleAttribute = await button.getAttribute('style');
|
||||||
|
expect(styleAttribute).toBe(`background-color: ${rgbColor};`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.describe('update form based on input values', async () => {
|
||||||
|
test('fill primary main color', async ({ userInterfacePage }) => {
|
||||||
|
await userInterfacePage.primaryMainColorInput.fill('#00adef');
|
||||||
|
await userInterfacePage.updateButton.click();
|
||||||
|
await userInterfacePage.snackbar.waitFor({ state: 'visible' });
|
||||||
|
await userInterfacePage.screenshot({
|
||||||
|
path: 'updated primary main color.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fill primary dark color', async ({ userInterfacePage }) => {
|
||||||
|
await userInterfacePage.primaryDarkColorInput.fill('#222222');
|
||||||
|
await userInterfacePage.updateButton.click();
|
||||||
|
await userInterfacePage.snackbar.waitFor({ state: 'visible' });
|
||||||
|
await userInterfacePage.screenshot({
|
||||||
|
path: 'updated primary dark color.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fill primary light color', async ({ userInterfacePage }) => {
|
||||||
|
await userInterfacePage.primaryLightColorInput.fill('#f90707');
|
||||||
|
await userInterfacePage.updateButton.click();
|
||||||
|
await userInterfacePage.goToDashboardButton.click();
|
||||||
|
await expect(userInterfacePage.page).toHaveURL('/flows');
|
||||||
|
const span = await userInterfacePage.flowRowCardActionArea;
|
||||||
|
await span.waitFor({ state: 'visible' });
|
||||||
|
await span.hover();
|
||||||
|
await userInterfacePage.screenshot({
|
||||||
|
path: 'updated primary light color.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fill logo svg code', async ({ userInterfacePage }) => {
|
||||||
|
await userInterfacePage.logoSvgCodeInput
|
||||||
|
.fill(`<svg width="25" height="25" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100">
|
||||||
|
<rect width="100%" height="100%" fill="white" />
|
||||||
|
<text x="10" y="40" font-family="Arial" font-size="40" fill="black">A</text>
|
||||||
|
</svg>`);
|
||||||
|
await userInterfacePage.updateButton.click();
|
||||||
|
await userInterfacePage.snackbar.waitFor({ state: 'visible' });
|
||||||
|
await userInterfacePage.screenshot({
|
||||||
|
path: 'updated svg code.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -42,6 +42,7 @@ function createDrawerLinks({
|
|||||||
Icon: GroupIcon,
|
Icon: GroupIcon,
|
||||||
primary: 'adminSettingsDrawer.users',
|
primary: 'adminSettingsDrawer.users',
|
||||||
to: URLS.USERS,
|
to: URLS.USERS,
|
||||||
|
dataTest: 'users-drawer-link',
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
canReadRole
|
canReadRole
|
||||||
@@ -49,6 +50,7 @@ function createDrawerLinks({
|
|||||||
Icon: GroupsIcon,
|
Icon: GroupsIcon,
|
||||||
primary: 'adminSettingsDrawer.roles',
|
primary: 'adminSettingsDrawer.roles',
|
||||||
to: URLS.ROLES,
|
to: URLS.ROLES,
|
||||||
|
dataTest: 'roles-drawer-link',
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
canUpdateConfig
|
canUpdateConfig
|
||||||
@@ -56,6 +58,7 @@ function createDrawerLinks({
|
|||||||
Icon: BrushIcon,
|
Icon: BrushIcon,
|
||||||
primary: 'adminSettingsDrawer.userInterface',
|
primary: 'adminSettingsDrawer.userInterface',
|
||||||
to: URLS.USER_INTERFACE,
|
to: URLS.USER_INTERFACE,
|
||||||
|
dataTest: 'user-interface-drawer-link',
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
canManageSamlAuthProvider
|
canManageSamlAuthProvider
|
||||||
@@ -63,6 +66,7 @@ function createDrawerLinks({
|
|||||||
Icon: LockIcon,
|
Icon: LockIcon,
|
||||||
primary: 'adminSettingsDrawer.authentication',
|
primary: 'adminSettingsDrawer.authentication',
|
||||||
to: URLS.AUTHENTICATION,
|
to: URLS.AUTHENTICATION,
|
||||||
|
dataTest: 'authentication-drawer-link',
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
].filter(Boolean) as DrawerLink[];
|
].filter(Boolean) as DrawerLink[];
|
||||||
@@ -75,6 +79,7 @@ const drawerBottomLinks = [
|
|||||||
Icon: ArrowBackIosNewIcon,
|
Icon: ArrowBackIosNewIcon,
|
||||||
primary: 'adminSettingsDrawer.goBack',
|
primary: 'adminSettingsDrawer.goBack',
|
||||||
to: '/',
|
to: '/',
|
||||||
|
dataTest: 'go-back-drawer-link',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -46,7 +46,7 @@ export default function AppBar(props: AppBarProps): React.ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiAppBar>
|
<MuiAppBar data-test="app-bar">
|
||||||
<Container maxWidth={maxWidth} disableGutters>
|
<Container maxWidth={maxWidth} disableGutters>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
40
packages/web/src/components/ColorInput/ColorButton/index.tsx
Normal file
40
packages/web/src/components/ColorInput/ColorButton/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ButtonProps } from '@mui/material/Button';
|
||||||
|
import { Button } from './style';
|
||||||
|
|
||||||
|
const BG_IMAGE_FALLBACK =
|
||||||
|
'linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(135deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(135deg, transparent 75%, #ccc 75%) /*! @noflip */';
|
||||||
|
|
||||||
|
export type ColorButtonProps = Omit<ButtonProps, 'children'> & {
|
||||||
|
bgColor: string;
|
||||||
|
isBgColorValid: boolean;
|
||||||
|
disablePopover: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ColorButtonElement = (props: ColorButtonProps) => JSX.Element;
|
||||||
|
|
||||||
|
const ColorButton = (props: ColorButtonProps) => {
|
||||||
|
const {
|
||||||
|
bgColor,
|
||||||
|
className,
|
||||||
|
disablePopover,
|
||||||
|
isBgColorValid,
|
||||||
|
...restButtonProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
data-test="color-button"
|
||||||
|
disableTouchRipple
|
||||||
|
style={{
|
||||||
|
backgroundColor: isBgColorValid ? bgColor : undefined,
|
||||||
|
backgroundImage: isBgColorValid ? undefined : BG_IMAGE_FALLBACK,
|
||||||
|
cursor: disablePopover ? 'default' : undefined,
|
||||||
|
}}
|
||||||
|
className={`MuiColorInput-Button ${className || ''}`}
|
||||||
|
{...restButtonProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorButton;
|
15
packages/web/src/components/ColorInput/ColorButton/style.tsx
Normal file
15
packages/web/src/components/ColorInput/ColorButton/style.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import MuiButton from '@mui/material/Button';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
|
export const Button = styled(MuiButton)(() => ({
|
||||||
|
backgroundSize: '8px 8px',
|
||||||
|
backgroundPosition: '0 0, 4px 0, 4px -4px, 0px 4px',
|
||||||
|
transition: 'none',
|
||||||
|
boxShadow: '0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08)',
|
||||||
|
border: 0,
|
||||||
|
borderRadius: 4,
|
||||||
|
width: '24px',
|
||||||
|
aspectRatio: '1 / 1',
|
||||||
|
height: '24px',
|
||||||
|
minWidth: 0,
|
||||||
|
})) as typeof MuiButton;
|
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import { MuiColorInput, MuiColorInputProps } from 'mui-color-input';
|
import { MuiColorInput, MuiColorInputProps } from 'mui-color-input';
|
||||||
|
import ColorButton from './ColorButton';
|
||||||
|
|
||||||
type ColorInputProps = {
|
type ColorInputProps = {
|
||||||
shouldUnregister?: boolean;
|
shouldUnregister?: boolean;
|
||||||
@@ -15,7 +16,6 @@ export default function ColorInput(props: ColorInputProps): React.ReactElement {
|
|||||||
name,
|
name,
|
||||||
shouldUnregister = false,
|
shouldUnregister = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
'data-test': dataTest,
|
|
||||||
...textFieldProps
|
...textFieldProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -27,12 +27,13 @@ export default function ColorInput(props: ColorInputProps): React.ReactElement {
|
|||||||
shouldUnregister={shouldUnregister}
|
shouldUnregister={shouldUnregister}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<MuiColorInput
|
<MuiColorInput
|
||||||
|
Adornment={ColorButton}
|
||||||
format="hex"
|
format="hex"
|
||||||
{...textFieldProps}
|
{...textFieldProps}
|
||||||
{...field}
|
{...field}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
'data-test': dataTest,
|
'data-test': 'color-text-field',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@@ -8,7 +8,10 @@ const CustomLogo = () => {
|
|||||||
const logoSvgData = config['logo.svgData'] as string;
|
const logoSvgData = config['logo.svgData'] as string;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<img src={`data:image/svg+xml;utf8,${encodeURIComponent(logoSvgData)}`} />
|
<img
|
||||||
|
data-test="custom-logo"
|
||||||
|
src={`data:image/svg+xml;utf8,${encodeURIComponent(logoSvgData)}`}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -68,7 +68,8 @@ export default function Drawer(props: DrawerProps): React.ReactElement {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<List sx={{ py: 0, mt: 3 }}>
|
<List sx={{ py: 0, mt: 3 }}>
|
||||||
{bottomLinks.map(({ Icon, badgeContent, primary, to }, index) => (
|
{bottomLinks.map(
|
||||||
|
({ Icon, badgeContent, primary, to, dataTest }, index) => (
|
||||||
<ListItemLink
|
<ListItemLink
|
||||||
key={`${to}-${index}`}
|
key={`${to}-${index}`}
|
||||||
icon={
|
icon={
|
||||||
@@ -79,8 +80,10 @@ export default function Drawer(props: DrawerProps): React.ReactElement {
|
|||||||
primary={formatMessage(primary)}
|
primary={formatMessage(primary)}
|
||||||
to={to}
|
to={to}
|
||||||
onClick={closeOnClick}
|
onClick={closeOnClick}
|
||||||
|
data-test={dataTest}
|
||||||
/>
|
/>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</List>
|
</List>
|
||||||
</BaseDrawer>
|
</BaseDrawer>
|
||||||
);
|
);
|
||||||
|
@@ -18,7 +18,7 @@ type FlowRowProps = {
|
|||||||
flow: IFlow;
|
flow: IFlow;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFlowStatusTranslationKey(status: IFlow["status"]): string {
|
function getFlowStatusTranslationKey(status: IFlow['status']): string {
|
||||||
if (status === 'published') {
|
if (status === 'published') {
|
||||||
return 'flow.published';
|
return 'flow.published';
|
||||||
} else if (status === 'paused') {
|
} else if (status === 'paused') {
|
||||||
@@ -28,7 +28,16 @@ function getFlowStatusTranslationKey(status: IFlow["status"]): string {
|
|||||||
return 'flow.draft';
|
return 'flow.draft';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFlowStatusColor(status: IFlow["status"]): 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' {
|
function getFlowStatusColor(
|
||||||
|
status: IFlow['status']
|
||||||
|
):
|
||||||
|
| 'default'
|
||||||
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
|
| 'error'
|
||||||
|
| 'info'
|
||||||
|
| 'success'
|
||||||
|
| 'warning' {
|
||||||
if (status === 'published') {
|
if (status === 'published') {
|
||||||
return 'success';
|
return 'success';
|
||||||
} else if (status === 'paused') {
|
} else if (status === 'paused') {
|
||||||
@@ -64,8 +73,12 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card sx={{ mb: 1 }}>
|
<Card sx={{ mb: 1 }} data-test="flow-row">
|
||||||
<CardActionArea component={Link} to={URLS.FLOW(flow.id)}>
|
<CardActionArea
|
||||||
|
component={Link}
|
||||||
|
to={URLS.FLOW(flow.id)}
|
||||||
|
data-test="card-action-area"
|
||||||
|
>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Apps direction="row" gap={1} sx={{ gridArea: 'apps' }}>
|
<Apps direction="row" gap={1} sx={{ gridArea: 'apps' }}>
|
||||||
<FlowAppIcons steps={flow.steps} />
|
<FlowAppIcons steps={flow.steps} />
|
||||||
@@ -98,9 +111,7 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement {
|
|||||||
size="small"
|
size="small"
|
||||||
color={getFlowStatusColor(flow?.status)}
|
color={getFlowStatusColor(flow?.status)}
|
||||||
variant={flow?.active ? 'filled' : 'outlined'}
|
variant={flow?.active ? 'filled' : 'outlined'}
|
||||||
label={formatMessage(
|
label={formatMessage(getFlowStatusTranslationKey(flow?.status))}
|
||||||
getFlowStatusTranslationKey(flow?.status)
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@@ -9,12 +9,12 @@ const Logo = () => {
|
|||||||
const { config, loading } = useConfig(['logo.svgData']);
|
const { config, loading } = useConfig(['logo.svgData']);
|
||||||
|
|
||||||
const logoSvgData = config?.['logo.svgData'] as string;
|
const logoSvgData = config?.['logo.svgData'] as string;
|
||||||
if (loading && !logoSvgData) return (<React.Fragment />);
|
if (loading && !logoSvgData) return <React.Fragment />;
|
||||||
|
|
||||||
if (logoSvgData) return <CustomLogo />;
|
if (logoSvgData) return <CustomLogo />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography variant="h6" component="h1" noWrap>
|
<Typography variant="h6" component="h1" data-test="typography-logo" noWrap>
|
||||||
<FormattedMessage id="brandText" />
|
<FormattedMessage id="brandText" />
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
|
@@ -90,18 +90,21 @@ export default function UserInterface(): React.ReactElement {
|
|||||||
name="palette.primary.main"
|
name="palette.primary.main"
|
||||||
label={formatMessage('userInterfacePage.mainColor')}
|
label={formatMessage('userInterfacePage.mainColor')}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
data-test="primary-main-color-input"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ColorInput
|
<ColorInput
|
||||||
name="palette.primary.dark"
|
name="palette.primary.dark"
|
||||||
label={formatMessage('userInterfacePage.darkColor')}
|
label={formatMessage('userInterfacePage.darkColor')}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
data-test="primary-dark-color-input"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ColorInput
|
<ColorInput
|
||||||
name="palette.primary.light"
|
name="palette.primary.light"
|
||||||
label={formatMessage('userInterfacePage.lightColor')}
|
label={formatMessage('userInterfacePage.lightColor')}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
data-test="primary-light-color-input"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
@@ -109,6 +112,7 @@ export default function UserInterface(): React.ReactElement {
|
|||||||
label={formatMessage('userInterfacePage.svgData')}
|
label={formatMessage('userInterfacePage.svgData')}
|
||||||
multiline
|
multiline
|
||||||
fullWidth
|
fullWidth
|
||||||
|
data-test="logo-svg-data-text-field"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@@ -117,6 +121,7 @@ export default function UserInterface(): React.ReactElement {
|
|||||||
color="primary"
|
color="primary"
|
||||||
sx={{ boxShadow: 2 }}
|
sx={{ boxShadow: 2 }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
data-test="update-button"
|
||||||
>
|
>
|
||||||
{formatMessage('userInterfacePage.submit')}
|
{formatMessage('userInterfacePage.submit')}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
|
Reference in New Issue
Block a user