mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
feat: disabled dp with content releases (#19756)
* feat: disabled dp with content releases * chore: export admin test setup (#19794) * fix: export admin store config * fix: pr feedback * fix: move default config fallback * chore: wip place everything on sub path * chore: tweak tsconfig to sort build out * chore: user admin server setup for content-releases --------- Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com> * chore: fix tests with permissions --------- Co-authored-by: markkaylor <mark.kaylor@strapi.io> Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com>
This commit is contained in:
parent
b17a180f13
commit
5004c4cc5a
@ -1884,7 +1884,7 @@ const CM_CONTENT_TYPE_MOCK_DATA = [
|
||||
displayName: 'Article',
|
||||
description: '',
|
||||
},
|
||||
options: {},
|
||||
options: { draftAndPublish: true },
|
||||
attributes: {
|
||||
id: {
|
||||
type: 'string',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { errors } from '@strapi/utils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { type SetupServer, setupServer } from 'msw/node';
|
||||
import * as qs from 'qs';
|
||||
|
||||
import { COLLECTION_TYPES, SINGLE_TYPES } from '../src/content-manager/constants/collections';
|
||||
@ -8,7 +8,7 @@ import { historyHandlers } from '../src/content-manager/history/tests/server';
|
||||
|
||||
import { MockData, mockData } from './mockData';
|
||||
|
||||
export const server = setupServer(
|
||||
export const server: SetupServer = setupServer(
|
||||
...[
|
||||
/**
|
||||
* TRACKING
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/* eslint-disable check-file/filename-naming-convention */
|
||||
import * as React from 'react';
|
||||
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { ConfigureStoreOptions, configureStore } from '@reduxjs/toolkit';
|
||||
import { fixtures } from '@strapi/admin-test-utils';
|
||||
import { darkTheme, lightTheme } from '@strapi/design-system';
|
||||
import { Permission, RBACContext } from '@strapi/helper-plugin';
|
||||
@ -51,9 +51,33 @@ setLogger({
|
||||
interface ProvidersProps {
|
||||
children: React.ReactNode;
|
||||
initialEntries?: MemoryRouterProps['initialEntries'];
|
||||
storeConfig?: Partial<ConfigureStoreOptions>;
|
||||
permissions?: Permission[];
|
||||
}
|
||||
|
||||
const Providers = ({ children, initialEntries }: ProvidersProps) => {
|
||||
const defaultTestStoreConfig = {
|
||||
preloadedState: initialState,
|
||||
reducer: {
|
||||
[adminApi.reducerPath]: adminApi.reducer,
|
||||
admin_app: appReducer,
|
||||
rbacProvider: RBACReducer,
|
||||
'content-manager_app': cmAppReducer,
|
||||
[contentManagerApi.reducerPath]: contentManagerApi.reducer,
|
||||
'content-manager': contentManagerReducer,
|
||||
},
|
||||
// @ts-expect-error – this fails.
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...getDefaultMiddleware({
|
||||
// Disable timing checks for test env
|
||||
immutableCheck: false,
|
||||
serializableCheck: false,
|
||||
}),
|
||||
adminApi.middleware,
|
||||
contentManagerApi.middleware,
|
||||
],
|
||||
};
|
||||
|
||||
const Providers = ({ children, initialEntries, storeConfig, permissions = [] }: ProvidersProps) => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
@ -62,28 +86,10 @@ const Providers = ({ children, initialEntries }: ProvidersProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const store = configureStore({
|
||||
const store = configureStore(
|
||||
// @ts-expect-error – we've not filled up the entire initial state.
|
||||
preloadedState: initialState,
|
||||
reducer: {
|
||||
[adminApi.reducerPath]: adminApi.reducer,
|
||||
admin_app: appReducer,
|
||||
rbacProvider: RBACReducer,
|
||||
'content-manager_app': cmAppReducer,
|
||||
[contentManagerApi.reducerPath]: contentManagerApi.reducer,
|
||||
'content-manager': contentManagerReducer,
|
||||
},
|
||||
// @ts-expect-error – this fails.
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...getDefaultMiddleware({
|
||||
// Disable timing checks for test env
|
||||
immutableCheck: false,
|
||||
serializableCheck: false,
|
||||
}),
|
||||
adminApi.middleware,
|
||||
contentManagerApi.middleware,
|
||||
],
|
||||
});
|
||||
storeConfig ?? defaultTestStoreConfig
|
||||
);
|
||||
|
||||
const router = createMemoryRouter(
|
||||
[
|
||||
@ -143,6 +149,7 @@ const Providers = ({ children, initialEntries }: ProvidersProps) => {
|
||||
refetchPermissions: jest.fn(),
|
||||
allPermissions: [
|
||||
...fixtures.permissions.allPermissions,
|
||||
...permissions,
|
||||
{
|
||||
id: 314,
|
||||
action: 'admin::users.read',
|
||||
@ -205,18 +212,24 @@ export interface RenderOptions {
|
||||
renderOptions?: RTLRenderOptions;
|
||||
userEventOptions?: Parameters<typeof userEvent.setup>[0];
|
||||
initialEntries?: MemoryRouterProps['initialEntries'];
|
||||
providerOptions?: Pick<ProvidersProps, 'storeConfig' | 'permissions'>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @description A custom render function that wraps the component with the necessary providers,
|
||||
* for use of testing components within the Strapi Admin.
|
||||
*/
|
||||
const render = (
|
||||
ui: React.ReactElement,
|
||||
{ renderOptions, userEventOptions, initialEntries }: RenderOptions = {}
|
||||
{ renderOptions, userEventOptions, initialEntries, providerOptions }: RenderOptions = {}
|
||||
): RenderResult & { user: ReturnType<typeof userEvent.setup> } => {
|
||||
const { wrapper: Wrapper = fallbackWrapper, ...restOptions } = renderOptions ?? {};
|
||||
|
||||
return {
|
||||
...renderRTL(ui, {
|
||||
wrapper: ({ children }) => (
|
||||
<Providers initialEntries={initialEntries}>
|
||||
<Providers initialEntries={initialEntries} {...providerOptions}>
|
||||
<Wrapper>{children}</Wrapper>
|
||||
</Providers>
|
||||
),
|
||||
@ -226,6 +239,11 @@ const render = (
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* @description A custom render-hook function that wraps the component with the necessary providers,
|
||||
* for use of testing hooks within the Strapi Admin.
|
||||
*/
|
||||
const renderHook = <
|
||||
Result,
|
||||
Props,
|
||||
@ -249,4 +267,4 @@ const renderHook = <
|
||||
});
|
||||
};
|
||||
|
||||
export { render, renderHook, waitFor, server, act, screen, fireEvent };
|
||||
export { render, renderHook, waitFor, server, act, screen, fireEvent, defaultTestStoreConfig };
|
||||
|
||||
@ -11,12 +11,12 @@
|
||||
"../shared",
|
||||
"./module.d.ts",
|
||||
"../ee/admin",
|
||||
"../package.json"
|
||||
"../package.json",
|
||||
"./tests"
|
||||
],
|
||||
"exclude": [
|
||||
"./tests",
|
||||
"**/__mocks__",
|
||||
"**/tests",
|
||||
"./src/**/tests",
|
||||
"**/*.test.*",
|
||||
"../ee/admin/**/*.test.*",
|
||||
"../ee/admin/**/__mocks__"
|
||||
|
||||
@ -27,6 +27,13 @@
|
||||
"require": "./dist/admin/index.js",
|
||||
"default": "./dist/admin/index.js"
|
||||
},
|
||||
"./strapi-admin/tests": {
|
||||
"types": "./dist/admin/tests/utils.d.ts",
|
||||
"source": "./admin/tests/utils.tsx",
|
||||
"import": "./dist/admin/tests/index.mjs",
|
||||
"require": "./dist/admin/tests/index.js",
|
||||
"default": "./dist/admin/tests/index.js"
|
||||
},
|
||||
"./_internal": {
|
||||
"types": "./dist/_internal/index.d.ts",
|
||||
"source": "./_internal/index.ts",
|
||||
|
||||
@ -10,6 +10,14 @@ const config: Config = defineConfig({
|
||||
tsconfig: './admin/tsconfig.build.json',
|
||||
runtime: 'web',
|
||||
},
|
||||
{
|
||||
source: './admin/tests/utils.tsx',
|
||||
import: './dist/admin/tests/index.mjs',
|
||||
require: './dist/admin/tests/index.js',
|
||||
types: './dist/admin/tests/utils.d.ts',
|
||||
tsconfig: './admin/tsconfig.build.json',
|
||||
runtime: 'web',
|
||||
},
|
||||
{
|
||||
source: './_internal/index.ts',
|
||||
import: './dist/_internal.mjs',
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAPIErrorHandler, useNotification, useQueryParams } from '@strapi/admin/strapi-admin';
|
||||
import {
|
||||
useAPIErrorHandler,
|
||||
useNotification,
|
||||
useQueryParams,
|
||||
unstable_useDocument,
|
||||
} from '@strapi/admin/strapi-admin';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -248,7 +253,7 @@ const AddActionToReleaseModal = ({
|
||||
export const CMReleasesContainer = () => {
|
||||
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
||||
const { formatMessage, formatDate, formatTime } = useIntl();
|
||||
const { id, slug } = useParams<{
|
||||
const { id, slug, collectionType } = useParams<{
|
||||
id: string;
|
||||
origin: string;
|
||||
slug: string;
|
||||
@ -259,6 +264,13 @@ export const CMReleasesContainer = () => {
|
||||
allowedActions: { canCreateAction, canMain, canDeleteAction },
|
||||
} = useRBAC(PERMISSIONS);
|
||||
|
||||
const { schema } = unstable_useDocument({
|
||||
collectionType: collectionType!,
|
||||
model: slug!,
|
||||
});
|
||||
|
||||
const hasDraftAndPublish = schema?.options?.draftAndPublish;
|
||||
|
||||
const contentTypeUid = slug as Common.UID.ContentType;
|
||||
const canFetch = id != null && contentTypeUid != null;
|
||||
const fetchParams = canFetch
|
||||
@ -282,8 +294,9 @@ export const CMReleasesContainer = () => {
|
||||
|
||||
/**
|
||||
* - Impossible to add entry to release before it exists
|
||||
* - Content types without draft and publish cannot add entries to release
|
||||
*/
|
||||
if (isCreatingEntry) {
|
||||
if (isCreatingEntry || !hasDraftAndPublish) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -43,21 +43,22 @@ describe('CMReleasesContainer', () => {
|
||||
it('should render the container', async () => {
|
||||
render();
|
||||
|
||||
await screen.findByRole('complementary', { name: 'Releases' });
|
||||
await screen.findByRole('button', { name: 'Add to release' });
|
||||
const informationBox = await screen.findByRole('complementary', { name: 'Releases' });
|
||||
expect(informationBox).toBeInTheDocument();
|
||||
|
||||
const addToReleaseButton = await screen.findByRole('button', { name: 'Add to release' });
|
||||
expect(addToReleaseButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open and close the add to release modal', async () => {
|
||||
const { user } = render();
|
||||
|
||||
await screen.findByRole('complementary', { name: 'Releases' });
|
||||
|
||||
const addToReleaseButton = screen.getByRole('button', { name: 'Add to release' });
|
||||
const addToReleaseButton = await screen.findByRole('button', { name: 'Add to release' });
|
||||
await user.click(addToReleaseButton);
|
||||
const modalDialog = screen.getByRole('dialog', { name: 'Add to release' });
|
||||
const modalDialog = await screen.findByRole('dialog', { name: 'Add to release' });
|
||||
expect(modalDialog).toBeVisible();
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close the modal' });
|
||||
const closeButton = await screen.findByRole('button', { name: 'Close the modal' });
|
||||
await user.click(closeButton);
|
||||
expect(modalDialog).not.toBeVisible();
|
||||
});
|
||||
@ -76,17 +77,15 @@ describe('CMReleasesContainer', () => {
|
||||
|
||||
const { user } = render();
|
||||
|
||||
await screen.findByRole('complementary', { name: 'Releases' });
|
||||
|
||||
const addToReleaseButton = screen.getByRole('button', { name: 'Add to release' });
|
||||
const addToReleaseButton = await screen.findByRole('button', { name: 'Add to release' });
|
||||
await user.click(addToReleaseButton);
|
||||
|
||||
// Select a value received from the server
|
||||
const select = screen.getByRole('combobox', { name: 'Select a release' });
|
||||
const select = await screen.findByRole('combobox', { name: 'Select a release' });
|
||||
await user.click(select);
|
||||
await user.click(screen.getByRole('option', { name: 'release1' }));
|
||||
await user.click(await screen.findByRole('option', { name: 'release1' }));
|
||||
|
||||
const submitButtom = screen.getByRole('button', { name: 'Continue' });
|
||||
const submitButtom = await screen.findByRole('button', { name: 'Continue' });
|
||||
expect(submitButtom).toBeEnabled();
|
||||
});
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ describe('ReleaseActionMenu', () => {
|
||||
<ReleaseActionMenu.DeleteReleaseActionItem releaseId="1" actionId="1" />
|
||||
<ReleaseActionMenu.ReleaseActionEntryLinkItem
|
||||
contentTypeUid="api::category.category"
|
||||
locale="en"
|
||||
locale="en-GB"
|
||||
entryId={1}
|
||||
/>
|
||||
</ReleaseActionMenu.Root>
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
// import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
export const server = setupServer(...[]);
|
||||
@ -1,4 +1,4 @@
|
||||
import { server } from './server';
|
||||
import { server } from '@strapi/admin/strapi-admin/tests';
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen();
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
import { fixtures } from '@strapi/admin-test-utils';
|
||||
|
||||
import { PERMISSIONS } from '../src/constants';
|
||||
/**
|
||||
* This is for the redux store in `utils`.
|
||||
* The more we adopt it, the bigger it will get – which is okay.
|
||||
*/
|
||||
const initialState = {
|
||||
admin_app: { permissions: fixtures.permissions.app },
|
||||
rbacProvider: {
|
||||
allPermissions: [
|
||||
...fixtures.permissions.allPermissions,
|
||||
{
|
||||
id: 314,
|
||||
action: 'admin::users.read',
|
||||
subject: null,
|
||||
properties: {},
|
||||
conditions: [],
|
||||
},
|
||||
...Object.values(PERMISSIONS).flat(),
|
||||
],
|
||||
collectionTypesRelatedPermissions: {
|
||||
'api::category.category': {
|
||||
'plugin::content-manager.explorer.update': [
|
||||
{
|
||||
action: 'plugin::content-manager.explorer.update',
|
||||
subject: 'api::category.category',
|
||||
properties: {
|
||||
locales: ['en'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export { initialState };
|
||||
@ -1,135 +1,39 @@
|
||||
/* eslint-disable check-file/filename-naming-convention */
|
||||
import * as React from 'react';
|
||||
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { NotificationsProvider } from '@strapi/admin/strapi-admin';
|
||||
import { fixtures } from '@strapi/admin-test-utils';
|
||||
import { DesignSystemProvider } from '@strapi/design-system';
|
||||
import { Permission, RBACContext } from '@strapi/helper-plugin';
|
||||
import { ConfigureStoreOptions } from '@reduxjs/toolkit';
|
||||
import {
|
||||
renderHook as renderHookRTL,
|
||||
render as renderRTL,
|
||||
defaultTestStoreConfig,
|
||||
render as renderAdmin,
|
||||
RenderOptions,
|
||||
server,
|
||||
waitFor,
|
||||
RenderOptions as RTLRenderOptions,
|
||||
RenderResult,
|
||||
act,
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter, MemoryRouterProps } from 'react-router-dom';
|
||||
} from '@strapi/admin/strapi-admin/tests';
|
||||
|
||||
import { PERMISSIONS } from '../src/constants';
|
||||
import { releaseApi } from '../src/services/release';
|
||||
|
||||
import { server } from './server';
|
||||
import { initialState } from './store';
|
||||
|
||||
interface ProvidersProps {
|
||||
children: React.ReactNode;
|
||||
initialEntries?: MemoryRouterProps['initialEntries'];
|
||||
}
|
||||
|
||||
const Providers = ({ children, initialEntries }: ProvidersProps) => {
|
||||
const store = configureStore({
|
||||
preloadedState: initialState,
|
||||
reducer: {
|
||||
[releaseApi.reducerPath]: releaseApi.reducer,
|
||||
admin_app: (state = initialState) => state,
|
||||
rbacProvider: (state = initialState) => state,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
immutableCheck: false,
|
||||
serializableCheck: false,
|
||||
}).concat(releaseApi.middleware),
|
||||
});
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// en is the default locale of the admin app.
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<DesignSystemProvider locale="en">
|
||||
<IntlProvider locale="en" messages={{}} textComponent="span">
|
||||
<NotificationsProvider>
|
||||
<RBACContext.Provider
|
||||
value={{
|
||||
refetchPermissions: jest.fn(),
|
||||
allPermissions: [
|
||||
...fixtures.permissions.allPermissions,
|
||||
{
|
||||
id: 314,
|
||||
action: 'admin::users.read',
|
||||
subject: null,
|
||||
properties: {},
|
||||
conditions: [],
|
||||
actionParameters: {},
|
||||
},
|
||||
...Object.values(PERMISSIONS).flat(),
|
||||
] as Permission[],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RBACContext.Provider>
|
||||
</NotificationsProvider>
|
||||
</IntlProvider>
|
||||
</DesignSystemProvider>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
const storeConfig: ConfigureStoreOptions = {
|
||||
preloadedState: defaultTestStoreConfig.preloadedState,
|
||||
reducer: {
|
||||
...defaultTestStoreConfig.reducer,
|
||||
[releaseApi.reducerPath]: releaseApi.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => [
|
||||
...defaultTestStoreConfig.middleware(getDefaultMiddleware),
|
||||
releaseApi.middleware,
|
||||
],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
const fallbackWrapper = ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
||||
|
||||
export interface RenderOptions {
|
||||
renderOptions?: RTLRenderOptions;
|
||||
userEventOptions?: Parameters<typeof userEvent.setup>[0];
|
||||
initialEntries?: MemoryRouterProps['initialEntries'];
|
||||
}
|
||||
|
||||
const render = (
|
||||
ui: React.ReactElement,
|
||||
{ renderOptions, userEventOptions, initialEntries }: RenderOptions = {}
|
||||
): RenderResult & { user: ReturnType<typeof userEvent.setup> } => {
|
||||
const { wrapper: Wrapper = fallbackWrapper, ...restOptions } = renderOptions ?? {};
|
||||
|
||||
return {
|
||||
...renderRTL(ui, {
|
||||
wrapper: ({ children }) => (
|
||||
<Providers initialEntries={initialEntries}>
|
||||
<Wrapper>{children}</Wrapper>
|
||||
</Providers>
|
||||
),
|
||||
...restOptions,
|
||||
}),
|
||||
user: userEvent.setup(userEventOptions),
|
||||
};
|
||||
};
|
||||
|
||||
const renderHook: typeof renderHookRTL = (hook, options) => {
|
||||
const { wrapper: Wrapper = fallbackWrapper, ...restOptions } = options ?? {};
|
||||
|
||||
return renderHookRTL(hook, {
|
||||
wrapper: ({ children }) => (
|
||||
<Providers>
|
||||
<Wrapper>{children}</Wrapper>
|
||||
</Providers>
|
||||
),
|
||||
...restOptions,
|
||||
options: RenderOptions = {}
|
||||
): ReturnType<typeof renderAdmin> =>
|
||||
renderAdmin(ui, {
|
||||
...options,
|
||||
providerOptions: { storeConfig, permissions: Object.values(PERMISSIONS).flat() },
|
||||
});
|
||||
};
|
||||
|
||||
export { render, renderHook, waitFor, act, screen, server };
|
||||
export { render, waitFor, act, screen, server };
|
||||
|
||||
@ -6,7 +6,9 @@ import { getEntryValidStatus, getService } from './utils';
|
||||
|
||||
export const bootstrap = async ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
if (strapi.ee.features.isEnabled('cms-content-releases')) {
|
||||
const contentTypesWithDraftAndPublish = Object.keys(strapi.contentTypes);
|
||||
const contentTypesWithDraftAndPublish = Object.keys(strapi.contentTypes).filter(
|
||||
(uid: any) => strapi.contentTypes[uid]?.options?.draftAndPublish
|
||||
);
|
||||
|
||||
// Clean up release-actions when an entry is deleted
|
||||
strapi.db.lifecycles.subscribe({
|
||||
@ -53,7 +55,7 @@ export const bootstrap = async ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
async beforeDeleteMany(event) {
|
||||
const { model, params } = event;
|
||||
// @ts-expect-error TODO: lifecycles types looks like are not 100% finished
|
||||
if (model.kind === 'collectionType') {
|
||||
if (model.kind === 'collectionType' && model.options?.draftAndPublish) {
|
||||
const { where } = params;
|
||||
const entriesToDelete = await strapi.db
|
||||
.query(model.uid)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Schema, Common } from '@strapi/types';
|
||||
import { async } from '@strapi/utils';
|
||||
import { contentTypes as contentTypesUtils, async } from '@strapi/utils';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import { difference, keys } from 'lodash';
|
||||
@ -13,6 +13,35 @@ interface Input {
|
||||
contentTypes: Record<string, Schema.ContentType>;
|
||||
}
|
||||
|
||||
export async function deleteActionsOnDisableDraftAndPublish({
|
||||
oldContentTypes,
|
||||
contentTypes,
|
||||
}: Input) {
|
||||
if (!oldContentTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const uid in contentTypes) {
|
||||
if (!oldContentTypes[uid]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldContentType = oldContentTypes[uid];
|
||||
const contentType = contentTypes[uid];
|
||||
|
||||
if (
|
||||
contentTypesUtils.hasDraftAndPublish(oldContentType) &&
|
||||
!contentTypesUtils.hasDraftAndPublish(contentType)
|
||||
) {
|
||||
await strapi.db
|
||||
?.queryBuilder(RELEASE_ACTION_MODEL_UID)
|
||||
.delete()
|
||||
.where({ contentType: uid })
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes }: Input) {
|
||||
const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes)) ?? [];
|
||||
|
||||
@ -96,7 +125,9 @@ export async function migrateIsValidAndStatusReleases() {
|
||||
|
||||
export async function revalidateChangedContentTypes({ oldContentTypes, contentTypes }: Input) {
|
||||
if (oldContentTypes !== undefined && contentTypes !== undefined) {
|
||||
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
|
||||
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
||||
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
||||
);
|
||||
const releasesAffected = new Set();
|
||||
|
||||
async
|
||||
|
||||
@ -4,6 +4,7 @@ import type { LoadedStrapi } from '@strapi/types';
|
||||
import { ACTIONS, RELEASE_MODEL_UID, RELEASE_ACTION_MODEL_UID } from './constants';
|
||||
import {
|
||||
deleteActionsOnDeleteContentType,
|
||||
deleteActionsOnDisableDraftAndPublish,
|
||||
migrateIsValidAndStatusReleases,
|
||||
revalidateChangedContentTypes,
|
||||
disableContentTypeLocalized,
|
||||
@ -14,7 +15,11 @@ export const register = async ({ strapi }: { strapi: LoadedStrapi }) => {
|
||||
if (strapi.ee.features.isEnabled('cms-content-releases')) {
|
||||
await strapi.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
||||
|
||||
strapi.hook('strapi::content-types.beforeSync').register(disableContentTypeLocalized);
|
||||
strapi
|
||||
.hook('strapi::content-types.beforeSync')
|
||||
.register(disableContentTypeLocalized)
|
||||
.register(deleteActionsOnDisableDraftAndPublish);
|
||||
|
||||
strapi
|
||||
.hook('strapi::content-types.afterSync')
|
||||
.register(deleteActionsOnDeleteContentType)
|
||||
|
||||
@ -25,6 +25,23 @@ describe('Release Validation service', () => {
|
||||
'No content type found for uid api::plop.plop'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error if the content type does not have draftAndPublish enabled', () => {
|
||||
const strapiMock = {
|
||||
...baseStrapiMock,
|
||||
contentType: jest.fn().mockReturnValue({
|
||||
options: {},
|
||||
}),
|
||||
};
|
||||
// @ts-expect-error Ignore missing properties
|
||||
const releaseValidationService = createReleaseValidationService({ strapi: strapiMock });
|
||||
|
||||
expect(() =>
|
||||
releaseValidationService.validateEntryContentType('api::category.category')
|
||||
).toThrow(
|
||||
'Content type with uid api::category.category does not have draftAndPublish enabled'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateUniqueEntry', () => {
|
||||
|
||||
@ -51,6 +51,12 @@ const createReleaseValidationService = ({ strapi }: { strapi: LoadedStrapi }) =>
|
||||
if (!contentType) {
|
||||
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
||||
}
|
||||
|
||||
if (!contentType.options?.draftAndPublish) {
|
||||
throw new errors.ValidationError(
|
||||
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
||||
);
|
||||
}
|
||||
},
|
||||
async validatePendingReleasesLimit() {
|
||||
// Use the maximum releases option if it exists, otherwise default to 3
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user