chore: merge master

This commit is contained in:
James
2022-09-11 18:07:05 -07:00
245 changed files with 4708 additions and 2347 deletions

View File

@@ -14,6 +14,10 @@ export const blocksField: Field = {
type: 'text',
required: true,
},
{
name: 'richText',
type: 'richText',
},
],
},
{
@@ -63,12 +67,55 @@ export const blocksField: Field = {
},
],
},
{
slug: 'tabs',
fields: [
{
type: 'tabs',
tabs: [
{
label: 'Tab with Collapsible',
fields: [
{
type: 'collapsible',
label: 'Collapsible within Block',
fields: [
{
// collapsible
name: 'textInCollapsible',
type: 'text',
},
],
},
{
type: 'row',
fields: [
{
// collapsible
name: 'textInRow',
type: 'text',
},
],
},
],
},
],
},
],
},
],
};
const BlockFields: CollectionConfig = {
slug: 'block-fields',
fields: [blocksField],
fields: [
blocksField,
{
...blocksField,
name: 'localizedBlocks',
localized: true,
},
],
};
export const blocksFieldSeedData = [
@@ -76,6 +123,7 @@ export const blocksFieldSeedData = [
blockName: 'First block',
blockType: 'text',
text: 'first block',
richText: [],
},
{
blockName: 'Second block',
@@ -102,6 +150,7 @@ export const blocksFieldSeedData = [
export const blocksDoc = {
blocks: blocksFieldSeedData,
localizedBlocks: blocksFieldSeedData,
};
export default BlockFields;

View File

@@ -0,0 +1,126 @@
import type { CollectionConfig } from '../../../../src/collections/config/types';
import { CodeField } from '../../payload-types';
const Code: CollectionConfig = {
slug: 'code-fields',
fields: [
{
name: 'javascript',
type: 'code',
admin: {
language: 'js',
},
},
{
name: 'typescript',
type: 'code',
admin: {
language: 'ts',
},
},
{
name: 'json',
type: 'code',
admin: {
language: 'json',
},
},
{
name: 'html',
type: 'code',
admin: {
language: 'html',
},
},
{
name: 'css',
type: 'code',
admin: {
language: 'css',
},
},
],
};
export const codeDoc: Partial<CodeField> = {
javascript: "console.log('Hello');",
typescript: `class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");`,
html: `<!DOCTYPE html>
<html lang="en">
<head>
<script>
// Just a lil script to show off that inline JS gets highlighted
window.console && console.log('foo');
</script>
<meta charset="utf-8" />
<link rel="icon" href="assets/favicon.png" />
<title>Prism</title>
<link rel="stylesheet" href="assets/style.css" />
<link rel="stylesheet" href="themes/prism.css" data-noprefix />
<script src="assets/vendor/prefixfree.min.js"></script>
<script>var _gaq = [['_setAccount', 'UA-11111111-1'], ['_trackPageview']];</script>
<script src="https://www.google-analytics.com/ga.js" async></script>
</head>
<body>`,
css: `@import url(https://fonts.googleapis.com/css?family=Questrial);
@import url(https://fonts.googleapis.com/css?family=Arvo);
@font-face {
src: url(https://lea.verou.me/logo.otf);
font-family: 'LeaVerou';
}
/*
Shared styles
*/
section h1,
#features li strong,
header h2,
footer p {
font: 100% Rockwell, Arvo, serif;
}
/*
Styles
*/
* {
margin: 0;
padding: 0;
}
body {
font: 100%/1.5 Questrial, sans-serif;
tab-size: 4;
hyphens: auto;
}
a {
color: inherit;
}
section h1 {
font-size: 250%;
}`,
json: JSON.stringify({ property: 'value', arr: ['val1', 'val2', 'val3'] }, null, 2),
};
export default Code;

View File

@@ -0,0 +1,63 @@
import type { CollectionConfig } from '../../../../src/collections/config/types';
export const defaultText = 'default-text';
const DateFields: CollectionConfig = {
slug: 'date-fields',
admin: {
useAsTitle: 'date',
},
fields: [
{
name: 'default',
type: 'date',
required: true,
},
{
name: 'timeOnly',
type: 'date',
admin: {
date: {
pickerAppearance: 'timeOnly',
},
},
},
{
name: 'dayOnly',
type: 'date',
admin: {
date: {
pickerAppearance: 'dayOnly',
},
},
},
{
name: 'dayAndTime',
type: 'date',
admin: {
date: {
pickerAppearance: 'dayAndTime',
},
},
},
{
name: 'monthOnly',
type: 'date',
admin: {
date: {
pickerAppearance: 'monthOnly',
},
},
},
],
};
export const dateDoc = {
default: '2022-08-12T10:00:00.000+00:00',
timeOnly: '2022-08-12T10:00:00.157+00:00',
dayOnly: '2022-08-11T22:00:00.000+00:00',
dayAndTime: '2022-08-12T10:00:00.052+00:00',
monthOnly: '2022-07-31T22:00:00.000+00:00',
};
export default DateFields;

View File

@@ -34,6 +34,24 @@ const IndexedFields: CollectionConfig = {
},
],
},
{
type: 'collapsible',
label: 'Collapsible',
fields: [
{
name: 'collapsibleLocalizedUnique',
type: 'text',
unique: true,
localized: true,
},
{
name: 'collapsibleTextUnique',
type: 'text',
label: 'collapsibleTextUnique',
unique: true,
},
],
},
],
};

