From c4826556d9fcbbd5030001807b4a5f731e1d26e5 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Thu, 26 Oct 2023 18:03:16 +0800 Subject: [PATCH] chore(test): init org ui tests --- .../CreateOrganizationModal/index.tsx | 34 ++++----- .../Organizations/PermissionModal/index.tsx | 40 ++++++----- packages/integration-tests/src/constants.ts | 4 +- .../src/tests/console/organizations.test.ts | 70 +++++++++++++++++++ .../src/ui-helpers/expect-console.ts | 69 +++++++++++++++++- .../src/ui-helpers/expect-organizations.ts | 23 ++++++ .../src/ui-helpers/expect-page.ts | 27 ++++++- 7 files changed, 226 insertions(+), 41 deletions(-) create mode 100644 packages/integration-tests/src/tests/console/organizations.test.ts create mode 100644 packages/integration-tests/src/ui-helpers/expect-organizations.ts diff --git a/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx b/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx index 33868feaf..cfc67f853 100644 --- a/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx +++ b/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx @@ -60,22 +60,24 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) { } onClose={onClose} > - - - - - - +
+ + + + + + +
); diff --git a/packages/console/src/pages/Organizations/PermissionModal/index.tsx b/packages/console/src/pages/Organizations/PermissionModal/index.tsx index 588be5c63..3954d08ac 100644 --- a/packages/console/src/pages/Organizations/PermissionModal/index.tsx +++ b/packages/console/src/pages/Organizations/PermissionModal/index.tsx @@ -78,25 +78,27 @@ function PermissionModal({ isOpen, editData, onClose }: Props) { } onClose={onClose} > - - - - - - +
+ + + + + + +
); diff --git a/packages/integration-tests/src/constants.ts b/packages/integration-tests/src/constants.ts index 19e6b668d..af4ee4ad6 100644 --- a/packages/integration-tests/src/constants.ts +++ b/packages/integration-tests/src/constants.ts @@ -22,6 +22,6 @@ export const signUpIdentifiers = { none: [], }; -export const consoleUsername = 'admin'; -export const consolePassword = 'some_random_password_123'; +export const consoleUsername = 'svhd'; +export const consolePassword = 'silverhandasd_1'; export const mockSocialAuthPageUrl = 'http://mock.social.com'; diff --git a/packages/integration-tests/src/tests/console/organizations.test.ts b/packages/integration-tests/src/tests/console/organizations.test.ts new file mode 100644 index 000000000..f95424c7e --- /dev/null +++ b/packages/integration-tests/src/tests/console/organizations.test.ts @@ -0,0 +1,70 @@ +import ExpectOrganizations from '#src/ui-helpers/expect-organizations.js'; +import { cls, dcls, generateTestName } from '#src/utils.js'; + +const expectOrg = new ExpectOrganizations(await browser.newPage()); + +describe('organizations: create, edit, and delete organization', () => { + it('navigates to organizations page', async () => { + await expectOrg.start(); + await expectOrg.gotoPage('/organizations', 'Organizations'); + await expectOrg.toExpectTabs('Organizations', 'Settings'); + }); + + it('should be able to see the table', async () => { + await expectOrg.toExpectTableHeaders('Name', 'Organization ID', 'Members'); + }); + + it('should be able to create a new organization', async () => { + await expectOrg.toCreateOrganization('Test organization'); + }); + + it('should be able to edit the created organization', async () => { + await expectOrg.toExpectTabs('Settings', 'Members'); + + const nameUpdated = 'Test organization updated'; + await expectOrg.toFillForm({ + name: nameUpdated, + description: 'Test organization description', + }); + await expectOrg.toSaveChanges(); + await expectOrg.toMatchElement([dcls('header'), dcls('metadata'), dcls('name')].join(' '), { + text: nameUpdated, + }); + }); + + it('should be able to delete the created organization', async () => { + // Open the dropdown menu + await expectOrg.toClick( + [dcls('header'), `button${cls('withIcon')}`].join(' '), + undefined, + false + ); + await expectOrg.toClick(`${dcls('danger')}[role=menuitem]`, 'Delete organization', false); + await expectOrg.toExpectModal('Reminder'); + await expectOrg.toClick(['.ReactModalPortal', `button${cls('danger')}`].join(' '), 'Delete'); + expectOrg.toMatchUrl(/\/organizations$/); + }); +}); + +describe('organizations: search organization', () => { + const [testName1, testName2] = [generateTestName(), generateTestName()]; + + it('creates two organizations', async () => { + await expectOrg.start(); + await expectOrg.toCreateOrganization(testName1); + await expectOrg.toCreateOrganization(testName2); + await expectOrg.gotoPage('/organizations', 'Organizations'); + }); + + it('should be able to search organization', async () => { + await expectOrg.toFill([dcls('search'), 'input'].join(' '), testName1); + await expectOrg.toClickButton('Search', false); + await expectOrg.toExpectTableCell(testName1); + + // This will work as expected since we can ensure the search result is ready from the previous step + await expect(expectOrg.page).not.toMatchElement([`table`, 'tbody', 'tr', 'td'].join(' '), { + text: new RegExp(testName2, 'i'), + visible: true, + }); + }); +}); diff --git a/packages/integration-tests/src/ui-helpers/expect-console.ts b/packages/integration-tests/src/ui-helpers/expect-console.ts index 5a5b0870b..b73e3b8b0 100644 --- a/packages/integration-tests/src/ui-helpers/expect-console.ts +++ b/packages/integration-tests/src/ui-helpers/expect-console.ts @@ -19,7 +19,7 @@ type ExpectConsoleOptions = { tenantId?: string; }; -export type ConsoleTitle = 'Sign-in experience'; +export type ConsoleTitle = 'Sign-in experience' | 'Organizations'; export default class ExpectConsole extends ExpectPage { readonly options: Required; @@ -48,17 +48,82 @@ export default class ExpectConsole extends ExpectPage { } } + /** + * Alias for `expect(page).toMatchElement(...)`. + * + * @see {@link jest.Matchers.toMatchElement} + */ + async toMatchElement(...args: Parameters['toMatchElement']>) { + return expect(this.page).toMatchElement(...args); + } + /** * Navigate to a specific page in the Console. */ async gotoPage(pathname: string, title: ConsoleTitle) { await this.navigateTo(this.buildUrl(path.join(this.options.tenantId, pathname))); await expect(this.page).toMatchElement( - [dcls('main'), dcls('container'), dcls('cardTitle')].join(' '), + [dcls('main'), dcls('container'), dcls('title')].join(' '), { text: title } ); } + /** + * Expect navigation tabs with the given names to be rendered in the Console. + * + * @param tabNames The names of the tabs to expect, case-insensitive. + */ + async toExpectTabs(...tabNames: string[]) { + await Promise.all( + tabNames.map(async (tabName) => { + return expect(this.page).toMatchElement( + ['nav', dcls('item'), dcls('link'), 'a'].join(' '), + { text: new RegExp(tabName, 'i'), visible: true } + ); + }) + ); + } + + /** + * Expect table headers with the given names to be rendered in the Console. + * + * @param headers The names of the table headers to expect, case-insensitive. + */ + async toExpectTableHeaders(...headers: string[]) { + await Promise.all( + headers.map(async (header) => { + return expect(this.page).toMatchElement( + [`table${cls('headerTable')}`, 'thead', 'tr', 'th'].join(' '), + { text: new RegExp(header, 'i'), visible: true } + ); + }) + ); + } + + /** + * Expect a table cell with the given text to be rendered in the Console. + * + * @param text The text to expect, case-insensitive. + */ + async toExpectTableCell(text: string) { + await expect(this.page).toMatchElement([`table`, 'tbody', 'tr', 'td'].join(' '), { + text: new RegExp(text, 'i'), + visible: true, + }); + } + + /** + * Expect a modal to appear with the given title. + * + * @param title The title of the modal to expect, case-insensitive. + */ + async toExpectModal(title: string) { + await expect(this.page).toMatchElement( + ['div.ReactModalPortal', dcls('header'), dcls('title')].join(' '), + { text: new RegExp(title, 'i'), visible: true } + ); + } + /** * Expect card components to be rendered in the Console. * diff --git a/packages/integration-tests/src/ui-helpers/expect-organizations.ts b/packages/integration-tests/src/ui-helpers/expect-organizations.ts new file mode 100644 index 000000000..be27b4e67 --- /dev/null +++ b/packages/integration-tests/src/ui-helpers/expect-organizations.ts @@ -0,0 +1,23 @@ +import { cls } from '#src/utils.js'; + +import ExpectConsole from './expect-console.js'; + +export default class ExpectOrganizations extends ExpectConsole { + /** + * Go to the organizations page and create a new organization, then assert that the URL matches + * the organization detail page. + * + * @param name The name of the organization to create. + */ + async toCreateOrganization(name: string) { + await this.gotoPage('/organizations', 'Organizations'); + await this.toClickButton('Create organization'); + + await this.toExpectModal('Create organization'); + await this.toFillForm({ + name, + }); + await this.toClick(['.ReactModalPortal', `button${cls('primary')}`].join(' '), 'Create', false); + this.toMatchUrl(/\/organizations\/.+$/); + } +} diff --git a/packages/integration-tests/src/ui-helpers/expect-page.ts b/packages/integration-tests/src/ui-helpers/expect-page.ts index 7be51f301..7b00c6d04 100644 --- a/packages/integration-tests/src/ui-helpers/expect-page.ts +++ b/packages/integration-tests/src/ui-helpers/expect-page.ts @@ -41,6 +41,17 @@ export default class ExpectPage { return shouldNavigate ? expectNavigation(clicked, this.page) : clicked; } + /** + * Shortcut for {@link ExpectPage.toClick} with the selector `button` and the given text. + * + * @param text The text to match. + * @param shouldNavigate Whether the click should trigger a navigation. Defaults to `true`. + * @see {@link ExpectPage.toClick} + */ + async toClickButton(text: string, shouldNavigate = true) { + return this.toClick('button', text, shouldNavigate); + } + /** * Click on the `