Files
payload/test/fields/e2e.spec.ts
Dan Ribbens bab34d82f5 feat: add i18n to admin panel (#1326)
Co-authored-by: shikhantmaungs <shinkhantmaungs@gmail.com>
Co-authored-by: Thomas Ghysels <info@thomasg.be>
Co-authored-by: Kokutse Djoguenou <kokutse@Kokutses-MacBook-Pro.local>
Co-authored-by: Christian Gil <47041342+ChrisGV04@users.noreply.github.com>
Co-authored-by: Łukasz Rabiec <lukaszrabiec@gmail.com>
Co-authored-by: Jenny <jennifer.eberlei@gmail.com>
Co-authored-by: Hung Vu <hunghvu2017@gmail.com>
Co-authored-by: Shin Khant Maung <101539335+shinkhantmaungs@users.noreply.github.com>
Co-authored-by: Carlo Brualdi <carlo.brualdi@gmail.com>
Co-authored-by: Ariel Tonglet <ariel.tonglet@gmail.com>
Co-authored-by: Roman Ryzhikov <general+github@ya.ru>
Co-authored-by: maekoya <maekoya@stromatolite.jp>
Co-authored-by: Emilia Trollros <3m1l1a@emiliatrollros.se>
Co-authored-by: Kokutse J Djoguenou <90865585+Julesdj@users.noreply.github.com>
Co-authored-by: Mitch Dries <mitch.dries@gmail.com>

BREAKING CHANGE: If you assigned labels to collections, globals or block names, you need to update your config! Your GraphQL schema and generated Typescript interfaces may have changed. Payload no longer uses labels for code based naming. To prevent breaking changes to your GraphQL API and typescript types in your project, you can assign the below properties to match what Payload previously generated for you from labels.

On Collections
Use `graphQL.singularName`, `graphQL.pluralName` for GraphQL schema names.
Use `typescript.interface` for typescript generation name.

On Globals
Use `graphQL.name` for GraphQL Schema name.
Use `typescript.interface` for typescript generation name.

On Blocks (within Block fields)
Use `graphQL.singularName` for graphQL schema names.
2022-11-18 07:36:30 -05:00

421 lines
16 KiB
TypeScript

import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
import { initPayloadE2E } from '../helpers/configHelpers';
import { login, saveDocAndAssert } from '../helpers';
import { textDoc } from './collections/Text';
import { arrayFieldsSlug } from './collections/Array';
import { pointFieldsSlug } from './collections/Point';
import { tabsSlug } from './collections/Tabs';
import { collapsibleFieldsSlug } from './collections/Collapsible';
import wait from '../../src/utilities/wait';
import { relationshipFieldsSlug } from './collections/Relationship';
const { beforeAll, describe } = test;
let page: Page;
let serverURL;
describe('fields', () => {
beforeAll(async ({ browser }) => {
const config = await initPayloadE2E(__dirname);
serverURL = config.serverURL;
const context = await browser.newContext();
page = await context.newPage();
await login({ page, serverURL });
});
describe('text', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, 'text-fields');
});
test('should display field in list view', async () => {
await page.goto(url.list);
const textCell = page.locator('.row-1 .cell-text');
await expect(textCell)
.toHaveText(textDoc.text);
});
test('should display i18n label in cells when missing field data', async () => {
await page.goto(url.list);
const textCell = page.locator('.row-1 .cell-i18nText');
await expect(textCell)
.toHaveText('<No Text en>');
});
test('should show i18n label', async () => {
await page.goto(url.create);
await expect(page.locator('label[for="field-i18nText"]')).toHaveText('Text en');
});
test('should show i18n placeholder', async () => {
await page.goto(url.create);
await expect(await page.locator('#field-i18nText')).toHaveAttribute('placeholder', 'en placeholder');
});
test('should show i18n descriptions', async () => {
await page.goto(url.create);
const description = page.locator('.field-description-i18nText');
await expect(description).toHaveText('en description');
});
});
describe('radio', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, 'radio-fields');
});
test('should show i18n label in list', async () => {
await page.goto(url.list);
await expect(page.locator('.cell-radio')).toHaveText('Value One');
});
test('should show i18n label while editing', async () => {
await page.goto(url.create);
await expect(page.locator('label[for="field-radio"]')).toHaveText('Radio en');
});
test('should show i18n radio labels', async () => {
await page.goto(url.create);
await expect(await page.locator('label[for="field-radio-one"] .radio-input__label'))
.toHaveText('Value One');
});
});
describe('point', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, pointFieldsSlug);
});
test('should save point', async () => {
await page.goto(url.create);
const longField = page.locator('#field-longitude-point');
await longField.fill('9');
const latField = page.locator('#field-latitude-point');
await latField.fill('-2');
const localizedLongField = page.locator('#field-longitude-localized');
await localizedLongField.fill('1');
const localizedLatField = page.locator('#field-latitude-localized');
await localizedLatField.fill('-1');
const groupLongitude = page.locator('#field-longitude-group__point');
await groupLongitude.fill('3');
const groupLatField = page.locator('#field-latitude-group__point');
await groupLatField.fill('-8');
await saveDocAndAssert(page);
});
});
describe('fields - collapsible', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, collapsibleFieldsSlug);
});
test('should render CollapsibleLabel using a function', async () => {
const label = 'custom row label';
await page.goto(url.create);
await page.locator('#field-collapsible-3__1 >> #field-nestedTitle').fill(label);
await wait(100);
const customCollapsibleLabel = await page.locator('#field-collapsible-3__1 >> .row-label');
await expect(customCollapsibleLabel).toContainText(label);
});
test('should render CollapsibleLabel using a component', async () => {
const label = 'custom row label as component';
await page.goto(url.create);
await page.locator('#field-arrayWithCollapsibles >> .array-field__add-button-wrap >> button').click();
await page.locator('#field-collapsible-4__0-arrayWithCollapsibles__0 >> #field-arrayWithCollapsibles__0__innerCollapsible').fill(label);
await wait(100);
const customCollapsibleLabel = await page.locator(`#field-collapsible-4__0-arrayWithCollapsibles__0 >> .row-label :text("${label}")`);
await expect(customCollapsibleLabel).toHaveCSS('text-transform', 'uppercase');
});
});
describe('blocks', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, 'block-fields');
});
test('should use i18n block labels', async () => {
await page.goto(url.create);
await expect(page.locator('#field-i18nBlocks .blocks-field__header')).toContainText('Block en');
const addButton = page.locator('#field-i18nBlocks .btn__label');
await expect(addButton).toContainText('Add Block en');
await addButton.click();
const blockSelector = page.locator('#field-i18nBlocks .block-selector .block-selection').first();
await expect(blockSelector).toContainText('Text en');
await blockSelector.click();
await expect(page.locator('#i18nBlocks-row-0 .blocks-field__block-pill-text')).toContainText('Text en');
});
});
describe('fields - array', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, arrayFieldsSlug);
});
test('should be readOnly', async () => {
await page.goto(url.create);
const field = page.locator('#field-readOnly__0__text');
await expect(field)
.toBeDisabled();
});
test('should have defaultValue', async () => {
await page.goto(url.create);
const field = page.locator('#field-readOnly__0__text');
await expect(field)
.toHaveValue('defaultValue');
});
test('should render RowLabel using a function', async () => {
const label = 'custom row label as function';
await page.goto(url.create);
await page.locator('#field-rowLabelAsFunction >> .array-field__add-button-wrap >> button').click();
await page.locator('#field-rowLabelAsFunction__0__title').fill(label);
await wait(100);
const customRowLabel = await page.locator('#rowLabelAsFunction-row-0 >> .row-label');
await expect(customRowLabel).toContainText(label);
});
test('should render RowLabel using a component', async () => {
const label = 'custom row label as component';
await page.goto(url.create);
await page.locator('#field-rowLabelAsComponent >> .array-field__add-button-wrap >> button').click();
await page.locator('#field-rowLabelAsComponent__0__title').fill(label);
await wait(100);
const customRowLabel = await page.locator('#rowLabelAsComponent-row-0 >> .row-label :text("custom row label")');
await expect(customRowLabel).toHaveCSS('text-transform', 'uppercase');
});
});
describe('fields - tabs', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, tabsSlug);
});
test('should fill and retain a new value within a tab while switching tabs', async () => {
const textInRowValue = 'hello';
const numberInRowValue = '23';
await page.goto(url.create);
await page.locator('.tabs-field__tab-button:has-text("Tab with Row")').click();
await page.locator('#field-textInRow').fill(textInRowValue);
await page.locator('#field-numberInRow').fill(numberInRowValue);
await wait(300);
await page.locator('.tabs-field__tab-button:has-text("Tab with Array")').click();
await page.locator('.tabs-field__tab-button:has-text("Tab with Row")').click();
await wait(100);
await expect(page.locator('#field-textInRow')).toHaveValue(textInRowValue);
await expect(page.locator('#field-numberInRow')).toHaveValue(numberInRowValue);
});
test('should retain updated values within tabs while switching between tabs', async () => {
const textInRowValue = 'new value';
await page.goto(url.list);
await page.locator('.cell-id a').click();
// Go to Row tab, update the value
await page.locator('.tabs-field__tab-button:has-text("Tab with Row")').click();
await page.locator('#field-textInRow').fill(textInRowValue);
await wait(250);
// Go to Array tab, then back to Row. Make sure new value is still there
await page.locator('.tabs-field__tab-button:has-text("Tab with Array")').click();
await page.locator('.tabs-field__tab-button:has-text("Tab with Row")').click();
await expect(page.locator('#field-textInRow')).toHaveValue(textInRowValue);
// Go to array tab, save the doc
await page.locator('.tabs-field__tab-button:has-text("Tab with Array")').click();
await page.click('#action-save', { delay: 100 });
await wait(250);
// Go back to row tab, make sure the new value is still present
await page.locator('.tabs-field__tab-button:has-text("Tab with Row")').click();
await expect(page.locator('#field-textInRow')).toHaveValue(textInRowValue);
});
});
describe('fields - richText', () => {
async function navigateToRichTextFields() {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields');
await page.goto(url.list);
await page.locator('.row-1 .cell-id').click();
}
describe('toolbar', () => {
test('should create new url link', async () => {
await navigateToRichTextFields();
// Open link popup
await page.locator('.rich-text__toolbar button:not([disabled]) .link').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Fill values and click Confirm
await editLinkModal.locator('#field-text').fill('link text');
await editLinkModal.locator('label[for="field-linkType-custom"]').click();
await editLinkModal.locator('#field-url').fill('https://payloadcms.com');
await wait(200);
await editLinkModal.locator('button[type="submit"]').click();
// Remove link from editor body
await page.locator('span >> text="link text"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup.locator('.rich-text-link__link-label')).toBeVisible();
await popup.locator('.rich-text-link__link-close').click();
await expect(page.locator('span >> text="link text"')).toHaveCount(0);
});
test('should not create new url link when read only', async () => {
await navigateToRichTextFields();
// Attempt to open link popup
const modalTrigger = page.locator('.rich-text--read-only .rich-text__toolbar button .link');
await expect(modalTrigger).toBeDisabled();
});
});
describe('editor', () => {
test('should populate url link', async () => {
navigateToRichTextFields();
// Open link popup
await page.locator('#field-richText span >> text="render links"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup).toBeVisible();
await expect(popup.locator('a')).toHaveAttribute('href', 'https://payloadcms.com');
// Open link edit modal
await popup.locator('.rich-text-link__link-edit').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Close link edit modal
await editLinkModal.locator('button[type="submit"]').click();
await expect(editLinkModal).not.toBeVisible();
});
test('should populate relationship link', async () => {
navigateToRichTextFields();
// Open link popup
await page.locator('#field-richText span >> text="link to relationships"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup).toBeVisible();
await expect(popup.locator('a')).toHaveAttribute('href', /\/admin\/collections\/array-fields\/.*/);
// Open link edit modal
await popup.locator('.rich-text-link__link-edit').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Close link edit modal
await editLinkModal.locator('button[type="submit"]').click();
await expect(editLinkModal).not.toBeVisible();
});
});
describe('relationship', () => {
let url: AdminUrlUtil;
beforeAll(() => {
url = new AdminUrlUtil(serverURL, relationshipFieldsSlug);
});
test('should create inline relationship within field with many relations', async () => {
await page.goto(url.create);
const button = page.locator('#relationship-add-new .relationship-add-new__add-button');
await button.click();
await page.locator('.relationship-add-new__relation-button--text-fields').click();
const textField = page.locator('#field-text');
const textValue = 'hello';
await textField.fill(textValue);
await page.locator('#relationship-add-modal-depth-1 #action-save').click();
await expect(page.locator('.Toastify')).toContainText('successfully');
await expect(page.locator('#field-relationship .rs__single-value')).toContainText(textValue);
await page.locator('#action-save').click();
await expect(page.locator('.Toastify')).toContainText('successfully');
});
test('should create nested inline relationships', async () => {
await page.goto(url.create);
// Open first modal
await page.locator('#relationToSelf-add-new .relationship-add-new__add-button').click();
// Fill first modal's required relationship field
await page.locator('#relationToSelf-add-modal-depth-1 #field-relationship').click();
await page.locator('#relationToSelf-add-modal-depth-1 .rs__option:has-text("Seeded text document")').click();
// Open second modal
await page.locator('#relationToSelf-add-modal-depth-1 #relationToSelf-add-new button').click();
// Fill second modal's required relationship field
await page.locator('#relationToSelf-add-modal-depth-2 #field-relationship').click();
await page.locator('#relationToSelf-add-modal-depth-2 .rs__option:has-text("Seeded text document")').click();
// Save second modal
await page.locator('#relationToSelf-add-modal-depth-2 #action-save').click();
// Assert that the first modal is still open
// and that the Relation to Self field now has a value in its input
await expect(page.locator('#relationToSelf-add-modal-depth-1 #field-relationToSelf .rs__single-value')).toBeVisible();
// Save first modal
await page.locator('#relationToSelf-add-modal-depth-1 #action-save').click();
await wait(200);
// Expect the original field to have a value filled
await expect(page.locator('#field-relationToSelf .rs__single-value')).toBeVisible();
// Fill the required field
await page.locator('#field-relationship').click();
await page.locator('.rs__option:has-text("Seeded text document")').click();
await page.locator('#action-save').click();
await expect(page.locator('.Toastify')).toContainText('successfully');
});
});
});
});