mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
chore(test): init org ui tests
This commit is contained in:
parent
7ea5b94178
commit
c4826556d9
7 changed files with 226 additions and 41 deletions
|
@ -60,6 +60,7 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) {
|
|||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form>
|
||||
<FormField isRequired title="general.name">
|
||||
<TextInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
|
@ -76,6 +77,7 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) {
|
|||
{...register('description')}
|
||||
/>
|
||||
</FormField>
|
||||
</form>
|
||||
</ModalLayout>
|
||||
</ReactModal>
|
||||
);
|
||||
|
|
|
@ -78,6 +78,7 @@ function PermissionModal({ isOpen, editData, onClose }: Props) {
|
|||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form>
|
||||
<FormField isRequired title="general.name">
|
||||
<TextInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
|
@ -97,6 +98,7 @@ function PermissionModal({ isOpen, editData, onClose }: Props) {
|
|||
{...register('description')}
|
||||
/>
|
||||
</FormField>
|
||||
</form>
|
||||
</ModalLayout>
|
||||
</ReactModal>
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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<ExpectConsoleOptions>;
|
||||
|
@ -48,17 +48,82 @@ export default class ExpectConsole extends ExpectPage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for `expect(page).toMatchElement(...)`.
|
||||
*
|
||||
* @see {@link jest.Matchers.toMatchElement}
|
||||
*/
|
||||
async toMatchElement(...args: Parameters<jest.Matchers<unknown>['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.
|
||||
*
|
||||
|
|
|
@ -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\/.+$/);
|
||||
}
|
||||
}
|
|
@ -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 `<button type="submit">` element on the page.
|
||||
*
|
||||
|
@ -64,6 +75,13 @@ export default class ExpectPage {
|
|||
return shouldNavigate ? expectNavigation(submitted, this.page) : submitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for {@link jest.Matchers['toFill']}.
|
||||
*/
|
||||
async toFill(...args: Parameters<jest.Matchers<unknown>['toFill']>) {
|
||||
return expect(this.page).toFill(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an `<input>` with the given name with the given value and optionally submit the form.
|
||||
*
|
||||
|
@ -116,9 +134,14 @@ export default class ExpectPage {
|
|||
/**
|
||||
* Expect the page's URL to match the given URL.
|
||||
*
|
||||
* @param url The URL to match.
|
||||
* @param url The URL to match, or a regular expression to match the URL against.
|
||||
*/
|
||||
toMatchUrl(url: URL | string) {
|
||||
toMatchUrl(url: URL | string | RegExp) {
|
||||
if (url instanceof RegExp) {
|
||||
expect(this.page.url()).toMatch(url);
|
||||
return;
|
||||
}
|
||||
|
||||
expect(this.page.url()).toBe(typeof url === 'string' ? url : url.href);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue