chore: merge master
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { PostsCollection, postsSlug } from './collections/Posts';
|
||||
import { MenuGlobal } from './globals/Menu';
|
||||
import { devUser } from '../credentials';
|
||||
import { MediaCollection } from './collections/Media';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
// ...extend config here
|
||||
collections: [
|
||||
PostsCollection,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { devUser } from '../credentials';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { FieldAccess } from '../../src/fields/config/types';
|
||||
import { firstArrayText, secondArrayText } from './shared';
|
||||
|
||||
@@ -36,7 +36,7 @@ const UseRequestHeadersAccess: FieldAccess = ({ req: { headers } }) => {
|
||||
return !!headers && headers.authorization === requestHeaders.authorization;
|
||||
};
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
user: 'users',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import { mapAsync } from '../../src/utilities/mapAsync';
|
||||
import { devUser } from '../credentials';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import AfterDashboard from './components/AfterDashboard';
|
||||
import CustomMinimalRoute from './components/views/CustomMinimal';
|
||||
import CustomDefaultRoute from './components/views/CustomDefault';
|
||||
@@ -20,7 +20,7 @@ export interface Post {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
css: path.resolve(__dirname, 'styles.scss'),
|
||||
components: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
{
|
||||
slug: 'arrays',
|
||||
@@ -41,4 +42,13 @@ export default buildConfig({
|
||||
],
|
||||
},
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { mapAsync } from '../../src/utilities/mapAsync';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
import { AuthDebug } from './AuthDebug';
|
||||
|
||||
export const slug = 'users';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
user: 'users',
|
||||
autoLogin: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request } from 'express';
|
||||
import { Strategy } from 'passport-strategy';
|
||||
import { Payload } from '../../../src/payload';
|
||||
import { buildConfig } from '../../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../../buildConfigWithDefaults';
|
||||
|
||||
export const slug = 'users';
|
||||
export const strategyName = 'test-local';
|
||||
@@ -41,7 +41,7 @@ export class CustomStrategy extends Strategy {
|
||||
}
|
||||
}
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
user: 'users',
|
||||
},
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
import { Config, SanitizedConfig } from '../src/config/types';
|
||||
import { buildConfig as buildPayloadConfig } from '../src/config/build';
|
||||
|
||||
export function buildConfig(config?: Partial<Config>): Promise<SanitizedConfig> {
|
||||
export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<SanitizedConfig> {
|
||||
const [name] = process.argv.slice(2);
|
||||
const baseConfig: Config = {
|
||||
const config: Config = {
|
||||
telemetry: false,
|
||||
rateLimit: {
|
||||
window: 15 * 60 * 100, // 15min default,
|
||||
max: 9999999999,
|
||||
},
|
||||
...config,
|
||||
...testConfig,
|
||||
};
|
||||
baseConfig.admin = {
|
||||
|
||||
config.admin = {
|
||||
autoLogin: process.env.PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN === 'true' ? false : {
|
||||
email: 'dev@payloadcms.com',
|
||||
password: 'test',
|
||||
},
|
||||
...(baseConfig.admin || {}),
|
||||
...(config.admin || {}),
|
||||
webpack: (webpackConfig) => {
|
||||
const existingConfig = typeof config?.admin?.webpack === 'function'
|
||||
? config.admin.webpack(webpackConfig)
|
||||
const existingConfig = typeof testConfig?.admin?.webpack === 'function'
|
||||
? testConfig.admin.webpack(webpackConfig)
|
||||
: webpackConfig;
|
||||
return {
|
||||
...existingConfig,
|
||||
name,
|
||||
cache: process.env.NODE_ENV === 'test' ? {
|
||||
type: 'memory',
|
||||
} : existingConfig.cache,
|
||||
cache: process.env.NODE_ENV === 'test'
|
||||
? { type: 'memory' }
|
||||
: existingConfig.cache,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.PAYLOAD_DISABLE_ADMIN === 'true') {
|
||||
if (typeof baseConfig.admin !== 'object') baseConfig.admin = {};
|
||||
baseConfig.admin.disable = true;
|
||||
if (typeof config.admin !== 'object') config.admin = {};
|
||||
config.admin.disable = true;
|
||||
}
|
||||
|
||||
return buildPayloadConfig(baseConfig);
|
||||
return buildPayloadConfig(config);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import type { CollectionConfig } from '../../src/collections/config/types';
|
||||
import { devUser } from '../credentials';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
|
||||
export interface Relation {
|
||||
id: string;
|
||||
@@ -30,7 +30,7 @@ const collectionWithName = (collectionSlug: string): CollectionConfig => {
|
||||
|
||||
export const slug = 'posts';
|
||||
export const relationSlug = 'relation';
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
graphQL: {
|
||||
schemaOutputFile: path.resolve(__dirname, 'schema.graphql'),
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from '../../src/collections/config/types';
|
||||
import { devUser } from '../credentials';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
|
||||
export interface Relation {
|
||||
id: string;
|
||||
@@ -34,7 +34,7 @@ export const customIdSlug = 'custom-id';
|
||||
export const customIdNumberSlug = 'custom-id-number';
|
||||
export const errorOnHookSlug = 'error-on-hooks';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
endpoints: [
|
||||
{
|
||||
path: '/send-test-email',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { openAccess } from '../helpers/configHelpers';
|
||||
import { Config } from '../../src/config/types';
|
||||
|
||||
@@ -62,4 +62,4 @@ const config: Config = {
|
||||
custom: { name: 'Customer portal' },
|
||||
};
|
||||
|
||||
export default buildConfig(config);
|
||||
export default buildConfigWithDefaults(config);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
{
|
||||
slug: 'posts',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import express, { Response } from 'express';
|
||||
import { devUser } from '../credentials';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { openAccess } from '../helpers/configHelpers';
|
||||
import { PayloadRequest } from '../../src/express/types';
|
||||
import { Config } from '../../src/config/types';
|
||||
@@ -148,4 +148,4 @@ const MyConfig: Config = {
|
||||
},
|
||||
};
|
||||
|
||||
export default buildConfig(MyConfig);
|
||||
export default buildConfigWithDefaults(MyConfig);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { ErrorFieldsCollection } from './collections/ErrorFields';
|
||||
import { devUser } from '../credentials';
|
||||
import Uploads from './collections/Upload';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
ErrorFieldsCollection,
|
||||
Uploads,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
{
|
||||
slug: 'blocks-collection',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CollectionConfig } from '../../src/collections/config/types';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
import { mapAsync } from '../../src/utilities/mapAsync';
|
||||
import { FilterOptionsProps } from '../../src/fields/config/types';
|
||||
@@ -33,7 +33,7 @@ const baseRelationshipFields: CollectionConfig['fields'] = [
|
||||
},
|
||||
];
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
{
|
||||
slug,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
import ArrayFields, { arrayDoc } from './collections/Array';
|
||||
import BlockFields, { blocksDoc } from './collections/Blocks';
|
||||
@@ -26,7 +26,7 @@ import Uploads2 from './collections/Upload2';
|
||||
import Uploads3 from './collections/Uploads3';
|
||||
import RowFields from './collections/Row';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
webpack: (config) => ({
|
||||
...config,
|
||||
|
||||
@@ -411,6 +411,88 @@ describe('fields', () => {
|
||||
const customRowLabel = await page.locator('#rowLabelAsComponent-row-0 >> .row-label :text("custom row label")');
|
||||
await expect(customRowLabel).toHaveCSS('text-transform', 'uppercase');
|
||||
});
|
||||
|
||||
describe('row manipulation', () => {
|
||||
test('should add 2 new rows', async () => {
|
||||
await page.goto(url.create);
|
||||
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
await page.locator('#field-potentiallyEmptyArray__0__text').fill('array row 1');
|
||||
await page.locator('#field-potentiallyEmptyArray__1__text').fill('array row 2');
|
||||
|
||||
await saveDocAndAssert(page);
|
||||
});
|
||||
|
||||
test('should remove 2 new rows', async () => {
|
||||
await page.goto(url.create);
|
||||
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
await page.locator('#field-potentiallyEmptyArray__0__text').fill('array row 1');
|
||||
await page.locator('#field-potentiallyEmptyArray__1__text').fill('array row 2');
|
||||
|
||||
await page.locator('#potentiallyEmptyArray-row-1 .array-actions__button').click();
|
||||
await page.locator('#potentiallyEmptyArray-row-1 .popup__scroll .array-actions__remove').click();
|
||||
await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click();
|
||||
await page.locator('#potentiallyEmptyArray-row-0 .popup__scroll .array-actions__remove').click();
|
||||
|
||||
const rowsContainer = await page.locator('#field-potentiallyEmptyArray > .array-field__draggable-rows');
|
||||
const directChildDivCount = await rowsContainer.evaluate((element) => {
|
||||
const childDivCount = element.querySelectorAll(':scope > div');
|
||||
return childDivCount.length;
|
||||
});
|
||||
|
||||
expect(directChildDivCount).toBe(0);
|
||||
});
|
||||
|
||||
test('should remove existing row', async () => {
|
||||
await page.goto(url.create);
|
||||
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
await page.locator('#field-potentiallyEmptyArray__0__text').fill('array row 1');
|
||||
|
||||
await saveDocAndAssert(page);
|
||||
|
||||
await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click();
|
||||
await page.locator('#potentiallyEmptyArray-row-0 .popup__scroll .array-actions__action.array-actions__remove').click();
|
||||
|
||||
const rowsContainer = await page.locator('#field-potentiallyEmptyArray > .array-field__draggable-rows');
|
||||
const directChildDivCount = await rowsContainer.evaluate((element) => {
|
||||
const childDivCount = element.querySelectorAll(':scope > div');
|
||||
return childDivCount.length;
|
||||
});
|
||||
|
||||
expect(directChildDivCount).toBe(0);
|
||||
});
|
||||
|
||||
test('should add row after removing existing row', async () => {
|
||||
await page.goto(url.create);
|
||||
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
await page.locator('#field-potentiallyEmptyArray__0__text').fill('array row 1');
|
||||
await page.locator('#field-potentiallyEmptyArray__1__text').fill('array row 2');
|
||||
|
||||
await saveDocAndAssert(page);
|
||||
|
||||
await page.locator('#potentiallyEmptyArray-row-1 .array-actions__button').click();
|
||||
await page.locator('#potentiallyEmptyArray-row-1 .popup__scroll .array-actions__action.array-actions__remove').click();
|
||||
await page.locator('#field-potentiallyEmptyArray > .array-field__add-button-wrap > button').click();
|
||||
|
||||
await page.locator('#field-potentiallyEmptyArray__1__text').fill('updated array row 2');
|
||||
|
||||
await saveDocAndAssert(page);
|
||||
|
||||
const rowsContainer = await page.locator('#field-potentiallyEmptyArray > .array-field__draggable-rows');
|
||||
const directChildDivCount = await rowsContainer.evaluate((element) => {
|
||||
const childDivCount = element.querySelectorAll(':scope > div');
|
||||
return childDivCount.length;
|
||||
});
|
||||
|
||||
expect(directChildDivCount).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tabs', () => {
|
||||
@@ -494,7 +576,7 @@ describe('fields', () => {
|
||||
await wait(200);
|
||||
await editLinkModal.locator('button[type="submit"]').click();
|
||||
const errorField = await page.locator('[id^=drawer_1_rich-text-link-] .render-fields > :nth-child(3)');
|
||||
const hasErrorClass = await errorField.evaluate(el => el.classList.contains('error'));
|
||||
const hasErrorClass = await errorField.evaluate((el) => el.classList.contains('error'));
|
||||
expect(hasErrorClass).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { devUser } from '../credentials';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
|
||||
export const slug = 'global';
|
||||
export const arraySlug = 'array';
|
||||
@@ -16,7 +16,7 @@ const access = {
|
||||
update: () => true,
|
||||
};
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
localization: {
|
||||
locales: [englishLocale, spanishLocale],
|
||||
defaultLocale: englishLocale,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
graphQL: {
|
||||
schemaOutputFile: path.resolve(__dirname, 'schema.graphql'),
|
||||
},
|
||||
|
||||
68
test/hooks/collections/ContextHooks/index.ts
Normal file
68
test/hooks/collections/ContextHooks/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import payload from '../../../../src';
|
||||
import { CollectionConfig } from '../../../../src/collections/config/types';
|
||||
import type { PayloadRequest } from '../../../../src/types';
|
||||
|
||||
export const contextHooksSlug = 'context-hooks';
|
||||
const ContextHooks: CollectionConfig = {
|
||||
slug: contextHooksSlug,
|
||||
access: {
|
||||
read: () => true,
|
||||
create: () => true,
|
||||
delete: () => true,
|
||||
update: () => true,
|
||||
},
|
||||
hooks: {
|
||||
beforeOperation: [async ({ context, args }) => {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const req: PayloadRequest = args.req;
|
||||
|
||||
if (!req.query || !Object.keys(req.query).length) {
|
||||
return args;
|
||||
}
|
||||
|
||||
Object.keys(req.query).forEach((key) => {
|
||||
if (key.startsWith('context_')) {
|
||||
// Strip 'context_' from key, add it to context object and remove it from query params
|
||||
const newKey = key.substring('context_'.length);
|
||||
context[newKey] = req.query[key];
|
||||
delete req.query[key];
|
||||
}
|
||||
});
|
||||
|
||||
return args;
|
||||
}],
|
||||
beforeChange: [({ context, data, req }) => {
|
||||
if (!context.secretValue) {
|
||||
context.secretValue = 'secret';
|
||||
}
|
||||
if (req.context !== context) {
|
||||
throw new Error('req.context !== context');
|
||||
}
|
||||
return data;
|
||||
}],
|
||||
afterChange: [async ({ context, doc }) => {
|
||||
if (context.triggerAfterChange === false) { // Make sure we don't trigger afterChange again and again in an infinite loop
|
||||
return;
|
||||
}
|
||||
await payload.update({
|
||||
collection: contextHooksSlug,
|
||||
id: doc.id,
|
||||
data: {
|
||||
value: context.secretValue ?? '',
|
||||
},
|
||||
context: {
|
||||
triggerAfterChange: false, // Make sure we don't trigger afterChange again and again in an infinite loop. This should be done via context and not a potential disableHooks property, as we want to specifically test the context functionality here
|
||||
},
|
||||
});
|
||||
}],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default ContextHooks;
|
||||
@@ -1,13 +1,15 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import TransformHooks from './collections/Transform';
|
||||
import Hooks, { hooksSlug } from './collections/Hook';
|
||||
import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
|
||||
import ChainingHooks from './collections/ChainingHooks';
|
||||
import Relations from './collections/Relations';
|
||||
import Users, { seedHooksUsers } from './collections/Users';
|
||||
import ContextHooks from './collections/ContextHooks';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
ContextHooks,
|
||||
TransformHooks,
|
||||
Hooks,
|
||||
NestedAfterReadHooks,
|
||||
|
||||
@@ -11,13 +11,16 @@ import type { NestedAfterReadHook } from './payload-types';
|
||||
import { hooksUsersSlug } from './collections/Users';
|
||||
import { devUser, regularUser } from '../credentials';
|
||||
import { AuthenticationError } from '../../src/errors';
|
||||
import { contextHooksSlug } from './collections/ContextHooks';
|
||||
|
||||
let client: RESTClient;
|
||||
let apiUrl;
|
||||
|
||||
describe('Hooks', () => {
|
||||
beforeAll(async () => {
|
||||
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
|
||||
client = new RESTClient(config, { serverURL, defaultSlug: transformSlug });
|
||||
apiUrl = `${serverURL}/api`;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -148,6 +151,63 @@ describe('Hooks', () => {
|
||||
|
||||
expect(retrievedDocs[0].text).toEqual('ok!!');
|
||||
});
|
||||
|
||||
it('should pass context from beforeChange to afterChange', async () => {
|
||||
const document = await payload.create({
|
||||
collection: contextHooksSlug,
|
||||
data: {
|
||||
value: 'wrongvalue',
|
||||
},
|
||||
});
|
||||
|
||||
const retrievedDoc = await payload.findByID({
|
||||
collection: contextHooksSlug,
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
expect(retrievedDoc.value).toEqual('secret');
|
||||
});
|
||||
|
||||
it('should pass context from local API to hooks', async () => {
|
||||
const document = await payload.create({
|
||||
collection: contextHooksSlug,
|
||||
data: {
|
||||
value: 'wrongvalue',
|
||||
},
|
||||
context: {
|
||||
secretValue: 'data from local API',
|
||||
},
|
||||
});
|
||||
|
||||
const retrievedDoc = await payload.findByID({
|
||||
collection: contextHooksSlug,
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
expect(retrievedDoc.value).toEqual('data from local API');
|
||||
});
|
||||
|
||||
it('should pass context from rest API to hooks', async () => {
|
||||
const params = new URLSearchParams({
|
||||
context_secretValue: 'data from rest API',
|
||||
});
|
||||
// send context as query params. It will be parsed by the beforeOperation hook
|
||||
const response = await fetch(`${apiUrl}/${contextHooksSlug}?${params.toString()}`, {
|
||||
body: JSON.stringify({
|
||||
value: 'wrongvalue',
|
||||
}),
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
const document = (await response.json()).doc;
|
||||
|
||||
const retrievedDoc = await payload.findByID({
|
||||
collection: contextHooksSlug,
|
||||
id: document.id,
|
||||
});
|
||||
|
||||
expect(retrievedDoc.value).toEqual('data from rest API');
|
||||
});
|
||||
});
|
||||
|
||||
describe('auth collection hooks', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
import { ArrayCollection } from './collections/Array';
|
||||
import { LocalizedPost, RelationshipLocalized } from './payload-types';
|
||||
@@ -32,7 +32,7 @@ const openAccess = {
|
||||
update: () => true,
|
||||
};
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
localization: {
|
||||
locales: [defaultLocale, spanishLocale],
|
||||
defaultLocale,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
|
||||
export const pagesSlug = 'pages';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
{
|
||||
slug: 'users',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
import GlobalViewWithRefresh from './GlobalViewWithRefresh';
|
||||
|
||||
export const pagesSlug = 'pages';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
globals: [
|
||||
{
|
||||
slug: 'settings',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from '../../src/collections/config/types';
|
||||
import { devUser } from '../credentials';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
|
||||
const openAccess = {
|
||||
create: () => true,
|
||||
@@ -41,7 +41,7 @@ export const defaultAccessRelSlug = 'strict-access';
|
||||
export const chainedRelSlug = 'chained-relation';
|
||||
export const customIdSlug = 'custom-id-relation';
|
||||
export const customIdNumberSlug = 'custom-id-number-relation';
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
{
|
||||
slug,
|
||||
|
||||
36
test/uploads/collections/admin-thumbnail/index.ts
Normal file
36
test/uploads/collections/admin-thumbnail/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import path from 'path';
|
||||
import { CollectionConfig } from '../../../../src/collections/config/types';
|
||||
|
||||
type TypeWithFile = {
|
||||
filename: string;
|
||||
mimeType: string;
|
||||
filesize: number;
|
||||
} & Record<string, unknown>
|
||||
|
||||
function docHasFilename(doc: Record<string, unknown>): doc is TypeWithFile {
|
||||
if (typeof doc === 'object' && 'filename' in doc) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const adminThumbnailSrc = '/media/image-640x480.png';
|
||||
|
||||
export const AdminThumbnailCol: CollectionConfig = {
|
||||
slug: 'admin-thumbnail',
|
||||
upload: {
|
||||
staticDir: path.resolve(__dirname, '../../media'),
|
||||
adminThumbnail: ({ doc }) => {
|
||||
if (docHasFilename(doc)) {
|
||||
if (doc.mimeType.startsWith('image/')) {
|
||||
return null; // Fallback to default admin thumbnail if image
|
||||
}
|
||||
return adminThumbnailSrc; // Use custom thumbnail if not image
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
fields: [],
|
||||
};
|
||||
|
||||
export default AdminThumbnailCol;
|
||||
@@ -1,10 +1,11 @@
|
||||
import path from 'path';
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import { devUser } from '../credentials';
|
||||
import getFileByPath from '../../src/uploads/getFileByPath';
|
||||
import removeFiles from '../helpers/removeFiles';
|
||||
import { Uploads1 } from './collections/Upload1';
|
||||
import Uploads2 from './collections/Upload2';
|
||||
import AdminThumbnailCol from './collections/admin-thumbnail';
|
||||
|
||||
export const mediaSlug = 'media';
|
||||
|
||||
@@ -12,9 +13,11 @@ export const relationSlug = 'relation';
|
||||
|
||||
export const audioSlug = 'audio';
|
||||
|
||||
export const adminThumbnailSlug = 'admin-thumbnail';
|
||||
|
||||
const mockModulePath = path.resolve(__dirname, './mocks/mockFSModule.js');
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
webpack: (config) => ({
|
||||
...config,
|
||||
@@ -198,6 +201,7 @@ export default buildConfig({
|
||||
},
|
||||
Uploads1,
|
||||
Uploads2,
|
||||
AdminThumbnailCol,
|
||||
],
|
||||
onInit: async (payload) => {
|
||||
const uploadsDir = path.resolve(__dirname, './media');
|
||||
@@ -244,5 +248,24 @@ export default buildConfig({
|
||||
audio: file.id,
|
||||
},
|
||||
});
|
||||
|
||||
// Create admin thumbnail media
|
||||
await payload.create({
|
||||
collection: AdminThumbnailCol.slug,
|
||||
data: {},
|
||||
file: {
|
||||
...audioFile,
|
||||
name: 'audio-thumbnail.mp3', // Override to avoid conflicts
|
||||
},
|
||||
});
|
||||
|
||||
await payload.create({
|
||||
collection: AdminThumbnailCol.slug,
|
||||
data: {},
|
||||
file: {
|
||||
...imageFile,
|
||||
name: `thumb-${imageFile.name}`,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import path from 'path';
|
||||
import { relationSlug, mediaSlug, audioSlug } from './config';
|
||||
import { relationSlug, mediaSlug, audioSlug, adminThumbnailSlug } from './config';
|
||||
import type { Media } from './payload-types';
|
||||
import payload from '../../src';
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil';
|
||||
import { initPayloadE2E } from '../helpers/configHelpers';
|
||||
import { saveDocAndAssert } from '../helpers';
|
||||
import wait from '../../src/utilities/wait';
|
||||
import { adminThumbnailSrc } from './collections/admin-thumbnail';
|
||||
|
||||
const { beforeAll, describe } = test;
|
||||
|
||||
let mediaURL: AdminUrlUtil;
|
||||
let audioURL: AdminUrlUtil;
|
||||
let relationURL: AdminUrlUtil;
|
||||
let adminThumbnailURL: AdminUrlUtil;
|
||||
|
||||
describe('uploads', () => {
|
||||
let page: Page;
|
||||
@@ -26,6 +28,7 @@ describe('uploads', () => {
|
||||
mediaURL = new AdminUrlUtil(serverURL, mediaSlug);
|
||||
audioURL = new AdminUrlUtil(serverURL, audioSlug);
|
||||
relationURL = new AdminUrlUtil(serverURL, relationSlug);
|
||||
adminThumbnailURL = new AdminUrlUtil(serverURL, adminThumbnailSlug);
|
||||
|
||||
const context = await browser.newContext();
|
||||
page = await context.newPage();
|
||||
@@ -144,4 +147,17 @@ describe('uploads', () => {
|
||||
await wait(200);
|
||||
await expect(page.locator('.Toastify')).toContainText('The following field is invalid: audio');
|
||||
});
|
||||
|
||||
test('Should execute adminThumbnail and provide thumbnail when set', async () => {
|
||||
await page.goto(adminThumbnailURL.list);
|
||||
await wait(200);
|
||||
|
||||
// Ensure sure false or null shows generic file svg
|
||||
const genericUploadImage = page.locator('tr.row-1 .thumbnail svg');
|
||||
await expect(genericUploadImage).toBeVisible();
|
||||
|
||||
// Ensure adminThumbnail fn returns correct value based on audio/mp3 mime
|
||||
const audioUploadImage = page.locator('tr.row-2 .thumbnail img');
|
||||
expect(await audioUploadImage.getAttribute('src')).toContain(adminThumbnailSrc);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,35 +1,44 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* 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` "relation".
|
||||
*/
|
||||
export interface Config {
|
||||
collections: {
|
||||
relation: Relation;
|
||||
audio: Audio;
|
||||
'gif-resize': GifResize;
|
||||
media: Media;
|
||||
'media-trim': MediaTrim;
|
||||
'unstored-media': UnstoredMedia;
|
||||
'externally-served-media': ExternallyServedMedia;
|
||||
'uploads-1': Uploads1;
|
||||
'uploads-2': Uploads2;
|
||||
users: User;
|
||||
};
|
||||
globals: {};
|
||||
}
|
||||
export interface Relation {
|
||||
id: string;
|
||||
image?: string | Media;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
sizes: {
|
||||
maintainedAspectRatio: {
|
||||
sizes?: {
|
||||
maintainedAspectRatio?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
@@ -37,7 +46,7 @@ export interface Media {
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
differentFormatFromMainImage: {
|
||||
differentFormatFromMainImage?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
@@ -45,7 +54,7 @@ export interface Media {
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
maintainedImageSize: {
|
||||
maintainedImageSize?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
@@ -53,7 +62,7 @@ export interface Media {
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
maintainedImageSizeWithNewFormat: {
|
||||
maintainedImageSizeWithNewFormat?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
@@ -61,7 +70,7 @@ export interface Media {
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
tablet: {
|
||||
tablet?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
@@ -69,7 +78,7 @@ export interface Media {
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
mobile: {
|
||||
mobile?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
@@ -77,7 +86,7 @@ export interface Media {
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
icon: {
|
||||
icon?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
@@ -86,35 +95,138 @@ export interface Media {
|
||||
filename?: string;
|
||||
};
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "unstored-media".
|
||||
*/
|
||||
export interface UnstoredMedia {
|
||||
export interface Audio {
|
||||
id: string;
|
||||
audio?: string | Media;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface GifResize {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
sizes?: {
|
||||
small?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
large?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface MediaTrim {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
sizes?: {
|
||||
trimNumber?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
trimString?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
trimOptions?: {
|
||||
url?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
filename?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface UnstoredMedia {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
export interface ExternallyServedMedia {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
export interface Uploads1 {
|
||||
id: string;
|
||||
media?: string | Uploads2;
|
||||
richText?: {
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
export interface Uploads2 {
|
||||
id: string;
|
||||
title?: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string;
|
||||
filename?: string;
|
||||
mimeType?: string;
|
||||
filesize?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
email?: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpiration?: string;
|
||||
salt?: string;
|
||||
hash?: string;
|
||||
loginAttempts?: number;
|
||||
lockUntil?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { buildConfig } from '../buildConfig';
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults';
|
||||
import AutosavePosts from './collections/Autosave';
|
||||
import DraftPosts from './collections/Drafts';
|
||||
import AutosaveGlobal from './globals/Autosave';
|
||||
@@ -7,7 +7,7 @@ import DraftGlobal from './globals/Draft';
|
||||
import VersionPosts from './collections/Versions';
|
||||
import { draftSlug } from './shared';
|
||||
|
||||
export default buildConfig({
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [
|
||||
AutosavePosts,
|
||||
DraftPosts,
|
||||
|
||||
Reference in New Issue
Block a user