test: group relationships
* test: relationship fields inside groups and subgroup * test: group nested relationships and arrays * test: improves coverage for hooks Co-authored-by: James <james@trbl.design>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { CollectionConfig } from '../../../src/collections/config/types';
|
||||
import type { CollectionConfig } from '../../src/collections/config/types';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { devUser } from '../credentials';
|
||||
import { mapAsync } from '../../src/utilities/mapAsync';
|
||||
@@ -9,8 +9,6 @@ export const relationOneSlug = 'relation-one';
|
||||
export const relationTwoSlug = 'relation-two';
|
||||
export const relationRestrictedSlug = 'relation-restricted';
|
||||
export const relationWithTitleSlug = 'relation-with-title';
|
||||
export const groupWithNestedRelationship = 'group-nested-relation-with-title';
|
||||
export const nestedRelationship = 'nested-relation-with-title';
|
||||
|
||||
export interface FieldsRelationship {
|
||||
id: string;
|
||||
@@ -39,20 +37,6 @@ const baseRelationshipFields: CollectionConfig['fields'] = [
|
||||
},
|
||||
];
|
||||
|
||||
const groupWithNestedRelationshipFields = (relationTo: string): CollectionConfig['fields'] => ([
|
||||
{
|
||||
type: 'group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'relation',
|
||||
type: 'relationship',
|
||||
relationTo,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
{
|
||||
@@ -126,14 +110,6 @@ export default buildConfig({
|
||||
},
|
||||
fields: baseRelationshipFields,
|
||||
},
|
||||
{
|
||||
slug: groupWithNestedRelationship,
|
||||
fields: groupWithNestedRelationshipFields(nestedRelationship),
|
||||
},
|
||||
{
|
||||
slug: nestedRelationship,
|
||||
fields: groupWithNestedRelationshipFields(relationWithTitleSlug),
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { groupWithNestedRelationship, nestedRelationship, relationWithTitleSlug } from './config';
|
||||
import { initPayloadTest } from '../helpers/configHelpers';
|
||||
import { RESTClient } from '../helpers/rest';
|
||||
import config from '../uploads/config';
|
||||
import payload from '../../src';
|
||||
import type { GroupNestedRelationWithTitle, NestedRelationWithTitle, RelationWithTitle } from './payload-types';
|
||||
|
||||
let client;
|
||||
|
||||
describe('fields-relationship', () => {
|
||||
beforeAll(async (done) => {
|
||||
const { serverURL } = await initPayloadTest({
|
||||
__dirname,
|
||||
init: { local: false },
|
||||
});
|
||||
client = new RESTClient(config, {
|
||||
serverURL,
|
||||
defaultSlug: nestedRelationship,
|
||||
});
|
||||
await client.login();
|
||||
|
||||
done();
|
||||
});
|
||||
describe('relationships within groups', () => {
|
||||
let document: GroupNestedRelationWithTitle;
|
||||
let child: NestedRelationWithTitle;
|
||||
let grandChild: RelationWithTitle;
|
||||
|
||||
beforeAll(async () => {
|
||||
grandChild = await payload.create<RelationWithTitle>({
|
||||
collection: relationWithTitleSlug,
|
||||
data: {
|
||||
text: 'grand child',
|
||||
},
|
||||
});
|
||||
child = await payload.create<NestedRelationWithTitle>({
|
||||
collection: groupWithNestedRelationship,
|
||||
data: {
|
||||
group: {
|
||||
relation: grandChild.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { id } = await payload.create<GroupNestedRelationWithTitle>({
|
||||
depth: 3,
|
||||
collection: groupWithNestedRelationship,
|
||||
data: {
|
||||
group: {
|
||||
relation: child.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
document = await payload.findByID<GroupNestedRelationWithTitle>({
|
||||
id,
|
||||
collection: groupWithNestedRelationship,
|
||||
depth: 9,
|
||||
});
|
||||
});
|
||||
|
||||
it('relationships should populate inside of groups several levels deep', async () => {
|
||||
expect(document.group.relation.group.relation.id)
|
||||
.toStrictEqual(grandChild.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -97,7 +97,9 @@ export interface GroupNestedRelationWithTitle {
|
||||
export interface NestedRelationWithTitle {
|
||||
id: string;
|
||||
group?: {
|
||||
relation?: string | RelationWithTitle;
|
||||
subGroup?: {
|
||||
relation?: string | RelationOne;
|
||||
};
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
@@ -58,6 +58,16 @@ const ArrayFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'potentiallyEmptyArray',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -57,6 +57,16 @@ const GroupFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'potentiallyEmptyGroup',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import config from '../uploads/config';
|
||||
import payload from '../../src';
|
||||
import { pointDoc } from './collections/Point';
|
||||
import type { ArrayField, GroupField } from './payload-types';
|
||||
import { arrayFieldsSlug, arrayDefaultValue } from './collections/Array';
|
||||
import { arrayFieldsSlug, arrayDefaultValue, arrayDoc } from './collections/Array';
|
||||
import { groupFieldsSlug, groupDefaultChild, groupDefaultValue, groupDoc } from './collections/Group';
|
||||
import { defaultText } from './collections/Text';
|
||||
|
||||
@@ -89,6 +89,15 @@ describe('Fields', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array for arrays when no data present', async () => {
|
||||
const document = await payload.create<ArrayField>({
|
||||
collection: arrayFieldsSlug,
|
||||
data: arrayDoc,
|
||||
});
|
||||
|
||||
expect(document.potentiallyEmptyArray).toEqual([]);
|
||||
});
|
||||
|
||||
it('should create with ids and nested ids', async () => {
|
||||
const docWithIDs = await payload.create<GroupField>({
|
||||
collection: groupFieldsSlug,
|
||||
@@ -158,5 +167,14 @@ describe('Fields', () => {
|
||||
expect(document.group.defaultParent).toStrictEqual(groupDefaultValue);
|
||||
expect(document.group.defaultChild).toStrictEqual(groupDefaultChild);
|
||||
});
|
||||
|
||||
it('should return empty object for groups when no data present', async () => {
|
||||
const doc = await payload.create<GroupField>({
|
||||
collection: groupFieldsSlug,
|
||||
data: groupDoc,
|
||||
});
|
||||
|
||||
expect(doc.potentiallyEmptyGroup).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,11 @@ export interface ArrayField {
|
||||
text: string;
|
||||
id?: string;
|
||||
}[];
|
||||
readOnly?: {
|
||||
readOnly: {
|
||||
text?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
potentiallyEmptyArray: {
|
||||
text?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
@@ -47,7 +51,7 @@ export interface BlockField {
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
subBlocks?: (
|
||||
subBlocks: (
|
||||
| {
|
||||
text: string;
|
||||
id?: string;
|
||||
@@ -76,9 +80,9 @@ export interface BlockField {
|
||||
export interface CollapsibleField {
|
||||
id: string;
|
||||
text: string;
|
||||
group?: {
|
||||
group: {
|
||||
textWithinGroup?: string;
|
||||
subGroup?: {
|
||||
subGroup: {
|
||||
textWithinSubGroup?: string;
|
||||
};
|
||||
};
|
||||
@@ -103,18 +107,21 @@ export interface ConditionalLogic {
|
||||
*/
|
||||
export interface GroupField {
|
||||
id: string;
|
||||
group?: {
|
||||
group: {
|
||||
text: string;
|
||||
defaultParent?: string;
|
||||
defaultChild?: string;
|
||||
subGroup?: {
|
||||
subGroup: {
|
||||
textWithinGroup?: string;
|
||||
arrayWithinGroup?: {
|
||||
arrayWithinGroup: {
|
||||
textWithinArray?: string;
|
||||
id?: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
potentiallyEmptyGroup: {
|
||||
text?: string;
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -134,7 +141,7 @@ export interface PointField {
|
||||
* @maxItems 2
|
||||
*/
|
||||
localized?: [number, number];
|
||||
group?: {
|
||||
group: {
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
@@ -192,7 +199,7 @@ export interface TabsField {
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
subBlocks?: (
|
||||
subBlocks: (
|
||||
| {
|
||||
text: string;
|
||||
id?: string;
|
||||
@@ -211,7 +218,7 @@ export interface TabsField {
|
||||
blockType: 'subBlocks';
|
||||
}
|
||||
)[];
|
||||
group?: {
|
||||
group: {
|
||||
number: number;
|
||||
};
|
||||
textarea?: string;
|
||||
|
||||
@@ -39,7 +39,7 @@ if (testConfigDir) {
|
||||
function setPaths(dir) {
|
||||
const configPath = path.resolve(dir, 'config.ts');
|
||||
const outputPath = path.resolve(dir, 'payload-types.ts');
|
||||
if (fs.existsSync(configPath) && fs.existsSync(outputPath)) {
|
||||
if (fs.existsSync(configPath)) {
|
||||
process.env.PAYLOAD_CONFIG_PATH = configPath;
|
||||
process.env.PAYLOAD_TS_OUTPUT_PATH = outputPath;
|
||||
return true;
|
||||
|
||||
@@ -95,6 +95,7 @@ const createHookOrder = [
|
||||
'collectionBeforeChange',
|
||||
'fieldBeforeChange',
|
||||
'fieldAfterRead',
|
||||
'collectionAfterRead',
|
||||
'fieldAfterChange',
|
||||
'collectionAfterChange',
|
||||
];
|
||||
|
||||
68
test/hooks/collections/NestedAfterReadHooks/index.ts
Normal file
68
test/hooks/collections/NestedAfterReadHooks/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { CollectionConfig } from '../../../../src/collections/config/types';
|
||||
import { relationsSlug } from '../Relations';
|
||||
|
||||
export const nestedAfterReadHooksSlug = 'nested-after-read-hooks';
|
||||
|
||||
export const generatedAfterReadText = 'hello';
|
||||
|
||||
const NestedAfterReadHooks: CollectionConfig = {
|
||||
slug: nestedAfterReadHooksSlug,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'array',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'input',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'afterRead',
|
||||
hooks: {
|
||||
afterRead: [(): string => {
|
||||
return generatedAfterReadText;
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'shouldPopulate',
|
||||
type: 'relationship',
|
||||
relationTo: relationsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
name: 'subGroup',
|
||||
fields: [
|
||||
{
|
||||
name: 'afterRead',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
afterRead: [(): string => {
|
||||
return generatedAfterReadText;
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'shouldPopulate',
|
||||
type: 'relationship',
|
||||
relationTo: relationsSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default NestedAfterReadHooks;
|
||||
16
test/hooks/collections/Relations/index.ts
Normal file
16
test/hooks/collections/Relations/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { CollectionConfig } from '../../../../src/collections/config/types';
|
||||
|
||||
export const relationsSlug = 'relations';
|
||||
|
||||
const Relations: CollectionConfig = {
|
||||
slug: relationsSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default Relations;
|
||||
@@ -1,10 +1,16 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { devUser } from '../credentials';
|
||||
import TransformHooks from './collections/Transform';
|
||||
import Hooks, { hooksSlug } from './collections/Hook';
|
||||
import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
|
||||
import Relations from './collections/Relations';
|
||||
|
||||
export default buildConfig({
|
||||
collections: [TransformHooks, Hooks],
|
||||
collections: [
|
||||
TransformHooks,
|
||||
Hooks,
|
||||
NestedAfterReadHooks,
|
||||
Relations,
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: hooksSlug,
|
||||
|
||||
@@ -3,10 +3,11 @@ import { initPayloadTest } from '../helpers/configHelpers';
|
||||
import config from './config';
|
||||
import payload from '../../src';
|
||||
import { RESTClient } from '../helpers/rest';
|
||||
import type { Post } from './payload-types';
|
||||
import { mapAsync } from '../../src/utilities/mapAsync';
|
||||
import { transformSlug } from './collections/Transform';
|
||||
import { hooksSlug } from './collections/Hook';
|
||||
import { generatedAfterReadText, nestedAfterReadHooksSlug } from './collections/NestedAfterReadHooks';
|
||||
import { relationsSlug } from './collections/Relations';
|
||||
import type { NestedAfterReadHook } from './payload-types';
|
||||
|
||||
let client: RESTClient;
|
||||
|
||||
@@ -22,10 +23,6 @@ describe('Hooks', () => {
|
||||
await payload.mongoMemoryServer.stop();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearDocs();
|
||||
});
|
||||
|
||||
describe('transform actions', () => {
|
||||
it('should create and not throw an error', async () => {
|
||||
// the collection has hooks that will cause an error if transform actions is not handled properly
|
||||
@@ -68,13 +65,56 @@ describe('Hooks', () => {
|
||||
expect(doc.collectionAfterChange).toEqual(true);
|
||||
expect(doc.fieldAfterRead).toEqual(true);
|
||||
});
|
||||
|
||||
it('should save data generated with afterRead hooks in nested field structures', async () => {
|
||||
const document = await payload.create<NestedAfterReadHook>({
|
||||
collection: nestedAfterReadHooksSlug,
|
||||
data: {
|
||||
text: 'ok',
|
||||
group: {
|
||||
array: [
|
||||
{ input: 'input' },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(document.group.subGroup.afterRead).toEqual(generatedAfterReadText);
|
||||
expect(document.group.array[0].afterRead).toEqual(generatedAfterReadText);
|
||||
});
|
||||
|
||||
it('should populate related docs within nested field structures', async () => {
|
||||
const relation = await payload.create({
|
||||
collection: relationsSlug,
|
||||
data: {
|
||||
title: 'Hello',
|
||||
},
|
||||
});
|
||||
|
||||
const document = await payload.create({
|
||||
collection: nestedAfterReadHooksSlug,
|
||||
data: {
|
||||
text: 'ok',
|
||||
group: {
|
||||
array: [
|
||||
{
|
||||
shouldPopulate: relation.id,
|
||||
},
|
||||
],
|
||||
subGroup: {
|
||||
shouldPopulate: relation.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const retrievedDoc = await payload.findByID({
|
||||
collection: nestedAfterReadHooksSlug,
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
expect(retrievedDoc.group.array[0].shouldPopulate.title).toEqual(relation.title);
|
||||
expect(retrievedDoc.group.subGroup.shouldPopulate.title).toEqual(relation.title);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function clearDocs(): Promise<void> {
|
||||
const allDocs = await payload.find<Post>({ collection: transformSlug, limit: 100 });
|
||||
const ids = allDocs.docs.map((doc) => doc.id);
|
||||
await mapAsync(ids, async (id) => {
|
||||
await payload.delete({ collection: transformSlug, id });
|
||||
});
|
||||
}
|
||||
|
||||
91
test/hooks/payload-types.ts
Normal file
91
test/hooks/payload-types.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/* 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` "transforms".
|
||||
*/
|
||||
export interface Transform {
|
||||
id: string;
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
transform?: [number, number];
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
localizedTransform?: [number, number];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "hooks".
|
||||
*/
|
||||
export interface Hook {
|
||||
id: string;
|
||||
fieldBeforeValidate?: boolean;
|
||||
fieldBeforeChange?: boolean;
|
||||
fieldAfterChange?: boolean;
|
||||
fieldAfterRead?: boolean;
|
||||
collectionBeforeValidate?: boolean;
|
||||
collectionBeforeChange?: boolean;
|
||||
collectionAfterChange?: boolean;
|
||||
collectionBeforeRead?: boolean;
|
||||
collectionAfterRead?: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "nested-after-read-hooks".
|
||||
*/
|
||||
export interface NestedAfterReadHook {
|
||||
id: string;
|
||||
text?: string;
|
||||
group: {
|
||||
array: {
|
||||
input?: string;
|
||||
afterRead?: string;
|
||||
shouldPopulate?: string | Relation;
|
||||
id?: string;
|
||||
}[];
|
||||
subGroup: {
|
||||
afterRead?: string;
|
||||
shouldPopulate?: string | Relation;
|
||||
};
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "relations".
|
||||
*/
|
||||
export interface Relation {
|
||||
id: string;
|
||||
title: 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;
|
||||
}
|
||||
Reference in New Issue
Block a user