test: collections and new test ids and classes

This commit is contained in:
Elliot DeNolf
2022-07-14 16:16:16 -07:00
parent 11600930b7
commit 4a3588e965
13 changed files with 74 additions and 48 deletions

View File

@@ -25,7 +25,9 @@ const ButtonContents = ({ children, icon, tooltip }) => {
const BuiltInIcon = icons[icon]; const BuiltInIcon = icons[icon];
return ( return (
<span className={`${baseClass}__content`}> <span
className={`${baseClass}__content`}
>
{tooltip && ( {tooltip && (
<Tooltip className={`${baseClass}__tooltip`}> <Tooltip className={`${baseClass}__tooltip`}>
{tooltip} {tooltip}
@@ -49,6 +51,7 @@ const ButtonContents = ({ children, icon, tooltip }) => {
const Button: React.FC<Props> = (props) => { const Button: React.FC<Props> = (props) => {
const { const {
className, className,
id,
type = 'button', type = 'button',
el, el,
to, to,
@@ -86,6 +89,7 @@ const Button: React.FC<Props> = (props) => {
} }
const buttonProps = { const buttonProps = {
id,
type, type,
className: classes, className: classes,
disabled, disabled,

View File

@@ -2,6 +2,7 @@ import React, { MouseEvent } from 'react';
export type Props = { export type Props = {
className?: string, className?: string,
id?: string,
type?: 'submit' | 'button', type?: 'submit' | 'button',
el?: 'link' | 'anchor' | undefined, el?: 'link' | 'anchor' | undefined,
to?: string, to?: string,
@@ -12,6 +13,7 @@ export type Props = {
icon?: React.ReactNode | ['chevron' | 'x' | 'plus' | 'edit'], icon?: React.ReactNode | ['chevron' | 'x' | 'plus' | 'edit'],
iconStyle?: 'with-border' | 'without-border' | 'none', iconStyle?: 'with-border' | 'without-border' | 'none',
buttonStyle?: 'primary' | 'secondary' | 'transparent' | 'error' | 'none' | 'icon-label', buttonStyle?: 'primary' | 'secondary' | 'transparent' | 'error' | 'none' | 'icon-label',
buttonId?: string,
round?: boolean, round?: boolean,
size?: 'small' | 'medium', size?: 'small' | 'medium',
iconPosition?: 'left' | 'right', iconPosition?: 'left' | 'right',

View File

@@ -18,6 +18,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
const { const {
title: titleFromProps, title: titleFromProps,
id, id,
buttonId,
collection: { collection: {
admin: { admin: {
useAsTitle, useAsTitle,
@@ -78,6 +79,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
<React.Fragment> <React.Fragment>
<button <button
type="button" type="button"
id={buttonId}
className={`${baseClass}__toggle`} className={`${baseClass}__toggle`}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@@ -105,6 +107,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
&quot;. Are you sure? &quot;. Are you sure?
</p> </p>
<Button <Button
id="confirm-cancel"
buttonStyle="secondary" buttonStyle="secondary"
type="button" type="button"
onClick={deleting ? undefined : () => toggle(modalSlug)} onClick={deleting ? undefined : () => toggle(modalSlug)}
@@ -113,6 +116,7 @@ const DeleteDocument: React.FC<Props> = (props) => {
</Button> </Button>
<Button <Button
onClick={deleting ? undefined : handleDelete} onClick={deleting ? undefined : handleDelete}
id="confirm-delete"
> >
{deleting ? 'Deleting...' : 'Confirm'} {deleting ? 'Deleting...' : 'Confirm'}
</Button> </Button>

View File

@@ -3,5 +3,6 @@ import { SanitizedCollectionConfig } from '../../../../collections/config/types'
export type Props = { export type Props = {
collection?: SanitizedCollectionConfig, collection?: SanitizedCollectionConfig,
id?: string, id?: string,
buttonId?: string,
title?: string, title?: string,
} }

View File

@@ -27,6 +27,7 @@ const Duplicate: React.FC<Props> = ({ slug }) => {
return ( return (
<Button <Button
id="action-duplicate"
buttonStyle="none" buttonStyle="none"
className={baseClass} className={baseClass}
onClick={handleClick} onClick={handleClick}

View File

@@ -16,7 +16,10 @@ const Table: React.FC<Props> = ({ columns, data }) => {
<thead> <thead>
<tr> <tr>
{columns.map((col, i) => ( {columns.map((col, i) => (
<th key={i}> <th
key={i}
id={`heading-${col.accessor}`}
>
{col.components.Heading} {col.components.Heading}
</th> </th>
))} ))}

View File

@@ -105,6 +105,7 @@ const Condition: React.FC<Props> = (props) => {
<div className={`${baseClass}__actions`}> <div className={`${baseClass}__actions`}>
<Button <Button
icon="x" icon="x"
className={`${baseClass}__actions-remove`}
round round
buttonStyle="icon-label" buttonStyle="icon-label"
iconStyle="with-border" iconStyle="with-border"
@@ -116,6 +117,7 @@ const Condition: React.FC<Props> = (props) => {
/> />
<Button <Button
icon="plus" icon="plus"
className={`${baseClass}__actions-add`}
round round
buttonStyle="icon-label" buttonStyle="icon-label"
iconStyle="with-border" iconStyle="with-border"

View File

@@ -8,7 +8,7 @@ import './index.scss';
const baseClass = 'form-submit'; const baseClass = 'form-submit';
const FormSubmit: React.FC<Props> = (props) => { const FormSubmit: React.FC<Props> = (props) => {
const { children, disabled: disabledFromProps, type = 'submit' } = props; const { children, buttonId: id, disabled: disabledFromProps, type = 'submit' } = props;
const processing = useFormProcessing(); const processing = useFormProcessing();
const { disabled } = useForm(); const { disabled } = useForm();
@@ -16,6 +16,7 @@ const FormSubmit: React.FC<Props> = (props) => {
<div className={baseClass}> <div className={baseClass}>
<Button <Button
{...props} {...props}
id={id}
type={type} type={type}
disabled={disabledFromProps || processing || disabled ? true : undefined} disabled={disabledFromProps || processing || disabled ? true : undefined}
> >

View File

@@ -134,7 +134,14 @@ const DefaultEditView: React.FC<Props> = (props) => {
<ul className={`${baseClass}__collection-actions`}> <ul className={`${baseClass}__collection-actions`}>
{(permissions?.create?.permission) && ( {(permissions?.create?.permission) && (
<React.Fragment> <React.Fragment>
<li><Link to={`${admin}/collections/${slug}/create`}>Create New</Link></li> <li>
<Link
id="action-create"
to={`${admin}/collections/${slug}/create`}
>
Create New
</Link>
</li>
{!disableDuplicate && ( {!disableDuplicate && (
<li><DuplicateDocument slug={slug} /></li> <li><DuplicateDocument slug={slug} /></li>
)} )}
@@ -145,6 +152,7 @@ const DefaultEditView: React.FC<Props> = (props) => {
<DeleteDocument <DeleteDocument
collection={collection} collection={collection}
id={id} id={id}
buttonId="action-delete"
/> />
</li> </li>
)} )}
@@ -167,7 +175,7 @@ const DefaultEditView: React.FC<Props> = (props) => {
</React.Fragment> </React.Fragment>
)} )}
{!collection.versions?.drafts && ( {!collection.versions?.drafts && (
<FormSubmit>Save</FormSubmit> <FormSubmit buttonId="action-save">Save</FormSubmit>
)} )}
</React.Fragment> </React.Fragment>
)} )}

View File

@@ -1,4 +1,5 @@
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../../src/utilities/mapAsync';
import { devUser } from '../../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
export const slug = 'posts'; export const slug = 'posts';
@@ -26,6 +27,14 @@ export default buildConfig({
], ],
}], }],
onInit: async (payload) => { onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
});
await mapAsync([...Array(11)], async () => { await mapAsync([...Array(11)], async () => {
await payload.create({ await payload.create({
collection: slug, collection: slug,

View File

@@ -2,8 +2,8 @@ import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import payload from '../../../src'; import payload from '../../../src';
import { AdminUrlUtil } from '../../helpers/adminUrlUtil'; import { AdminUrlUtil } from '../../helpers/adminUrlUtil';
import { initPayloadTest } from '../../helpers/configHelpers'; import { initPayloadE2E } from '../../helpers/configHelpers';
import { firstRegister, saveDocAndAssert } from '../helpers'; import { login, saveDocAndAssert } from '../helpers';
import type { Post } from './config'; import type { Post } from './config';
import { slug } from './config'; import { slug } from './config';
import { mapAsync } from '../../../src/utilities/mapAsync'; import { mapAsync } from '../../../src/utilities/mapAsync';
@@ -20,19 +20,14 @@ describe('collections', () => {
let page: Page; let page: Page;
beforeAll(async ({ browser }) => { beforeAll(async ({ browser }) => {
const { serverURL } = await initPayloadTest({ const { serverURL } = await initPayloadE2E(__dirname);
__dirname,
init: {
local: false,
},
});
await clearDocs(); // Clear any seeded data from onInit await clearDocs(); // Clear any seeded data from onInit
url = new AdminUrlUtil(serverURL, slug); url = new AdminUrlUtil(serverURL, slug);
const context = await browser.newContext(); const context = await browser.newContext();
page = await context.newPage(); page = await context.newPage();
await firstRegister({ page, serverURL }); await login({ page, serverURL });
}); });
afterEach(async () => { afterEach(async () => {
@@ -50,13 +45,13 @@ describe('collections', () => {
test('should navigate to collection - card', async () => { test('should navigate to collection - card', async () => {
await page.goto(url.admin); await page.goto(url.admin);
await page.locator('a:has-text("Posts")').click(); await page.locator(`#card-${slug}`).click();
expect(page.url()).toContain(url.list); expect(page.url()).toContain(url.list);
}); });
test('breadcrumbs - from card to dashboard', async () => { test('breadcrumbs - from list to dashboard', async () => {
await page.goto(url.list); await page.goto(url.list);
await page.locator('a:has-text("Dashboard")').click(); await page.locator('.step-nav a[href="/admin"]').click();
expect(page.url()).toContain(url.admin); expect(page.url()).toContain(url.admin);
}); });
@@ -64,7 +59,7 @@ describe('collections', () => {
const { id } = await createPost(); const { id } = await createPost();
await page.goto(url.edit(id)); await page.goto(url.edit(id));
await page.locator('nav >> text=Posts').click(); await page.locator(`.step-nav >> text=${slug}`).click();
expect(page.url()).toContain(url.list); expect(page.url()).toContain(url.list);
}); });
}); });
@@ -73,14 +68,14 @@ describe('collections', () => {
describe('CRUD', () => { describe('CRUD', () => {
test('should create', async () => { test('should create', async () => {
await page.goto(url.create); await page.goto(url.create);
await page.locator('#title').fill(title); await page.locator('#field-title').fill(title);
await page.locator('#description').fill(description); await page.locator('#field-description').fill(description);
await page.click('text=Save', { delay: 100 }); await page.click('#action-save', { delay: 100 });
await saveDocAndAssert(page); await saveDocAndAssert(page);
await expect(page.locator('#title')).toHaveValue(title); await expect(page.locator('#field-title')).toHaveValue(title);
await expect(page.locator('#description')).toHaveValue(description); await expect(page.locator('#field-description')).toHaveValue(description);
}); });
test('should read existing', async () => { test('should read existing', async () => {
@@ -88,8 +83,8 @@ describe('collections', () => {
await page.goto(url.edit(id)); await page.goto(url.edit(id));
await expect(page.locator('#title')).toHaveValue(title); await expect(page.locator('#field-title')).toHaveValue(title);
await expect(page.locator('#description')).toHaveValue(description); await expect(page.locator('#field-description')).toHaveValue(description);
}); });
test('should update existing', async () => { test('should update existing', async () => {
@@ -99,21 +94,21 @@ describe('collections', () => {
const newTitle = 'new title'; const newTitle = 'new title';
const newDesc = 'new description'; const newDesc = 'new description';
await page.locator('#title').fill(newTitle); await page.locator('#field-title').fill(newTitle);
await page.locator('#description').fill(newDesc); await page.locator('#field-description').fill(newDesc);
await saveDocAndAssert(page); await saveDocAndAssert(page);
await expect(page.locator('#title')).toHaveValue(newTitle); await expect(page.locator('#field-title')).toHaveValue(newTitle);
await expect(page.locator('#description')).toHaveValue(newDesc); await expect(page.locator('#field-description')).toHaveValue(newDesc);
}); });
test('should delete existing', async () => { test('should delete existing', async () => {
const { id } = await createPost(); const { id } = await createPost();
await page.goto(url.edit(id)); await page.goto(url.edit(id));
await page.locator('button:has-text("Delete")').click(); await page.locator('#action-delete').click();
await page.locator('button:has-text("Confirm")').click(); await page.locator('#confirm-delete').click();
await expect(page.locator(`text=Post "${id}" successfully deleted.`)).toBeVisible(); await expect(page.locator(`text=Post "${id}" successfully deleted.`)).toBeVisible();
expect(page.url()).toContain(url.list); expect(page.url()).toContain(url.list);
@@ -123,10 +118,10 @@ describe('collections', () => {
const { id } = await createPost(); const { id } = await createPost();
await page.goto(url.edit(id)); await page.goto(url.edit(id));
await page.locator('button:has-text("Duplicate")').click(); await page.locator('#action-duplicate').click();
expect(page.url()).toContain(url.create); expect(page.url()).toContain(url.create);
await page.locator('button:has-text("Save")').click(); await page.locator('#action-save').click();
expect(page.url()).not.toContain(id); // new id expect(page.url()).not.toContain(id); // new id
}); });
}); });
@@ -149,7 +144,7 @@ describe('collections', () => {
test('toggle columns', async () => { test('toggle columns', async () => {
const columnCountLocator = 'table >> thead >> tr >> th'; const columnCountLocator = 'table >> thead >> tr >> th';
await createPost(); await createPost();
await page.locator('button:has-text("Columns")').click(); await page.locator('.list-controls__toggle-columns').click();
await wait(1000); // Wait for column toggle UI, should probably use waitForSelector await wait(1000); // Wait for column toggle UI, should probably use waitForSelector
const numberOfColumns = await page.locator(columnCountLocator).count(); const numberOfColumns = await page.locator(columnCountLocator).count();
@@ -170,13 +165,13 @@ describe('collections', () => {
await expect(page.locator(tableRowLocator)).toHaveCount(2); await expect(page.locator(tableRowLocator)).toHaveCount(2);
await page.locator('button:has-text("Filters")').click(); await page.locator('.list-controls__toggle-where').click();
await wait(1000); // Wait for column toggle UI, should probably use waitForSelector await wait(1000); // Wait for column toggle UI, should probably use waitForSelector
await page.locator('text=Add filter').click(); await page.locator('.where-builder__add-first-filter').click();
const operatorField = page.locator('.condition >> .condition__operator'); const operatorField = page.locator('.condition__operator');
const valueField = page.locator('.condition >> .condition__value >> input'); const valueField = page.locator('.condition__value >> input');
await operatorField.click(); await operatorField.click();
@@ -192,7 +187,7 @@ describe('collections', () => {
expect(firstId).toEqual(id); expect(firstId).toEqual(id);
// Remove filter // Remove filter
await page.locator('.condition >> .icon--x').click(); await page.locator('.condition__actions-remove').click();
await wait(1000); await wait(1000);
await expect(page.locator(tableRowLocator)).toHaveCount(2); await expect(page.locator(tableRowLocator)).toHaveCount(2);
}); });
@@ -237,14 +232,11 @@ describe('collections', () => {
await expect(getTableItems()).toHaveCount(2); await expect(getTableItems()).toHaveCount(2);
const chevrons = page.locator('table >> thead >> th >> button'); const upChevron = page.locator('#heading-id .sort-column__asc');
const upChevron = chevrons.first(); const downChevron = page.locator('#heading-id .sort-column__desc');
const downChevron = chevrons.nth(1);
const getFirstId = async () => getTableItems().first().locator('td').first() const getFirstId = async () => page.locator('.row-1 .cell-id').innerText();
.innerText(); const getSecondId = async () => page.locator('.row-2 .cell-id').innerText();
const getSecondId = async () => getTableItems().nth(1).locator('td').first()
.innerText();
const firstId = await getFirstId(); const firstId = await getFirstId();
const secondId = await getSecondId(); const secondId = await getSecondId();

View File

@@ -37,7 +37,7 @@ export async function login(args: LoginArgs): Promise<void> {
} }
export async function saveDocAndAssert(page: Page): Promise<void> { export async function saveDocAndAssert(page: Page): Promise<void> {
await page.click('text=Save', { delay: 100 }); await page.click('#action-save', { delay: 100 });
await expect(page.locator('.Toastify')).toContainText('successfully'); await expect(page.locator('.Toastify')).toContainText('successfully');
expect(page.url()).not.toContain('create'); expect(page.url()).not.toContain('create');
} }

View File

@@ -5,7 +5,6 @@ import express from 'express';
import type { CollectionConfig } from '../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import type { InitOptions } from '../../src/config/types'; import type { InitOptions } from '../../src/config/types';
import payload from '../../src'; import payload from '../../src';
import { devUser } from '../credentials';
type Options = { type Options = {
__dirname: string __dirname: string