test: array fields
This commit is contained in:
@@ -203,7 +203,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={classes}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
|
||||
@@ -211,7 +211,7 @@ const Blocks: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={classes}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
|
||||
@@ -70,7 +70,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
type="checkbox"
|
||||
name={path}
|
||||
checked={Boolean(value)}
|
||||
|
||||
@@ -83,7 +83,7 @@ const Code: React.FC<Props> = (props) => {
|
||||
required={required}
|
||||
/>
|
||||
<Editor
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
value={value as string || ''}
|
||||
onValueChange={readOnly ? () => null : setValue}
|
||||
highlight={highlighter}
|
||||
|
||||
@@ -77,8 +77,8 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
required={required}
|
||||
/>
|
||||
<div
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={`${baseClass}__input-wrapper`}
|
||||
id={`field-${path}`}
|
||||
>
|
||||
<DatePicker
|
||||
{...date}
|
||||
|
||||
@@ -69,12 +69,12 @@ const Email: React.FC<Props> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={path}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
value={value as string || ''}
|
||||
onChange={setValue}
|
||||
disabled={Boolean(readOnly)}
|
||||
|
||||
@@ -38,7 +38,7 @@ const Group: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={[
|
||||
'field-type',
|
||||
baseClass,
|
||||
|
||||
@@ -25,7 +25,7 @@ const HiddenInput: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<input
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
type="hidden"
|
||||
value={value as string || ''}
|
||||
onChange={setValue}
|
||||
|
||||
@@ -79,12 +79,12 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
value={typeof value === 'number' ? value : ''}
|
||||
onChange={handleChange}
|
||||
disabled={readOnly}
|
||||
|
||||
@@ -60,12 +60,12 @@ const Password: React.FC<Props> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
value={value as string || ''}
|
||||
onChange={setValue}
|
||||
disabled={formProcessing}
|
||||
|
||||
@@ -81,12 +81,12 @@ const PointField: React.FC<Props> = (props) => {
|
||||
<ul className={`${baseClass}__wrap`}>
|
||||
<li>
|
||||
<Label
|
||||
htmlFor={`field-longitude-${path}`}
|
||||
htmlFor={`field-longitude-${path.replace(/\./gi, '__')}`}
|
||||
label={`${label} - Longitude`}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
id={`field-longitude-${path}`}
|
||||
id={`field-longitude-${path.replace(/\./gi, '__')}`}
|
||||
value={(value && typeof value[0] === 'number') ? value[0] : ''}
|
||||
onChange={(e) => handleChange(e, 0)}
|
||||
disabled={readOnly}
|
||||
@@ -98,12 +98,12 @@ const PointField: React.FC<Props> = (props) => {
|
||||
</li>
|
||||
<li>
|
||||
<Label
|
||||
htmlFor={`field-latitude-${path}`}
|
||||
htmlFor={`field-latitude-${path.replace(/\./gi, '__')}`}
|
||||
label={`${label} - Latitude`}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
id={`field-latitude-${path}`}
|
||||
id={`field-latitude-${path.replace(/\./gi, '__')}`}
|
||||
value={(value && typeof value[1] === 'number') ? value[1] : ''}
|
||||
onChange={(e) => handleChange(e, 1)}
|
||||
disabled={readOnly}
|
||||
|
||||
@@ -78,7 +78,7 @@ const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
||||
required={required}
|
||||
/>
|
||||
<ul
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={`${baseClass}--group`}
|
||||
>
|
||||
{options.map((option) => {
|
||||
|
||||
@@ -335,7 +335,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
|
||||
@@ -211,7 +211,7 @@ const RichText: React.FC<Props> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
@@ -272,7 +272,7 @@ const RichText: React.FC<Props> = (props) => {
|
||||
ref={editorRef}
|
||||
>
|
||||
<Editable
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={`${baseClass}__input`}
|
||||
renderElement={renderElement}
|
||||
renderLeaf={renderLeaf}
|
||||
|
||||
@@ -64,7 +64,7 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
className={classes}
|
||||
style={{
|
||||
...style,
|
||||
@@ -76,7 +76,7 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={path}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
|
||||
@@ -61,12 +61,12 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
value={value || ''}
|
||||
onChange={onChange}
|
||||
disabled={readOnly}
|
||||
|
||||
@@ -62,12 +62,12 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`field-${path}`}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
<textarea
|
||||
id={`field-${path}`}
|
||||
id={`field-${path.replace(/\./gi, '__')}`}
|
||||
value={value || ''}
|
||||
onChange={onChange}
|
||||
disabled={readOnly}
|
||||
|
||||
@@ -111,7 +111,7 @@ const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
message={errorMessage}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={path}
|
||||
htmlFor={`field-${path.replace(/\./gi, '__')}`}
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
|
||||
export const slug = 'fields-array';
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
{
|
||||
slug,
|
||||
fields: [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'readOnlyArray',
|
||||
label: 'readOnly Array',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
defaultValue: [
|
||||
{
|
||||
text: 'defaultValue',
|
||||
},
|
||||
{
|
||||
text: 'defaultValue2',
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import wait from '../../src/utilities/wait';
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
|
||||
import { initPayloadTest } from '../helpers/configHelpers';
|
||||
import { firstRegister } from '../helpers';
|
||||
import { slug } from './config';
|
||||
|
||||
const { beforeAll, describe } = test;
|
||||
|
||||
let url: AdminUrlUtil;
|
||||
|
||||
describe('fields - array', () => {
|
||||
let page: Page;
|
||||
beforeAll(async ({ browser }) => {
|
||||
const { serverURL } = await initPayloadTest({
|
||||
__dirname,
|
||||
init: {
|
||||
local: false,
|
||||
},
|
||||
});
|
||||
url = new AdminUrlUtil(serverURL, slug);
|
||||
|
||||
const context = await browser.newContext();
|
||||
page = await context.newPage();
|
||||
|
||||
await firstRegister({ page, serverURL });
|
||||
});
|
||||
|
||||
test('should be readOnly', async () => {
|
||||
await page.goto(url.create);
|
||||
await wait(2000);
|
||||
const field = page.locator('#readOnlyArray\\.0\\.text');
|
||||
await expect(field).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should have defaultValue', async () => {
|
||||
await page.goto(url.create);
|
||||
await wait(2000);
|
||||
const field = page.locator('#readOnlyArray\\.0\\.text');
|
||||
await expect(field).toHaveValue('defaultValue');
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload CMS.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "fields-array".
|
||||
*/
|
||||
export interface FieldsArray {
|
||||
id: string;
|
||||
readOnlyArray?: {
|
||||
text?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
email?: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -37,6 +37,27 @@ const ArrayFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'readOnly',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
defaultValue: [
|
||||
{
|
||||
text: 'defaultValue',
|
||||
},
|
||||
{
|
||||
text: 'defaultValue2',
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,16 @@ const GroupFields: CollectionConfig = {
|
||||
name: 'textWithinGroup',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'arrayWithinGroup',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'textWithinArray',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -55,6 +65,9 @@ export const groupDoc = {
|
||||
text: 'some text within a group',
|
||||
subGroup: {
|
||||
textWithinGroup: 'please',
|
||||
arrayWithinGroup: [{
|
||||
textWithinArray: 'text in a group and array',
|
||||
}],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { CollectionConfig } from '../../../../src/collections/config/types';
|
||||
|
||||
export const pointFieldsSlug = 'point-fields';
|
||||
|
||||
const PointFields: CollectionConfig = {
|
||||
slug: 'point-fields',
|
||||
slug: pointFieldsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'point',
|
||||
},
|
||||
|
||||
@@ -4,16 +4,18 @@ 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';
|
||||
|
||||
const { beforeAll, describe } = test;
|
||||
|
||||
let page: Page;
|
||||
let url: AdminUrlUtil;
|
||||
let serverURL;
|
||||
|
||||
describe('fields', () => {
|
||||
beforeAll(async ({ browser }) => {
|
||||
const { serverURL } = await initPayloadE2E(__dirname);
|
||||
url = new AdminUrlUtil(serverURL, 'text-fields');
|
||||
const config = await initPayloadE2E(__dirname);
|
||||
serverURL = config.serverURL;
|
||||
|
||||
const context = await browser.newContext();
|
||||
page = await context.newPage();
|
||||
@@ -23,15 +25,22 @@ describe('fields', () => {
|
||||
|
||||
describe('text', () => {
|
||||
test('should display field in list view', async () => {
|
||||
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'text-fields');
|
||||
await page.goto(url.list);
|
||||
const textCell = page.locator('table tr:first-child td:first-child a');
|
||||
await expect(textCell).toHaveText(textDoc.text);
|
||||
await expect(textCell)
|
||||
.toHaveText(textDoc.text);
|
||||
});
|
||||
});
|
||||
|
||||
describe('point', () => {
|
||||
let url: AdminUrlUtil;
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, pointFieldsSlug);
|
||||
});
|
||||
|
||||
test('should save point', async () => {
|
||||
await page.goto(url.create, 'point-fields');
|
||||
await page.goto(url.create);
|
||||
const longField = page.locator('#field-longitude-point');
|
||||
await longField.fill('9');
|
||||
|
||||
@@ -44,13 +53,34 @@ describe('fields', () => {
|
||||
const localizedLatField = page.locator('#field-latitude-localized');
|
||||
await localizedLatField.fill('-1');
|
||||
|
||||
const groupLongitude = page.locator('#field-longitude-group.point');
|
||||
const groupLongitude = page.locator('#field-longitude-group__point');
|
||||
await groupLongitude.fill('3');
|
||||
|
||||
const groupLatField = page.locator('#field-latitude-group.point');
|
||||
const groupLatField = page.locator('#field-latitude-group__point');
|
||||
await groupLatField.fill('-8');
|
||||
|
||||
await saveDocAndAssert(page);
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import payload from '../../src';
|
||||
import { pointDoc } from './collections/Point';
|
||||
import type { ArrayField, GroupField } from './payload-types';
|
||||
import { arrayFieldsSlug, arrayDefaultValue } from './collections/Array';
|
||||
import { groupFieldsSlug, groupDefaultChild, groupDefaultValue } from './collections/Group';
|
||||
import { groupFieldsSlug, groupDefaultChild, groupDefaultValue, groupDoc } from './collections/Group';
|
||||
import { defaultText } from './collections/Text';
|
||||
|
||||
let client;
|
||||
@@ -80,6 +80,7 @@ describe('Fields', () => {
|
||||
});
|
||||
describe('array', () => {
|
||||
let doc;
|
||||
let arrayInGroupDoc;
|
||||
const collection = arrayFieldsSlug;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -89,6 +90,14 @@ describe('Fields', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should create with ids and nested ids', async () => {
|
||||
const docWithIDs = await payload.create<GroupField>({
|
||||
collection: groupFieldsSlug,
|
||||
data: groupDoc,
|
||||
});
|
||||
expect(docWithIDs.group.subGroup.arrayWithinGroup[0].id).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create with defaultValue', async () => {
|
||||
expect(doc.items).toMatchObject(arrayDefaultValue);
|
||||
expect(doc.localized).toMatchObject(arrayDefaultValue);
|
||||
@@ -137,18 +146,18 @@ describe('Fields', () => {
|
||||
});
|
||||
|
||||
describe('group', () => {
|
||||
let groupDoc;
|
||||
let document;
|
||||
|
||||
beforeAll(async () => {
|
||||
groupDoc = await payload.create<GroupField>({
|
||||
document = await payload.create<GroupField>({
|
||||
collection: groupFieldsSlug,
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create with defaultValue', async () => {
|
||||
expect(groupDoc.group.defaultParent).toStrictEqual(groupDefaultValue);
|
||||
expect(groupDoc.group.defaultChild).toStrictEqual(groupDefaultChild);
|
||||
expect(document.group.defaultParent).toStrictEqual(groupDefaultValue);
|
||||
expect(document.group.defaultChild).toStrictEqual(groupDefaultChild);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,10 @@ export interface ArrayField {
|
||||
text: string;
|
||||
id?: string;
|
||||
}[];
|
||||
readOnly?: {
|
||||
text?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -105,6 +109,10 @@ export interface GroupField {
|
||||
defaultChild?: string;
|
||||
subGroup?: {
|
||||
textWithinGroup?: string;
|
||||
arrayWithinGroup?: {
|
||||
textWithinArray?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
createdAt: string;
|
||||
|
||||
Reference in New Issue
Block a user