feat: async plugins (#2030)

* feat: async plugins

* wip: async config

* fix: async config
This commit is contained in:
Jacob Fletcher
2023-02-13 10:46:55 -05:00
committed by GitHub
parent 11532857d2
commit 9f30553813
25 changed files with 218 additions and 119 deletions

View File

@@ -3,10 +3,29 @@ import { SanitizedConfig } from '../../../../config/types';
const Context = createContext<SanitizedConfig>({} as SanitizedConfig); const Context = createContext<SanitizedConfig>({} as SanitizedConfig);
export const ConfigProvider: React.FC<{config: SanitizedConfig, children: React.ReactNode}> = ({ children, config }) => ( export const ConfigProvider: React.FC<{config: SanitizedConfig, children: React.ReactNode}> = ({ children, config: incomingConfig }) => {
<Context.Provider value={config}> const [config, setConfig] = React.useState<SanitizedConfig>();
{children} const hasAwaited = React.useRef(false);
</Context.Provider>
); React.useEffect(() => {
if (incomingConfig && !hasAwaited.current) {
hasAwaited.current = true;
const awaitConfig = async () => {
const resolvedConfig = await incomingConfig;
setConfig(resolvedConfig);
};
awaitConfig();
}
}, [incomingConfig]);
if (!config) return null;
return (
<Context.Provider value={config}>
{children}
</Context.Provider>
);
};
export const useConfig = (): SanitizedConfig => useContext(Context); export const useConfig = (): SanitizedConfig => useContext(Context);

View File

@@ -9,7 +9,7 @@ const rawConfigPath = findConfig();
export const build = async (): Promise<void> => { export const build = async (): Promise<void> => {
try { try {
const config = loadConfig(); const config = await loadConfig();
const webpackProdConfig = getWebpackProdConfig(config); const webpackProdConfig = getWebpackProdConfig(config);

View File

@@ -7,7 +7,7 @@ import payload from '..';
export async function generateGraphQLSchema(): Promise<void> { export async function generateGraphQLSchema(): Promise<void> {
const logger = Logger(); const logger = Logger();
const config = loadConfig(); const config = await loadConfig();
await payload.init({ await payload.init({
secret: '--unused--', secret: '--unused--',

View File

@@ -32,9 +32,9 @@ function configToJsonSchema(config: SanitizedConfig): JSONSchema4 {
}; };
} }
export function generateTypes(): void { export async function generateTypes(): Promise<void> {
const logger = Logger(); const logger = Logger();
const config = loadConfig(); const config = await loadConfig();
const outputFile = process.env.PAYLOAD_TS_OUTPUT_PATH || config.typescript.outputFile; const outputFile = process.env.PAYLOAD_TS_OUTPUT_PATH || config.typescript.outputFile;
logger.info('Compiling TS types for Collections and Globals...'); logger.info('Compiling TS types for Collections and Globals...');

View File

@@ -8,14 +8,17 @@ import sanitize from './sanitize';
* @param config Payload Config * @param config Payload Config
* @returns Built and sanitized Payload Config * @returns Built and sanitized Payload Config
*/ */
export function buildConfig(config: Config): SanitizedConfig { export async function buildConfig(config: Config): Promise<SanitizedConfig> {
if (Array.isArray(config.plugins)) { if (Array.isArray(config.plugins)) {
const configWithPlugins = config.plugins.reduce( const configAfterPlugins = await config.plugins.reduce(
(updatedConfig, plugin) => plugin(updatedConfig), async (acc, plugin) => {
config, const configAfterPlugin = await acc;
return plugin(configAfterPlugin);
},
Promise.resolve(config),
); );
const sanitizedConfig = sanitize(configWithPlugins); const sanitizedConfig = sanitize(configAfterPlugins);
return sanitizedConfig; return sanitizedConfig;
} }

View File

@@ -9,7 +9,7 @@ import findConfig from './find';
import validate from './validate'; import validate from './validate';
import { clientFiles } from './clientFiles'; import { clientFiles } from './clientFiles';
const loadConfig = (logger?: pino.Logger): SanitizedConfig => { const loadConfig = async (logger?: pino.Logger): Promise<SanitizedConfig> => {
const localLogger = logger ?? Logger(); const localLogger = logger ?? Logger();
const configPath = findConfig(); const configPath = findConfig();
@@ -19,18 +19,18 @@ const loadConfig = (logger?: pino.Logger): SanitizedConfig => {
}); });
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
let config = require(configPath); const configPromise = require(configPath);
if (config.default) config = config.default; let config = await configPromise;
let validatedConfig = config; if (config.default) config = await config.default;
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
validatedConfig = validate(config, localLogger); config = await validate(config, localLogger);
} }
return { return {
...validatedConfig, ...config,
paths: { paths: {
configDir: path.dirname(configPath), configDir: path.dirname(configPath),
config: configPath, config: configPath,

View File

@@ -27,7 +27,7 @@ type Email = {
}; };
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
export type Plugin = (config: Config) => Config; export type Plugin = (config: Config) => Promise<Config> | Config;
type GeneratePreviewURLOptions = { type GeneratePreviewURLOptions = {
locale: string; locale: string;

View File

@@ -32,7 +32,7 @@ const validateFields = (context: string, entity: SanitizedCollectionConfig | San
return errors; return errors;
}; };
const validateCollections = (collections: SanitizedCollectionConfig[]): string[] => { const validateCollections = async (collections: SanitizedCollectionConfig[]): Promise<string[]> => {
const errors: string[] = []; const errors: string[] = [];
collections.forEach((collection) => { collections.forEach((collection) => {
const result = collectionSchema.validate(collection, { abortEarly: false }); const result = collectionSchema.validate(collection, { abortEarly: false });
@@ -62,13 +62,13 @@ const validateGlobals = (globals: SanitizedGlobalConfig[]): string[] => {
return errors; return errors;
}; };
const validateSchema = (config: SanitizedConfig, logger: Logger): SanitizedConfig => { const validateSchema = async (config: SanitizedConfig, logger: Logger): Promise<SanitizedConfig> => {
const result = schema.validate(config, { const result = schema.validate(config, {
abortEarly: false, abortEarly: false,
}); });
const nestedErrors = [ const nestedErrors = [
...validateCollections(config.collections), ...await validateCollections(config.collections),
...validateGlobals(config.globals), ...validateGlobals(config.globals),
]; ];

View File

@@ -181,7 +181,7 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
const loadConfig = require('./config/load').default; const loadConfig = require('./config/load').default;
this.config = loadConfig(this.logger); this.config = await loadConfig(this.logger);
} }
// Configure email service // Configure email service

View File

@@ -76,7 +76,7 @@ describe('access control', () => {
let existingDoc: ReadOnlyCollection; let existingDoc: ReadOnlyCollection;
beforeAll(async () => { beforeAll(async () => {
existingDoc = await payload.create<ReadOnlyCollection>({ existingDoc = await payload.create({
collection: readOnlySlug, collection: readOnlySlug,
data: { data: {
name: 'name', name: 'name',
@@ -114,7 +114,7 @@ describe('access control', () => {
let existingDoc: ReadOnlyCollection; let existingDoc: ReadOnlyCollection;
beforeAll(async () => { beforeAll(async () => {
existingDoc = await payload.create<ReadOnlyCollection>({ existingDoc = await payload.create({
collection: readOnlySlug, collection: readOnlySlug,
data: { data: {
name: 'name', name: 'name',
@@ -162,7 +162,7 @@ describe('access control', () => {
let existingDoc: RestrictedVersion; let existingDoc: RestrictedVersion;
beforeAll(async () => { beforeAll(async () => {
existingDoc = await payload.create<RestrictedVersion>({ existingDoc = await payload.create({
collection: restrictedVersionsSlug, collection: restrictedVersionsSlug,
data: { data: {
name: 'name', name: 'name',
@@ -172,7 +172,7 @@ describe('access control', () => {
test('versions sidebar should not show', async () => { test('versions sidebar should not show', async () => {
await page.goto(restrictedVersionsUrl.edit(existingDoc.id)); await page.goto(restrictedVersionsUrl.edit(existingDoc.id));
await expect(page.locator('.versions-count')).not.toBeVisible(); await expect(page.locator('.versions-count')).toBeHidden();
}); });
}); });

View File

@@ -5,7 +5,7 @@ import { Forbidden } from '../../src/errors';
import type { PayloadRequest } from '../../src/types'; import type { PayloadRequest } from '../../src/types';
import { initPayloadTest } from '../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import { relyOnRequestHeadersSlug, requestHeaders, restrictedSlug, siblingDataSlug, slug } from './config'; import { relyOnRequestHeadersSlug, requestHeaders, restrictedSlug, siblingDataSlug, slug } from './config';
import type { Restricted, Post, SiblingDatum, RelyOnRequestHeader } from './payload-types'; import type { Restricted, Post, RelyOnRequestHeader } from './payload-types';
import { firstArrayText, secondArrayText } from './shared'; import { firstArrayText, secondArrayText } from './shared';
describe('Access Control', () => { describe('Access Control', () => {
@@ -17,12 +17,12 @@ describe('Access Control', () => {
}); });
beforeEach(async () => { beforeEach(async () => {
post1 = await payload.create<Post>({ post1 = await payload.create({
collection: slug, collection: slug,
data: { name: 'name' }, data: { name: 'name' },
}); });
restricted = await payload.create<Restricted>({ restricted = await payload.create({
collection: restrictedSlug, collection: restrictedSlug,
data: { name: 'restricted' }, data: { name: 'restricted' },
}); });
@@ -37,7 +37,7 @@ describe('Access Control', () => {
it.todo('should properly prevent / allow public users from reading a restricted field'); it.todo('should properly prevent / allow public users from reading a restricted field');
it('should be able to restrict access based upon siblingData', async () => { it('should be able to restrict access based upon siblingData', async () => {
const { id } = await payload.create<SiblingDatum>({ const { id } = await payload.create({
collection: siblingDataSlug, collection: siblingDataSlug,
data: { data: {
array: [ array: [
@@ -53,7 +53,7 @@ describe('Access Control', () => {
}, },
}); });
const doc = await payload.findByID<SiblingDatum>({ const doc = await payload.findByID({
id, id,
collection: siblingDataSlug, collection: siblingDataSlug,
overrideAccess: false, overrideAccess: false,
@@ -64,7 +64,7 @@ describe('Access Control', () => {
expect(doc.array?.[1].text).toBeUndefined(); expect(doc.array?.[1].text).toBeUndefined();
// Retrieve with default of overriding access // Retrieve with default of overriding access
const docOverride = await payload.findByID<SiblingDatum>({ const docOverride = await payload.findByID({
id, id,
collection: siblingDataSlug, collection: siblingDataSlug,
}); });
@@ -150,7 +150,7 @@ describe('Access Control', () => {
describe('Override Access', () => { describe('Override Access', () => {
describe('Fields', () => { describe('Fields', () => {
it('should allow overrideAccess: false', async () => { it('should allow overrideAccess: false', async () => {
const req = async () => payload.update<Post>({ const req = async () => payload.update({
collection: slug, collection: slug,
id: post1.id, id: post1.id,
data: { restrictedField: restricted.id }, data: { restrictedField: restricted.id },
@@ -161,7 +161,7 @@ describe('Access Control', () => {
}); });
it('should allow overrideAccess: true', async () => { it('should allow overrideAccess: true', async () => {
const doc = await payload.update<Post>({ const doc = await payload.update({
collection: slug, collection: slug,
id: post1.id, id: post1.id,
data: { restrictedField: restricted.id }, data: { restrictedField: restricted.id },
@@ -172,7 +172,7 @@ describe('Access Control', () => {
}); });
it('should allow overrideAccess by default', async () => { it('should allow overrideAccess by default', async () => {
const doc = await payload.update<Post>({ const doc = await payload.update({
collection: slug, collection: slug,
id: post1.id, id: post1.id,
data: { restrictedField: restricted.id }, data: { restrictedField: restricted.id },
@@ -221,7 +221,7 @@ describe('Access Control', () => {
}); });
async function createDoc<Collection>(data: Partial<Collection>, overrideSlug = slug, options?: Partial<CreateOptions<Collection>>): Promise<Collection> { async function createDoc<Collection>(data: Partial<Collection>, overrideSlug = slug, options?: Partial<CreateOptions<Collection>>): Promise<Collection> {
return payload.create<Collection>({ return payload.create({
...options, ...options,
collection: overrideSlug, collection: overrideSlug,
data: data ?? {}, data: data ?? {},

View File

@@ -1,13 +1,14 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { initPayloadTest } from '../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import payload from '../../src'; import payload from '../../src';
import config from './config'; import configPromise from './config';
import type { Array as ArrayCollection } from './payload-types';
const collection = config.collections[0]?.slug; let collection: string;
describe('array-update', () => { describe('array-update', () => {
beforeAll(async () => { beforeAll(async () => {
const config = await configPromise;
collection = config.collections[0]?.slug;
await initPayloadTest({ __dirname }); await initPayloadTest({ __dirname });
}); });
@@ -45,7 +46,7 @@ describe('array-update', () => {
required: updatedText, required: updatedText,
}; };
const updatedDoc = await payload.update<ArrayCollection>({ const updatedDoc = await payload.update({
id: doc.id, id: doc.id,
collection, collection,
data: { data: {
@@ -67,7 +68,7 @@ describe('array-update', () => {
optional: 'optional test', optional: 'optional test',
}; };
const doc = await payload.create<ArrayCollection>({ const doc = await payload.create({
collection, collection,
data: { data: {
array: [ array: [
@@ -80,7 +81,7 @@ describe('array-update', () => {
}, },
}); });
const updatedDoc = await payload.update<ArrayCollection>({ const updatedDoc = await payload.update({
id: doc.id, id: doc.id,
collection, collection,
data: { data: {

View File

@@ -1,7 +1,7 @@
import { Config, SanitizedConfig } from '../src/config/types'; import { Config, SanitizedConfig } from '../src/config/types';
import { buildConfig as buildPayloadConfig } from '../src/config/build'; import { buildConfig as buildPayloadConfig } from '../src/config/build';
export function buildConfig(config?: Partial<Config>): SanitizedConfig { export function buildConfig(config?: Partial<Config>): Promise<SanitizedConfig> {
const [name] = process.argv.slice(2); const [name] = process.argv.slice(2);
const baseConfig: Config = { const baseConfig: Config = {
telemetry: false, telemetry: false,

View File

@@ -1,7 +1,6 @@
import type { CollectionConfig } from '../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
import type { Post } from './payload-types';
export interface Relation { export interface Relation {
id: string; id: string;
@@ -88,20 +87,20 @@ export default buildConfig({
}, },
}); });
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'post1', title: 'post1',
}, },
}); });
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'post2', title: 'post2',
}, },
}); });
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'with-description', title: 'with-description',
@@ -109,14 +108,14 @@ export default buildConfig({
}, },
}); });
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'numPost1', title: 'numPost1',
number: 1, number: 1,
}, },
}); });
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'numPost2', title: 'numPost2',
@@ -124,13 +123,13 @@ export default buildConfig({
}, },
}); });
const rel1 = await payload.create<Relation>({ const rel1 = await payload.create({
collection: relationSlug, collection: relationSlug,
data: { data: {
name: 'name', name: 'name',
}, },
}); });
const rel2 = await payload.create<Relation>({ const rel2 = await payload.create({
collection: relationSlug, collection: relationSlug,
data: { data: {
name: 'name2', name: 'name2',
@@ -138,14 +137,14 @@ export default buildConfig({
}); });
// Relation - hasMany // Relation - hasMany
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to hasMany', title: 'rel to hasMany',
relationHasManyField: rel1.id, relationHasManyField: rel1.id,
}, },
}); });
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to hasMany 2', title: 'rel to hasMany 2',
@@ -154,7 +153,7 @@ export default buildConfig({
}); });
// Relation - relationTo multi // Relation - relationTo multi
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to multi', title: 'rel to multi',
@@ -166,7 +165,7 @@ export default buildConfig({
}); });
// Relation - relationTo multi hasMany // Relation - relationTo multi hasMany
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to multi hasMany', title: 'rel to multi hasMany',

View File

@@ -1,11 +1,11 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { GraphQLClient } from 'graphql-request'; import { GraphQLClient } from 'graphql-request';
import { initPayloadTest } from '../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import config from './config'; import configPromise from './config';
import payload from '../../src'; import payload from '../../src';
import type { Post } from './payload-types'; import type { Post } from './payload-types';
const slug = config.collections[0]?.slug; let slug = '';
const title = 'title'; const title = 'title';
let client: GraphQLClient; let client: GraphQLClient;
@@ -13,6 +13,8 @@ let client: GraphQLClient;
describe('collections-graphql', () => { describe('collections-graphql', () => {
beforeAll(async () => { beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } }); const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
const config = await configPromise;
slug = config.collections[0]?.slug;
const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`; const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`;
client = new GraphQLClient(url); client = new GraphQLClient(url);
}); });
@@ -354,7 +356,7 @@ describe('collections-graphql', () => {
}); });
async function createPost(overrides?: Partial<Post>) { async function createPost(overrides?: Partial<Post>) {
const doc = await payload.create<Post>({ const doc = await payload.create({
collection: slug, collection: slug,
data: { title: 'title', ...overrides }, data: { title: 'title', ...overrides },
}); });

View File

@@ -1,7 +1,6 @@
import type { CollectionConfig } from '../../src/collections/config/types'; import type { CollectionConfig } from '../../src/collections/config/types';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
import { buildConfig } from '../buildConfig'; import { buildConfig } from '../buildConfig';
import type { Post } from './payload-types';
export interface Relation { export interface Relation {
id: string; id: string;
@@ -135,13 +134,13 @@ export default buildConfig({
}, },
}); });
const rel1 = await payload.create<Relation>({ const rel1 = await payload.create({
collection: relationSlug, collection: relationSlug,
data: { data: {
name: 'name', name: 'name',
}, },
}); });
const rel2 = await payload.create<Relation>({ const rel2 = await payload.create({
collection: relationSlug, collection: relationSlug,
data: { data: {
name: 'name2', name: 'name2',
@@ -156,14 +155,14 @@ export default buildConfig({
}); });
// Relation - hasMany // Relation - hasMany
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to hasMany', title: 'rel to hasMany',
relationHasManyField: rel1.id, relationHasManyField: rel1.id,
}, },
}); });
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to hasMany 2', title: 'rel to hasMany 2',
@@ -172,7 +171,7 @@ export default buildConfig({
}); });
// Relation - relationTo multi // Relation - relationTo multi
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to multi', title: 'rel to multi',
@@ -184,7 +183,7 @@ export default buildConfig({
}); });
// Relation - relationTo multi hasMany // Relation - relationTo multi hasMany
await payload.create<Post>({ await payload.create({
collection: slug, collection: slug,
data: { data: {
title: 'rel to multi hasMany', title: 'rel to multi hasMany',

View File

@@ -1,10 +1,9 @@
import type { IndexDirection, IndexOptions } from 'mongoose'; import type { IndexDirection, IndexOptions } from 'mongoose';
import { initPayloadTest } from '../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import { RESTClient } from '../helpers/rest'; import { RESTClient } from '../helpers/rest';
import config from '../uploads/config'; import configPromise from '../uploads/config';
import payload from '../../src'; import payload from '../../src';
import { pointDoc } from './collections/Point'; import { pointDoc } from './collections/Point';
import type { ArrayField, GroupField, TabsField } from './payload-types';
import { arrayFieldsSlug, arrayDefaultValue, arrayDoc } from './collections/Array'; import { arrayFieldsSlug, arrayDefaultValue, arrayDoc } from './collections/Array';
import { groupFieldsSlug, groupDefaultChild, groupDefaultValue, groupDoc } from './collections/Group'; import { groupFieldsSlug, groupDefaultChild, groupDefaultValue, groupDoc } from './collections/Group';
import { defaultText } from './collections/Text'; import { defaultText } from './collections/Text';
@@ -17,6 +16,7 @@ let client;
describe('Fields', () => { describe('Fields', () => {
beforeAll(async () => { beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } }); const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
const config = await configPromise;
client = new RESTClient(config, { serverURL, defaultSlug: 'point-fields' }); client = new RESTClient(config, { serverURL, defaultSlug: 'point-fields' });
await client.login(); await client.login();
}); });
@@ -268,14 +268,14 @@ describe('Fields', () => {
const collection = arrayFieldsSlug; const collection = arrayFieldsSlug;
beforeAll(async () => { beforeAll(async () => {
doc = await payload.create<ArrayField>({ doc = await payload.create({
collection, collection,
data: {}, data: {},
}); });
}); });
it('should return undefined arrays when no data present', async () => { it('should return undefined arrays when no data present', async () => {
const document = await payload.create<ArrayField>({ const document = await payload.create({
collection: arrayFieldsSlug, collection: arrayFieldsSlug,
data: arrayDoc, data: arrayDoc,
}); });
@@ -284,7 +284,7 @@ describe('Fields', () => {
}); });
it('should create with ids and nested ids', async () => { it('should create with ids and nested ids', async () => {
const docWithIDs = await payload.create<GroupField>({ const docWithIDs = await payload.create({
collection: groupFieldsSlug, collection: groupFieldsSlug,
data: groupDoc, data: groupDoc,
}); });
@@ -300,14 +300,14 @@ describe('Fields', () => {
const localized = [{ text: 'unique' }]; const localized = [{ text: 'unique' }];
const enText = 'english'; const enText = 'english';
const esText = 'spanish'; const esText = 'spanish';
const { id } = await payload.create<ArrayField>({ const { id } = await payload.create({
collection, collection,
data: { data: {
localized, localized,
}, },
}); });
const enDoc = await payload.update<ArrayField>({ const enDoc = await payload.update({
collection, collection,
id, id,
locale: 'en', locale: 'en',
@@ -316,7 +316,7 @@ describe('Fields', () => {
}, },
}); });
const esDoc = await payload.update<ArrayField>({ const esDoc = await payload.update({
collection, collection,
id, id,
locale: 'es', locale: 'es',
@@ -342,7 +342,7 @@ describe('Fields', () => {
let document; let document;
beforeAll(async () => { beforeAll(async () => {
document = await payload.create<GroupField>({ document = await payload.create({
collection: groupFieldsSlug, collection: groupFieldsSlug,
data: {}, data: {},
}); });
@@ -358,7 +358,7 @@ describe('Fields', () => {
let document; let document;
beforeAll(async () => { beforeAll(async () => {
document = await payload.create<TabsField>({ document = await payload.create({
collection: tabsSlug, collection: tabsSlug,
data: tabsDoc, data: tabsDoc,
}); });
@@ -395,7 +395,7 @@ describe('Fields', () => {
}); });
it('should allow hooks on a named tab', async () => { it('should allow hooks on a named tab', async () => {
const newDocument = await payload.create<TabsField>({ const newDocument = await payload.create({
collection: tabsSlug, collection: tabsSlug,
data: tabsDoc, data: tabsDoc,
}); });
@@ -406,7 +406,7 @@ describe('Fields', () => {
}); });
it('should return empty object for groups when no data present', async () => { it('should return empty object for groups when no data present', async () => {
const doc = await payload.create<GroupField>({ const doc = await payload.create({
collection: groupFieldsSlug, collection: groupFieldsSlug,
data: groupDoc, data: groupDoc,
}); });

View File

@@ -1,6 +1,6 @@
import { GraphQLClient } from 'graphql-request'; import { GraphQLClient } from 'graphql-request';
import { initPayloadTest } from '../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import config, { arraySlug, englishLocale, slug, spanishLocale } from './config'; import configPromise, { arraySlug, englishLocale, slug, spanishLocale } from './config';
import payload from '../../src'; import payload from '../../src';
import { RESTClient } from '../helpers/rest'; import { RESTClient } from '../helpers/rest';
@@ -13,6 +13,7 @@ describe('globals', () => {
describe('REST', () => { describe('REST', () => {
let client: RESTClient; let client: RESTClient;
beforeAll(async () => { beforeAll(async () => {
const config = await configPromise;
client = new RESTClient(config, { serverURL, defaultSlug: slug }); client = new RESTClient(config, { serverURL, defaultSlug: slug });
}); });
it('should create', async () => { it('should create', async () => {
@@ -135,6 +136,7 @@ describe('globals', () => {
describe('graphql', () => { describe('graphql', () => {
let client: GraphQLClient; let client: GraphQLClient;
beforeAll(async () => { beforeAll(async () => {
const config = await configPromise;
const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`; const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`;
client = new GraphQLClient(url); client = new GraphQLClient(url);
}); });

View File

@@ -258,7 +258,7 @@ export class RESTClient {
return { status, doc: result }; return { status, doc: result };
} }
async endpoint<T = any>(path: string, method = 'get', params = undefined): Promise<{ status: number, data: T }> { async endpoint<T = any>(path: string, method = 'get', params: any = undefined): Promise<{ status: number, data: T }> {
const response = await fetch(`${this.serverURL}${path}`, { const response = await fetch(`${this.serverURL}${path}`, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@@ -10,7 +10,7 @@ import type {
GlobalArray, GlobalArray,
} from './payload-types'; } from './payload-types';
import type { LocalizedPostAllLocale } from './config'; import type { LocalizedPostAllLocale } from './config';
import config, { relationshipLocalizedSlug, slug, withLocalizedRelSlug, withRequiredLocalizedFields } from './config'; import configPromise, { relationshipLocalizedSlug, slug, withLocalizedRelSlug, withRequiredLocalizedFields } from './config';
import { import {
defaultLocale, defaultLocale,
englishTitle, englishTitle,
@@ -23,8 +23,10 @@ import {
} from './shared'; } from './shared';
import type { Where } from '../../src/types'; import type { Where } from '../../src/types';
import { arrayCollectionSlug } from './collections/Array'; import { arrayCollectionSlug } from './collections/Array';
import type { Config } from '../../src/config/types';
const collection = slug; const collection = slug;
let config: Config;
let serverURL; let serverURL;
@@ -34,6 +36,7 @@ describe('Localization', () => {
beforeAll(async () => { beforeAll(async () => {
({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } })); ({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } }));
config = await configPromise;
post1 = await payload.create({ post1 = await payload.create({
collection, collection,
@@ -49,7 +52,7 @@ describe('Localization', () => {
}, },
}); });
await payload.update<LocalizedPost>({ await payload.update({
collection, collection,
id: postWithLocalizedData.id, id: postWithLocalizedData.id,
locale: spanishLocale, locale: spanishLocale,
@@ -67,7 +70,7 @@ describe('Localization', () => {
describe('localized text', () => { describe('localized text', () => {
it('create english', async () => { it('create english', async () => {
const allDocs = await payload.find<LocalizedPost>({ const allDocs = await payload.find({
collection, collection,
where: { where: {
title: { equals: post1.title }, title: { equals: post1.title },
@@ -77,7 +80,7 @@ describe('Localization', () => {
}); });
it('add spanish translation', async () => { it('add spanish translation', async () => {
const updated = await payload.update<LocalizedPost>({ const updated = await payload.update({
collection, collection,
id: post1.id, id: post1.id,
locale: spanishLocale, locale: spanishLocale,
@@ -88,7 +91,7 @@ describe('Localization', () => {
expect(updated.title).toEqual(spanishTitle); expect(updated.title).toEqual(spanishTitle);
const localized = await payload.findByID<LocalizedPostAllLocale>({ const localized = await payload.findByID({
collection, collection,
id: post1.id, id: post1.id,
locale: 'all', locale: 'all',
@@ -99,7 +102,7 @@ describe('Localization', () => {
}); });
it('should fallback to english translation when empty', async () => { it('should fallback to english translation when empty', async () => {
const updated = await payload.update<LocalizedPost>({ const updated = await payload.update({
collection, collection,
id: post1.id, id: post1.id,
locale: spanishLocale, locale: spanishLocale,
@@ -110,7 +113,7 @@ describe('Localization', () => {
expect(updated.title).toEqual(englishTitle); expect(updated.title).toEqual(englishTitle);
const localizedFallback = await payload.findByID<LocalizedPostAllLocale>({ const localizedFallback = await payload.findByID({
collection, collection,
id: post1.id, id: post1.id,
locale: 'all', locale: 'all',
@@ -123,14 +126,14 @@ describe('Localization', () => {
describe('querying', () => { describe('querying', () => {
let localizedPost: LocalizedPost; let localizedPost: LocalizedPost;
beforeEach(async () => { beforeEach(async () => {
const { id } = await payload.create<LocalizedPost>({ const { id } = await payload.create({
collection, collection,
data: { data: {
title: englishTitle, title: englishTitle,
}, },
}); });
localizedPost = await payload.update<LocalizedPost>({ localizedPost = await payload.update({
collection, collection,
id, id,
locale: spanishLocale, locale: spanishLocale,
@@ -170,7 +173,7 @@ describe('Localization', () => {
}); });
it('all locales', async () => { it('all locales', async () => {
const localized = await payload.findByID<LocalizedPostAllLocale>({ const localized = await payload.findByID({
collection, collection,
locale: 'all', locale: 'all',
id: localizedPost.id, id: localizedPost.id,
@@ -181,7 +184,7 @@ describe('Localization', () => {
}); });
it('by localized field value - default locale', async () => { it('by localized field value - default locale', async () => {
const result = await payload.find<LocalizedPost>({ const result = await payload.find({
collection, collection,
where: { where: {
title: { title: {
@@ -194,7 +197,7 @@ describe('Localization', () => {
}); });
it('by localized field value - alternate locale', async () => { it('by localized field value - alternate locale', async () => {
const result = await payload.find<LocalizedPost>({ const result = await payload.find({
collection, collection,
locale: spanishLocale, locale: spanishLocale,
where: { where: {
@@ -208,7 +211,7 @@ describe('Localization', () => {
}); });
it('by localized field value - opposite locale???', async () => { it('by localized field value - opposite locale???', async () => {
const result = await payload.find<LocalizedPost>({ const result = await payload.find({
collection, collection,
locale: 'all', locale: 'all',
where: { where: {
@@ -258,7 +261,7 @@ describe('Localization', () => {
describe('regular relationship', () => { describe('regular relationship', () => {
it('can query localized relationship', async () => { it('can query localized relationship', async () => {
const result = await payload.find<WithLocalizedRelationship>({ const result = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
where: { where: {
'localizedRelationship.title': { 'localizedRelationship.title': {
@@ -271,7 +274,7 @@ describe('Localization', () => {
}); });
it('specific locale', async () => { it('specific locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({ const result = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
locale: spanishLocale, locale: spanishLocale,
where: { where: {
@@ -285,7 +288,7 @@ describe('Localization', () => {
}); });
it('all locales', async () => { it('all locales', async () => {
const result = await payload.find<WithLocalizedRelationship>({ const result = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
locale: 'all', locale: 'all',
where: { where: {
@@ -300,7 +303,7 @@ describe('Localization', () => {
it('populates relationships with all locales', async () => { it('populates relationships with all locales', async () => {
// the relationship fields themselves are localized on this collection // the relationship fields themselves are localized on this collection
const result = await payload.find<RelationshipLocalized>({ const result = await payload.find({
collection: relationshipLocalizedSlug, collection: relationshipLocalizedSlug,
locale: 'all', locale: 'all',
depth: 1, depth: 1,
@@ -314,7 +317,7 @@ describe('Localization', () => {
describe('relationship - hasMany', () => { describe('relationship - hasMany', () => {
it('default locale', async () => { it('default locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({ const result = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
where: { where: {
'localizedRelationHasManyField.title': { 'localizedRelationHasManyField.title': {
@@ -326,7 +329,7 @@ describe('Localization', () => {
expect(result.docs[0].id).toEqual(withRelationship.id); expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship // Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({ const result2 = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
where: { where: {
'localizedRelationHasManyField.title': { 'localizedRelationHasManyField.title': {
@@ -339,7 +342,7 @@ describe('Localization', () => {
}); });
it('specific locale', async () => { it('specific locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({ const result = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
locale: spanishLocale, locale: spanishLocale,
where: { where: {
@@ -352,7 +355,7 @@ describe('Localization', () => {
expect(result.docs[0].id).toEqual(withRelationship.id); expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship // Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({ const result2 = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
locale: spanishLocale, locale: spanishLocale,
where: { where: {
@@ -366,7 +369,7 @@ describe('Localization', () => {
}); });
it('relationship population uses locale', async () => { it('relationship population uses locale', async () => {
const result = await payload.findByID<WithLocalizedRelationship>({ const result = await payload.findByID({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
depth: 1, depth: 1,
id: withRelationship.id, id: withRelationship.id,
@@ -377,7 +380,7 @@ describe('Localization', () => {
it('all locales', async () => { it('all locales', async () => {
const queryRelation = (where: Where) => { const queryRelation = (where: Where) => {
return payload.find<WithLocalizedRelationship>({ return payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
locale: 'all', locale: 'all',
where, where,
@@ -423,7 +426,7 @@ describe('Localization', () => {
describe('relationTo multi', () => { describe('relationTo multi', () => {
it('by id', async () => { it('by id', async () => {
const result = await payload.find<WithLocalizedRelationship>({ const result = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
where: { where: {
'localizedRelationMultiRelationTo.value': { 'localizedRelationMultiRelationTo.value': {
@@ -435,7 +438,7 @@ describe('Localization', () => {
expect(result.docs[0].id).toEqual(withRelationship.id); expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship // Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({ const result2 = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
locale: spanishLocale, locale: spanishLocale,
where: { where: {
@@ -451,7 +454,7 @@ describe('Localization', () => {
describe('relationTo multi hasMany', () => { describe('relationTo multi hasMany', () => {
it('by id', async () => { it('by id', async () => {
const result = await payload.find<WithLocalizedRelationship>({ const result = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
where: { where: {
'localizedRelationMultiRelationToHasMany.value': { 'localizedRelationMultiRelationToHasMany.value': {
@@ -463,7 +466,7 @@ describe('Localization', () => {
expect(result.docs[0].id).toEqual(withRelationship.id); expect(result.docs[0].id).toEqual(withRelationship.id);
// First relationship - spanish locale // First relationship - spanish locale
const result2 = await payload.find<WithLocalizedRelationship>({ const result2 = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
locale: spanishLocale, locale: spanishLocale,
where: { where: {
@@ -476,7 +479,7 @@ describe('Localization', () => {
expect(result2.docs[0].id).toEqual(withRelationship.id); expect(result2.docs[0].id).toEqual(withRelationship.id);
// Second relationship // Second relationship
const result3 = await payload.find<WithLocalizedRelationship>({ const result3 = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
where: { where: {
'localizedRelationMultiRelationToHasMany.value': { 'localizedRelationMultiRelationToHasMany.value': {
@@ -488,7 +491,7 @@ describe('Localization', () => {
expect(result3.docs[0].id).toEqual(withRelationship.id); expect(result3.docs[0].id).toEqual(withRelationship.id);
// Second relationship - spanish locale // Second relationship - spanish locale
const result4 = await payload.find<WithLocalizedRelationship>({ const result4 = await payload.find({
collection: withLocalizedRelSlug, collection: withLocalizedRelSlug,
where: { where: {
'localizedRelationMultiRelationToHasMany.value': { 'localizedRelationMultiRelationToHasMany.value': {
@@ -504,7 +507,7 @@ describe('Localization', () => {
describe('Localized - arrays with nested localized fields', () => { describe('Localized - arrays with nested localized fields', () => {
it('should allow moving rows and retain existing row locale data', async () => { it('should allow moving rows and retain existing row locale data', async () => {
const globalArray = await payload.findGlobal<GlobalArray>({ const globalArray = await payload.findGlobal({
slug: 'global-array', slug: 'global-array',
}); });
@@ -553,7 +556,7 @@ describe('Localization', () => {
}, },
}); });
const updatedDoc = await payload.update<LocalizedRequired>({ const updatedDoc = await payload.update({
collection: withRequiredLocalizedFields, collection: withRequiredLocalizedFields,
id: newDoc.id, id: newDoc.id,
data: { data: {
@@ -577,7 +580,7 @@ describe('Localization', () => {
let token; let token;
it('should allow user to login and retrieve populated localized field', async () => { it('should allow user to login and retrieve populated localized field', async () => {
const url = `${serverURL}${config.routes.api}${config.routes.graphQL}?locale=en`; const url = `${serverURL}${config?.routes?.api}${config?.routes?.graphQL}?locale=en`;
const client = new GraphQLClient(url); const client = new GraphQLClient(url);
const query = `mutation { const query = `mutation {
@@ -602,7 +605,7 @@ describe('Localization', () => {
it('should allow retrieval of populated localized fields within meUser', async () => { it('should allow retrieval of populated localized fields within meUser', async () => {
// Defining locale=en in graphQL string should not break JWT strategy // Defining locale=en in graphQL string should not break JWT strategy
const url = `${serverURL}${config.routes.api}${config.routes.graphQL}?locale=en`; const url = `${serverURL}${config?.routes?.api}${config?.routes?.graphQL}?locale=en`;
const client = new GraphQLClient(url); const client = new GraphQLClient(url);
const query = `query { const query = `query {
@@ -626,7 +629,7 @@ describe('Localization', () => {
}); });
it('should create and update collections', async () => { it('should create and update collections', async () => {
const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`; const url = `${serverURL}${config?.routes?.api}${config?.routes?.graphQL}`;
const client = new GraphQLClient(url); const client = new GraphQLClient(url);
const create = `mutation { const create = `mutation {
@@ -749,14 +752,14 @@ async function createLocalizedPost(data: {
[spanishLocale]: string; [spanishLocale]: string;
}; };
}): Promise<LocalizedPost> { }): Promise<LocalizedPost> {
const localizedRelation = await payload.create<LocalizedPost>({ const localizedRelation = await payload.create({
collection, collection,
data: { data: {
title: data.title.en, title: data.title.en,
}, },
}); });
await payload.update<LocalizedPost>({ await payload.update({
collection, collection,
id: localizedRelation.id, id: localizedRelation.id,
locale: spanishLocale, locale: spanishLocale,

1
test/plugins/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
media

40
test/plugins/config.ts Normal file
View File

@@ -0,0 +1,40 @@
import { buildConfig } from '../buildConfig';
import { devUser } from '../credentials';
export const pagesSlug = 'pages';
export default buildConfig({
collections: [
{
slug: 'users',
auth: true,
fields: [],
},
],
plugins: [
async (config) => ({
...config,
collections: [
...config.collections || [],
{
slug: pagesSlug,
fields: [
{
name: 'title',
type: 'text',
},
],
},
],
}),
],
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
});
},
});

View File

28
test/plugins/int.spec.ts Normal file
View File

@@ -0,0 +1,28 @@
import { initPayloadTest } from '../helpers/configHelpers';
import { RESTClient } from '../helpers/rest';
import configPromise, { pagesSlug } from './config';
import payload from '../../src';
require('isomorphic-fetch');
let client;
describe('Collections - Plugins', () => {
beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
const config = await configPromise;
client = new RESTClient(config, { serverURL, defaultSlug: pagesSlug });
await client.login();
});
it('created pages collection', async () => {
const { id } = await payload.create({
collection: pagesSlug,
data: {
title: 'Test Page',
},
});
expect(id).toEqual(expect.any(String));
});
});

View File

@@ -1,7 +1,7 @@
import { request, GraphQLClient } from 'graphql-request'; import { request, GraphQLClient } from 'graphql-request';
import { initPayloadTest } from '../helpers/configHelpers'; import { initPayloadTest } from '../helpers/configHelpers';
import payload from '../../src'; import payload from '../../src';
import config from './config'; import configPromise from './config';
import AutosavePosts from './collections/Autosave'; import AutosavePosts from './collections/Autosave';
import AutosaveGlobal from './globals/Autosave'; import AutosaveGlobal from './globals/Autosave';
import { devUser } from '../credentials'; import { devUser } from '../credentials';
@@ -27,6 +27,8 @@ const globalGraphQLOriginalTitle = 'updated global title';
describe('Versions', () => { describe('Versions', () => {
beforeAll(async () => { beforeAll(async () => {
const config = await configPromise;
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } }); const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}`; graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}`;