feat: add admin.hidden to collections and globals (#2487)

This commit is contained in:
Dan Ribbens
2023-04-17 15:21:42 -04:00
committed by GitHub
parent faef4d5f8e
commit 81d69d1b64
14 changed files with 276 additions and 198 deletions

View File

@@ -63,18 +63,19 @@ You can find an assortment of [example collection configs](https://github.com/pa
You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin` property on a collection's config. You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin` property on a collection's config.
| Option | Description | | Option | Description |
| --------------------------- | -------------| |------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `group` | Text used as a label for grouping collection links together in the navigation. | | `group` | Text used as a label for grouping collection links together in the navigation. |
| `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) | | `hidden` | Set to true or a function, called with the current user, returning true to exclude this collection from navigation and admin routing. |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. | | `hooks` | Admin-specific hooks for this collection. [More](#admin-hooks) |
| `description` | Text or React component to display below the Collection label in the List view to give editors more information. | | `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin panel. If no field is defined, the ID of the document is used as the title. |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. | | `description` | Text or React component to display below the Collection label in the List view to give editors more information. |
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. | | `defaultColumns` | Array of field names that correspond to which columns to show by default in this collection's List view. |
| `disableDuplicate ` | Disables the "Duplicate" button while editing documents within this collection. |
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. | | `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). | | `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). |
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) | | `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) | | `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
### Preview ### Preview

View File

@@ -64,10 +64,11 @@ You can find an [example Global config](https://github.com/payloadcms/public-dem
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config. You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config.
| Option | Description | | Option | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------------------- | |--------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) | | `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). | | `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
### Preview ### Preview

View File

