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:
Dan Ribbens
2022-07-20 14:52:21 -04:00
committed by GitHub
parent 893772ebd8
commit 09f57e9a4c
14 changed files with 299 additions and 119 deletions

View File

@@ -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({

View File

@@ -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);
});
});
});

View File

@@ -97,7 +97,9 @@ export interface GroupNestedRelationWithTitle {
export interface NestedRelationWithTitle {
id: string;
group?: {
relation?: string | RelationWithTitle;
subGroup?: {
relation?: string | RelationOne;
};
};
createdAt: string;
updatedAt: string;

View File

@@ -58,6 +58,16 @@ const ArrayFields: CollectionConfig = {
},
],
},
{
type: 'array',
name: 'potentiallyEmptyArray',
fields: [
{
type: 'text',
name: 'text',
},
],
},
],
};

View File

@@ -57,6 +57,16 @@ const GroupFields: CollectionConfig = {
},
],
},
{
name: 'potentiallyEmptyGroup',
type: 'group',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
};

View File

@@ -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({});
});
});
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -95,6 +95,7 @@ const createHookOrder = [
'collectionBeforeChange',
'fieldBeforeChange',
'fieldAfterRead',
'collectionAfterRead',
'fieldAfterChange',
'collectionAfterChange',
];

View 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;

View 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;

View File

@@ -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,

View File

@@ -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' },
],
},
},
});
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 });
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);
});
});
});
}

View 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;
}