View File

@@ -19,6 +19,7 @@ const PointFields: CollectionConfig = {
name: 'localized',
type: 'point',
label: 'Localized Point',
unique: true,
localized: true,
},
{
@@ -36,7 +37,7 @@ const PointFields: CollectionConfig = {
export const pointDoc = {
point: [7, -7],
localized: [5, -2],
localized: [15, -12],
group: { point: [1, 9] },
};

View File

@@ -43,6 +43,22 @@ const RichTextFields: CollectionConfig = {
type: 'richText',
required: true,
admin: {
link: {
fields: [
{
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: [
'noopener', 'noreferrer', 'nofollow',
],
admin: {
description: 'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
},
},
],
},
upload: {
collections: {
uploads: {
@@ -78,7 +94,7 @@ export const richTextDoc = {
},
{
type: 'link',
url: 'test.com',
url: 'https://payloadcms.com',
newTab: true,
children: [
{
@@ -87,7 +103,24 @@ export const richTextDoc = {
],
},
{
text: ' and store nested relationship fields:',
text: ', ',
},
{
type: 'link',
linkType: 'internal',
doc: {
value: '{{ARRAY_DOC_ID}}',
relationTo: 'array-fields',
},
fields: {},
children: [
{
text: 'link to relationships',
},
],
},
{
text: ', and store nested relationship fields:',
},
],
},

View File

@@ -30,6 +30,7 @@ const SelectFields: CollectionConfig = {
type: 'select',
admin: {
isClearable: true,
isSortable: true,
},
options: [
{

View File

@@ -13,6 +13,11 @@ const TextFields: CollectionConfig = {
type: 'text',
required: true,
},
{
name: 'localizedText',
type: 'text',
localized: true,
},
{
name: 'defaultFunction',
type: 'text',
@@ -32,6 +37,7 @@ const TextFields: CollectionConfig = {
export const textDoc = {
text: 'Seeded text document',
localizedText: 'Localized text',
};
export default TextFields;

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import path from 'path';
import fs from 'fs';
import { buildConfig } from '../buildConfig';
@@ -6,6 +7,7 @@ import ArrayFields, { arrayDoc } from './collections/Array';
import BlockFields, { blocksDoc } from './collections/Blocks';
import CollapsibleFields, { collapsibleDoc } from './collections/Collapsible';
import ConditionalLogic, { conditionalLogicDoc } from './collections/ConditionalLogic';
import DateFields, { dateDoc } from './collections/Date';
import RichTextFields, { richTextDoc } from './collections/RichText';
import SelectFields, { selectsDoc } from './collections/Select';
import TabsFields, { tabsDoc } from './collections/Tabs';
@@ -16,6 +18,7 @@ import getFileByPath from '../../src/uploads/getFileByPath';
import Uploads, { uploadsDoc } from './collections/Upload';
import IndexedFields from './collections/Indexed';
import NumberFields, { numberDoc } from './collections/Number';
import CodeFields, { codeDoc } from './collections/Code';
export default buildConfig({
admin: {
@@ -33,6 +36,7 @@ export default buildConfig({
collections: [
ArrayFields,
BlockFields,
CodeFields,
CollapsibleFields,
ConditionalLogic,
GroupFields,
@@ -44,6 +48,7 @@ export default buildConfig({
NumberFields,
Uploads,
IndexedFields,
DateFields,
],
localization: {
defaultLocale: 'en',
@@ -58,14 +63,15 @@ export default buildConfig({
},
});
await payload.create({ collection: 'array-fields', data: arrayDoc });
await payload.create({ collection: 'block-fields', data: blocksDoc });
const createdArrayDoc = await payload.create({ collection: 'array-fields', data: arrayDoc });
await payload.create({ collection: 'collapsible-fields', data: collapsibleDoc });
await payload.create({ collection: 'conditional-logic', data: conditionalLogicDoc });
await payload.create({ collection: 'group-fields', data: groupDoc });
await payload.create({ collection: 'select-fields', data: selectsDoc });
await payload.create({ collection: 'tabs-fields', data: tabsDoc });
await payload.create({ collection: 'point-fields', data: pointDoc });
await payload.create({ collection: 'date-fields', data: dateDoc });
await payload.create({ collection: 'code-fields', data: codeDoc });
const createdTextDoc = await payload.create({ collection: 'text-fields', data: textDoc });
@@ -78,7 +84,8 @@ export default buildConfig({
const createdUploadDoc = await payload.create({ collection: 'uploads', data: uploadsDoc, file });
const richTextDocWithRelationship = { ...richTextDoc };
const richTextDocWithRelId = JSON.parse(JSON.stringify(richTextDoc).replace('{{ARRAY_DOC_ID}}', createdArrayDoc.id));
const richTextDocWithRelationship = { ...richTextDocWithRelId };
const richTextRelationshipIndex = richTextDocWithRelationship.richText.findIndex(({ type }) => type === 'relationship');
richTextDocWithRelationship.richText[richTextRelationshipIndex].value = { id: createdTextDoc.id };
@@ -89,5 +96,14 @@ export default buildConfig({
await payload.create({ collection: 'rich-text-fields', data: richTextDocWithRelationship });
await payload.create({ collection: 'number-fields', data: numberDoc });
const blocksDocWithRichText = { ...blocksDoc };
// @ts-ignore
blocksDocWithRichText.blocks[0].richText = richTextDocWithRelationship.richText;
// @ts-ignore
blocksDocWithRichText.localizedBlocks[0].richText = richTextDocWithRelationship.richText;
await payload.create({ collection: 'block-fields', data: blocksDocWithRichText });
},
});

View File

@@ -139,4 +139,75 @@ describe('fields', () => {
await expect(page.locator('#field-textInRow')).toHaveValue(textInRowValue);
});
});
describe('fields - richText', () => {
test('should create new url link', async () => {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields');
await page.goto(url.list);
await page.locator('.row-1 .cell-id').click();
// Open link popup
await page.locator('.rich-text__toolbar .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
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 populate url link', async () => {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields');
await page.goto(url.list);
await page.locator('.row-1 .cell-id').click();
// Open link popup
await page.locator('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 () => {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields');
await page.goto(url.list);
await page.locator('.row-1 .cell-id').click();
// Open link popup
await page.locator('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();
// await page.locator('span >> text="render links"').click();
});
});
});

View File

@@ -120,6 +120,9 @@ describe('Fields', () => {
const options: Record<string, IndexOptions> = {};
beforeAll(() => {
// mongoose model schema indexes do not always create indexes in the actual database
// see: https://github.com/payloadcms/payload/issues/571
indexes = payload.collections['indexed-fields'].Model.schema.indexes() as [Record<string, IndexDirection>, IndexOptions];
indexes.forEach((index) => {
@@ -149,10 +152,19 @@ describe('Fields', () => {
expect(definitions['group.localizedUnique.es']).toEqual(1);
expect(options['group.localizedUnique.es']).toMatchObject({ unique: true, sparse: true });
});
it('should have unique indexes in a collapsible', () => {
expect(definitions['collapsibleLocalizedUnique.en']).toEqual(1);
expect(options['collapsibleLocalizedUnique.en']).toMatchObject({ unique: true, sparse: true });
expect(definitions.collapsibleTextUnique).toEqual(1);
expect(options.collapsibleTextUnique).toMatchObject({ unique: true });
});
});
describe('point', () => {
let doc;
const point = [7, -7];
const localized = [5, -2];
const group = { point: [1, 9] };
beforeAll(async () => {
const findDoc = await payload.find({
@@ -176,9 +188,6 @@ describe('Fields', () => {
});
it('should create', async () => {
const point = [7, -7];
const localized = [5, -2];
const group = { point: [1, 9] };
doc = await payload.create({
collection: 'point-fields',
data: {
@@ -192,6 +201,30 @@ describe('Fields', () => {
expect(doc.localized).toEqual(localized);
expect(doc.group).toMatchObject(group);
});
it('should not create duplicate point when unique', async () => {
await expect(() => payload.create({
collection: 'point-fields',
data: {
point,
localized,
group,
},
}))
.rejects
.toThrow(Error);
await expect(async () => payload.create({
collection: 'number-fields',
data: {
min: 5,
},
})).rejects.toThrow('The following field is invalid: min');
expect(doc.point).toEqual(point);
expect(doc.localized).toEqual(localized);
expect(doc.group).toMatchObject(group);
});
});
describe('array', () => {
let doc;
@@ -359,6 +392,78 @@ describe('Fields', () => {
expect(blockFields.docs[0].blocks[2].subBlocks[0].number).toEqual(blocksFieldSeedData[2].subBlocks[0].number);
expect(blockFields.docs[0].blocks[2].subBlocks[1].text).toEqual(blocksFieldSeedData[2].subBlocks[1].text);
});
it('should query based on richtext data within a block', async () => {
const blockFieldsSuccess = await payload.find({
collection: 'block-fields',
where: {
'blocks.richText.children.text': {
like: 'fun',
},
},
});
expect(blockFieldsSuccess.docs).toHaveLength(1);
const blockFieldsFail = await payload.find({
collection: 'block-fields',
where: {
'blocks.richText.children.text': {
like: 'funny',
},
},
});
expect(blockFieldsFail.docs).toHaveLength(0);
});
it('should query based on richtext data within a localized block, specifying locale', async () => {
const blockFieldsSuccess = await payload.find({
collection: 'block-fields',
where: {
'localizedBlocks.en.richText.children.text': {
like: 'fun',
},
},
});
expect(blockFieldsSuccess.docs).toHaveLength(1);
const blockFieldsFail = await payload.find({
collection: 'block-fields',
where: {
'localizedBlocks.en.richText.children.text': {
like: 'funny',
},
},
});
expect(blockFieldsFail.docs).toHaveLength(0);
});
it('should query based on richtext data within a localized block, without specifying locale', async () => {
const blockFieldsSuccess = await payload.find({
collection: 'block-fields',
where: {
'localizedBlocks.richText.children.text': {
like: 'fun',
},
},
});
expect(blockFieldsSuccess.docs).toHaveLength(1);
const blockFieldsFail = await payload.find({
collection: 'block-fields',
where: {
'localizedBlocks.richText.children.text': {
like: 'funny',
},
},
});
expect(blockFieldsFail.docs).toHaveLength(0);
});
});
describe('richText', () => {
@@ -385,5 +490,28 @@ describe('Fields', () => {
expect(workingRichTextQuery.docs).toHaveLength(1);
});
it('should populate link relationship', async () => {
const query = await payload.find({
collection: 'rich-text-fields',
where: {
'richText.children.linkType': {
equals: 'internal',
},
},
});
const nodes = query.docs[0].richText;
expect(nodes).toBeDefined();
const child = nodes.flatMap((n) => n.children)
.find((c) => c.doc);
expect(child).toMatchObject({
type: 'link',
linkType: 'internal',
});
expect(child.doc.relationTo).toEqual('array-fields');
expect(typeof child.doc.value.id).toBe('string');
expect(child.doc.value.items).toHaveLength(6);
});
});
});

View File

@@ -40,6 +40,9 @@ export interface BlockField {
blocks: (
| {
text: string;
richText?: {
[k: string]: unknown;
}[];
id?: string;
blockName?: string;
blockType: 'text';
@@ -70,6 +73,56 @@ export interface BlockField {
blockType: 'subBlocks';
}
)[];
localizedBlocks: (
| {
text: string;
richText?: {
[k: string]: unknown;
}[];
id?: string;
blockName?: string;
blockType: 'text';
}
| {
number: number;
id?: string;
blockName?: string;
blockType: 'number';
}
| {
subBlocks: (
| {
text: string;
id?: string;
blockName?: string;
blockType: 'text';
}
| {
number: number;
id?: string;
blockName?: string;
blockType: 'number';
}
)[];
id?: string;
blockName?: string;
blockType: 'subBlocks';
}
)[];
createdAt: string;
updatedAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "code-fields".
*/
export interface CodeField {
id: string;
javascript?: string;
typescript?: string;
json?: string;
html?: string;
css?: string;
createdAt: string;
updatedAt: string;
}
@@ -188,6 +241,9 @@ export interface TabsField {
blocks: (
| {
text: string;
richText?: {
[k: string]: unknown;
}[];
id?: string;
blockName?: string;
blockType: 'text';
@@ -235,6 +291,7 @@ export interface TabsField {
export interface TextField {
id: string;
text: string;
localizedText?: string;
defaultFunction?: string;
defaultAsync?: string;
createdAt: string;
@@ -292,6 +349,22 @@ export interface IndexedField {
*/
point?: [number, number];
};
collapsibleLocalizedUnique?: string;
collapsibleTextUnique?: string;
createdAt: string;
updatedAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "date-fields".
*/
export interface DateField {
id: string;
default: string;
timeOnly?: string;
dayOnly?: string;
dayAndTime?: string;
monthOnly?: string;
createdAt: string;
updatedAt: string;
}