@@ -1,7 +1,5 @@
import React, { Suspense, lazy, useState, useEffect } from 'react'; import React, { lazy, Suspense, useEffect, useState } from 'react';
import { import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
Route, Switch, withRouter, Redirect,
} from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAuth } from './utilities/Auth'; import { useAuth } from './utilities/Auth';
import { useConfig } from './utilities/Config'; import { useConfig } from './utilities/Config';
@@ -178,82 +176,19 @@ const Routes = () => {
</DocumentInfoProvider> </DocumentInfoProvider>
</Route> </Route>
{collections.reduce((collectionRoutes, collection) => { {collections
const routesToReturn = [ .filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
...collectionRoutes, .reduce((collectionRoutes, collection) => {
<Route const routesToReturn = [
key={`${collection.slug}-list`} ...collectionRoutes,
path={`${match.url}/collections/${collection.slug}`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<List
{...routeProps}
collection={collection}
/>
);
}
return <Unauthorized />;
}}
/>,
<Route
key={`${collection.slug}-create`}
path={`${match.url}/collections/${collection.slug}/create`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.create?.permission) {
return (
<DocumentInfoProvider collection={collection}>
<Edit
{...routeProps}
collection={collection}
/>
</DocumentInfoProvider>
);
}
return <Unauthorized />;
}}
/>,
<Route
key={`${collection.slug}-edit`}
path={`${match.url}/collections/${collection.slug}/:id`}
exact
render={(routeProps) => {
const { match: { params: { id } } } = routeProps;
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return (
<DocumentInfoProvider
key={`${collection.slug}-edit-${id}-${locale}`}
collection={collection}
id={id}
>
<Edit
isEditing
{...routeProps}
collection={collection}
/>
</DocumentInfoProvider>
);
}
return <Unauthorized />;
}}
/>,
];
if (collection.versions) {
routesToReturn.push(
<Route <Route
key={`${collection.slug}-versions`} key={`${collection.slug}-list`}
path={`${match.url}/collections/${collection.slug}/:id/versions`} path={`${match.url}/collections/${collection.slug}`}
exact exact
render={(routeProps) => { render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) { if (permissions?.collections?.[collection.slug]?.read?.permission) {
return ( return (
<Versions <List
{...routeProps} {...routeProps}
collection={collection} collection={collection}
/> />
@@ -263,21 +198,15 @@ const Routes = () => {
return <Unauthorized />; return <Unauthorized />;
}} }}
/>, />,
);
routesToReturn.push(
<Route <Route
key={`${collection.slug}-view-version`} key={`${collection.slug}-create`}
path={`${match.url}/collections/${collection.slug}/:id/versions/:versionID`} path={`${match.url}/collections/${collection.slug}/create`}
exact exact
render={(routeProps) => { render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) { if (permissions?.collections?.[collection.slug]?.create?.permission) {
return ( return (
<DocumentInfoProvider <DocumentInfoProvider collection={collection}>
collection={collection} <Edit
id={routeProps.match.params.id}
>
<Version
{...routeProps} {...routeProps}
collection={collection} collection={collection}
/> />
@@ -288,81 +217,154 @@ const Routes = () => {
return <Unauthorized />; return <Unauthorized />;
}} }}
/>, />,
);
}
return routesToReturn;
}, [])}
{globals && globals.reduce((globalRoutes, global) => {
const routesToReturn = [
...globalRoutes,
<Route
key={`${global.slug}`}
path={`${match.url}/globals/${global.slug}`}
exact
render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.read?.permission) {
return (
<DocumentInfoProvider
global={global}
key={`${global.slug}-${locale}`}
>
<EditGlobal
{...routeProps}
global={global}
/>
</DocumentInfoProvider>
);
}
return <Unauthorized />;
}}
/>,
];
if (global.versions) {
routesToReturn.push(
<Route <Route
key={`${global.slug}-versions`} key={`${collection.slug}-edit`}
path={`${match.url}/globals/${global.slug}/versions`} path={`${match.url}/collections/${collection.slug}/:id`}
exact exact
render={(routeProps) => { render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.readVersions?.permission) { const { match: { params: { id } } } = routeProps;
if (permissions?.collections?.[collection.slug]?.read?.permission) {
return ( return (
<Versions <DocumentInfoProvider
{...routeProps} key={`${collection.slug}-edit-${id}-${locale}`}
global={global} collection={collection}
/> id={id}
>
<Edit
isEditing
{...routeProps}
collection={collection}
/>
</DocumentInfoProvider>
); );
} }
return <Unauthorized />; return <Unauthorized />;
}} }}
/>, />,
); ];
routesToReturn.push(
if (collection.versions) {
routesToReturn.push(
<Route
key={`${collection.slug}-versions`}
path={`${match.url}/collections/${collection.slug}/:id/versions`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) {
return (
<Versions
{...routeProps}
collection={collection}
/>
);
}
return <Unauthorized />;
}}
/>,
);
routesToReturn.push(
<Route
key={`${collection.slug}-view-version`}
path={`${match.url}/collections/${collection.slug}/:id/versions/:versionID`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.readVersions?.permission) {
return (
<DocumentInfoProvider
collection={collection}
id={routeProps.match.params.id}
>
<Version
{...routeProps}
collection={collection}
/>
</DocumentInfoProvider>
);
}
return <Unauthorized />;
}}
/>,
);
}
return routesToReturn;
}, [])}
{globals && globals
.filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
.reduce((globalRoutes, global) => {
const routesToReturn = [
...globalRoutes,
<Route <Route
key={`${global.slug}-view-version`} key={`${global.slug}`}
path={`${match.url}/globals/${global.slug}/versions/:versionID`} path={`${match.url}/globals/${global.slug}`}
exact exact
render={(routeProps) => { render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.readVersions?.permission) { if (permissions?.globals?.[global.slug]?.read?.permission) {
return ( return (
<Version <DocumentInfoProvider
{...routeProps}
global={global} global={global}
/> key={`${global.slug}-${locale}`}
>
<EditGlobal
{...routeProps}
global={global}
/>
</DocumentInfoProvider>
); );
} }
return <Unauthorized />; return <Unauthorized />;
}} }}
/>, />,
); ];
}
return routesToReturn; if (global.versions) {
}, [])} routesToReturn.push(
<Route
key={`${global.slug}-versions`}
path={`${match.url}/globals/${global.slug}/versions`}
exact
render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.readVersions?.permission) {
return (
<Versions
{...routeProps}
global={global}
/>
);
}
return <Unauthorized />;
}}
/>,
);
routesToReturn.push(
<Route
key={`${global.slug}-view-version`}
path={`${match.url}/globals/${global.slug}/versions/:versionID`}
exact
render={(routeProps) => {
if (permissions?.globals?.[global.slug]?.readVersions?.permission) {
return (
<Version
{...routeProps}
global={global}
/>
);
}
return <Unauthorized />;
}}
/>,
);
}
return routesToReturn;
}, [])}
<Route path={`${match.url}*`}> <Route path={`${match.url}*`}>
<NotFound /> <NotFound />

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { NavLink, Link, useHistory } from 'react-router-dom'; import { Link, NavLink, useHistory } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useConfig } from '../../utilities/Config'; import { useConfig } from '../../utilities/Config';
import { useAuth } from '../../utilities/Auth'; import { useAuth } from '../../utilities/Auth';
@@ -12,7 +12,7 @@ import Account from '../../graphics/Account';
import Localizer from '../Localizer'; import Localizer from '../Localizer';
import NavGroup from '../NavGroup'; import NavGroup from '../NavGroup';
import Logout from '../Logout'; import Logout from '../Logout';
import { groupNavItems, Group, EntityToGroup, EntityType } from '../../../utilities/groupNavItems'; import { EntityToGroup, EntityType, Group, groupNavItems } from '../../../utilities/groupNavItems';
import { getTranslation } from '../../../../utilities/getTranslation'; import { getTranslation } from '../../../../utilities/getTranslation';
import './index.scss'; import './index.scss';
@@ -20,7 +20,7 @@ import './index.scss';
const baseClass = 'nav'; const baseClass = 'nav';
const DefaultNav = () => { const DefaultNav = () => {
const { permissions } = useAuth(); const { permissions, user } = useAuth();
const [menuActive, setMenuActive] = useState(false); const [menuActive, setMenuActive] = useState(false);
const [groups, setGroups] = useState<Group[]>([]); const [groups, setGroups] = useState<Group[]>([]);
const history = useHistory(); const history = useHistory();
@@ -47,24 +47,28 @@ const DefaultNav = () => {
useEffect(() => { useEffect(() => {
setGroups(groupNavItems([ setGroups(groupNavItems([
...collections.map((collection) => { ...collections
const entityToGroup: EntityToGroup = { .filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
type: EntityType.collection, .map((collection) => {
entity: collection, const entityToGroup: EntityToGroup = {
}; type: EntityType.collection,
entity: collection,
};
return entityToGroup; return entityToGroup;
}), }),
...globals.map((global) => { ...globals
const entityToGroup: EntityToGroup = { .filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
type: EntityType.global, .map((global) => {
entity: global, const entityToGroup: EntityToGroup = {
}; type: EntityType.global,
entity: global,
};
return entityToGroup; return entityToGroup;
}), }),
], permissions, i18n)); ], permissions, i18n));
}, [collections, globals, permissions, i18n, i18n.language]); }, [collections, globals, permissions, i18n, i18n.language, user]);
useEffect(() => history.listen(() => { useEffect(() => history.listen(() => {
setMenuActive(false); setMenuActive(false);

View File

@@ -8,7 +8,7 @@ import Card from '../../elements/Card';
import Button from '../../elements/Button'; import Button from '../../elements/Button';
import { Props } from './types'; import { Props } from './types';
import { Gutter } from '../../elements/Gutter'; import { Gutter } from '../../elements/Gutter';
import { groupNavItems, Group, EntityToGroup, EntityType } from '../../../utilities/groupNavItems'; import { EntityToGroup, EntityType, Group, groupNavItems } from '../../../utilities/groupNavItems';
import { getTranslation } from '../../../../utilities/getTranslation'; import { getTranslation } from '../../../../utilities/getTranslation';
import './index.scss'; import './index.scss';
@@ -20,6 +20,7 @@ const Dashboard: React.FC<Props> = (props) => {
collections, collections,
globals, globals,
permissions, permissions,
user,
} = props; } = props;
const { push } = useHistory(); const { push } = useHistory();
@@ -41,24 +42,28 @@ const Dashboard: React.FC<Props> = (props) => {
useEffect(() => { useEffect(() => {
setGroups(groupNavItems([ setGroups(groupNavItems([
...collections.map((collection) => { ...collections
const entityToGroup: EntityToGroup = { .filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
type: EntityType.collection, .map((collection) => {
entity: collection, const entityToGroup: EntityToGroup = {
}; type: EntityType.collection,
entity: collection,
};
return entityToGroup; return entityToGroup;
}), }),
...globals.map((global) => { ...globals
const entityToGroup: EntityToGroup = { .filter(({ admin: { hidden } }) => !(typeof hidden === 'function' ? hidden({ user }) : hidden))
type: EntityType.global, .map((global) => {
entity: global, const entityToGroup: EntityToGroup = {
}; type: EntityType.global,
entity: global,
};
return entityToGroup; return entityToGroup;
}), }),
], permissions, i18n)); ], permissions, i18n));
}, [collections, globals, i18n, permissions]); }, [collections, globals, i18n, permissions, user]);
return ( return (
<div className={baseClass}> <div className={baseClass}>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { useConfig } from '../../utilities/Config'; import { useConfig } from '../../utilities/Config';
import { useAuth } from '../../utilities/Auth'; import { useAuth } from '../../utilities/Auth';
import { useStepNav } from '../../elements/StepNav'; import { useStepNav } from '../../elements/StepNav';
@@ -6,7 +6,7 @@ import RenderCustomComponent from '../../utilities/RenderCustomComponent';
import DefaultDashboard from './Default'; import DefaultDashboard from './Default';
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
const { permissions } = useAuth(); const { permissions, user } = useAuth();
const { setStepNav } = useStepNav(); const { setStepNav } = useStepNav();
const [filteredGlobals, setFilteredGlobals] = useState([]); const [filteredGlobals, setFilteredGlobals] = useState([]);
@@ -42,6 +42,7 @@ const Dashboard: React.FC = () => {
globals: filteredGlobals, globals: filteredGlobals,
collections: collections.filter((collection) => permissions?.collections?.[collection.slug]?.read?.permission), collections: collections.filter((collection) => permissions?.collections?.[collection.slug]?.read?.permission),
permissions, permissions,
user,
}} }}
/> />
); );

View File

@@ -1,9 +1,10 @@
import { SanitizedCollectionConfig } from '../../../../collections/config/types'; import { SanitizedCollectionConfig } from '../../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../../globals/config/types'; import { SanitizedGlobalConfig } from '../../../../globals/config/types';
import { Permissions } from '../../../../auth/types'; import { Permissions, User } from '../../../../auth/types';
export type Props = { export type Props = {
collections: SanitizedCollectionConfig[], collections: SanitizedCollectionConfig[],
globals: SanitizedGlobalConfig[], globals: SanitizedGlobalConfig[],
permissions: Permissions permissions: Permissions,
user: User,
} }

View File

@@ -38,6 +38,10 @@ const collectionSchema = joi.object().keys({
}), }),
timestamps: joi.boolean(), timestamps: joi.boolean(),
admin: joi.object({ admin: joi.object({
hidden: joi.alternatives().try(
joi.boolean(),
joi.func(),
),
useAsTitle: joi.string(), useAsTitle: joi.string(),
defaultColumns: joi.array().items(joi.string()), defaultColumns: joi.array().items(joi.string()),
listSearchableFields: joi.array().items(joi.string()), listSearchableFields: joi.array().items(joi.string()),

View File

@@ -6,7 +6,7 @@ import { Response } from 'express';
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types'; import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
import { Field } from '../../fields/config/types'; import { Field } from '../../fields/config/types';
import { PayloadRequest } from '../../express/types'; import { PayloadRequest } from '../../express/types';
import { Auth, IncomingAuthType } from '../../auth/types'; import { Auth, IncomingAuthType, User } from '../../auth/types';
import { IncomingUploadType, Upload } from '../../uploads/types'; import { IncomingUploadType, Upload } from '../../uploads/types';
import { IncomingCollectionVersions, SanitizedCollectionVersions } from '../../versions/types'; import { IncomingCollectionVersions, SanitizedCollectionVersions } from '../../versions/types';
import { Config as GeneratedTypes } from '../../generated-types'; import { Config as GeneratedTypes } from '../../generated-types';
@@ -153,6 +153,10 @@ type BeforeDuplicateArgs<T> = {
export type BeforeDuplicate<T = any> = (args: BeforeDuplicateArgs<T>) => T | Promise<T> export type BeforeDuplicate<T = any> = (args: BeforeDuplicateArgs<T>) => T | Promise<T>
export type CollectionAdminOptions = { export type CollectionAdminOptions = {
/**
* Exclude the collection from the admin nav and routes
*/
hidden?: ((args: { user: User }) => boolean) | boolean;
/** /**
* Field to use as title in Edit view and first column in List view * Field to use as title in Edit view and first column in List view
*/ */

View File

@@ -9,6 +9,10 @@ const globalSchema = joi.object().keys({
joi.object().pattern(joi.string(), [joi.string()]), joi.object().pattern(joi.string(), [joi.string()]),
), ),
admin: joi.object({ admin: joi.object({
hidden: joi.alternatives().try(
joi.boolean(),
joi.func(),
),
group: joi.alternatives().try( group: joi.alternatives().try(
joi.string(), joi.string(),
joi.object().pattern(joi.string(), [joi.string()]), joi.object().pattern(joi.string(), [joi.string()]),

View File

@@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { Model, Document } from 'mongoose'; import { Document, Model } from 'mongoose';
import { DeepRequired } from 'ts-essentials'; import { DeepRequired } from 'ts-essentials';
import { GraphQLNonNull, GraphQLObjectType } from 'graphql'; import { GraphQLNonNull, GraphQLObjectType } from 'graphql';
import { User } from '../../auth/types';
import { PayloadRequest } from '../../express/types'; import { PayloadRequest } from '../../express/types';
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types'; import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
import { Field } from '../../fields/config/types'; import { Field } from '../../fields/config/types';
@@ -46,6 +47,10 @@ export interface GlobalModel extends Model<Document> {
} }
export type GlobalAdminOptions = { export type GlobalAdminOptions = {
/**
* Exclude the global from the admin nav and routes
*/
hidden?: ((args: { user: User }) => boolean) | boolean;
/** /**
* Place globals into a navigational group * Place globals into a navigational group
* */ * */

View File

@@ -7,7 +7,7 @@ import CustomMinimalRoute from './components/views/CustomMinimal';
import CustomDefaultRoute from './components/views/CustomDefault'; import CustomDefaultRoute from './components/views/CustomDefault';
import BeforeLogin from './components/BeforeLogin'; import BeforeLogin from './components/BeforeLogin';
import AfterNavLinks from './components/AfterNavLinks'; import AfterNavLinks from './components/AfterNavLinks';
import { slug, globalSlug } from './shared'; import { globalSlug, slug } from './shared';
import Logout from './components/Logout'; import Logout from './components/Logout';
import DemoUIFieldField from './components/DemoUIField/Field'; import DemoUIFieldField from './components/DemoUIField/Field';
import DemoUIFieldCell from './components/DemoUIField/Cell'; import DemoUIFieldCell from './components/DemoUIField/Cell';
@@ -68,6 +68,18 @@ export default buildConfig({
auth: true, auth: true,
fields: [], fields: [],
}, },
{
slug: 'hidden-collection',
admin: {
hidden: () => true,
},
fields: [
{
name: 'title',
type: 'text',
},
],
},
{ {
slug, slug,
labels: { labels: {
@@ -176,6 +188,18 @@ export default buildConfig({
}, },
], ],
globals: [ globals: [
{
slug: 'hidden-global',
admin: {
hidden: () => true,
},
fields: [
{
name: 'title',
type: 'text',
},
],
},
{ {
slug: globalSlug, slug: globalSlug,
label: { label: {

View File

@@ -111,6 +111,24 @@ describe('admin', () => {
await page.locator(`.step-nav >> text=${slug}`).click(); await page.locator(`.step-nav >> text=${slug}`).click();
expect(page.url()).toContain(url.list); expect(page.url()).toContain(url.list);
}); });
test('should not show hidden collections and globals', async () => {
await page.goto(url.admin);
// nav menu
await expect(await page.locator('#nav-hidden-collection')).toBeHidden();
await expect(await page.locator('#nav-hidden-global')).toBeHidden();
// dashboard
await expect(await page.locator('#card-hidden-collection')).toBeHidden();
await expect(await page.locator('#card-hidden-global')).toBeHidden();
// routing
await page.goto(url.collection('hidden-collection'));
await expect(await page.locator('.not-found')).toContainText('Nothing found');
await page.goto(url.global('hidden-global'));
await expect(await page.locator('.not-found')).toContainText('Nothing found');
});
}); });
describe('CRUD', () => { describe('CRUD', () => {

View File

@@ -18,6 +18,10 @@ export class AdminUrlUtil {
return `${this.list}/${id}`; return `${this.list}/${id}`;
} }
collection(slug: string): string {
return `${this.admin}/collections/${slug}`;
}
global(slug: string): string { global(slug: string): string {
return `${this.admin}/globals/${slug}`; return `${this.admin}/globals/${slug}`;
} }