Compare commits
6 Commits
chore/driz
...
fix/beta/l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a52c56acb | ||
|
|
ada9978a8c | ||
|
|
874279c530 | ||
|
|
7ed6634bc5 | ||
|
|
09a0ee3ab9 | ||
|
|
67acab2cd5 |
@@ -1,3 +1,3 @@
|
||||
export { GraphQLJSON, GraphQLJSONObject } from '../packages/graphql-type-json/index.js'
|
||||
export { buildPaginatedListType } from '../schema/buildPaginatedListType.js'
|
||||
export { default as GraphQL } from 'graphql'
|
||||
export * as GraphQL from 'graphql'
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
|
||||
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
|
||||
import { getRouteWithoutAdmin, isAdminAuthRoute, isAdminRoute } from './shared.js'
|
||||
|
||||
export const handleAdminPage = ({
|
||||
adminRoute,
|
||||
@@ -20,9 +20,9 @@ export const handleAdminPage = ({
|
||||
permissions: Permissions
|
||||
route: string
|
||||
}) => {
|
||||
if (isAdminRoute(route, adminRoute)) {
|
||||
const baseAdminRoute = adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
|
||||
const routeSegments = baseAdminRoute.split('/').filter(Boolean)
|
||||
if (isAdminRoute({ adminRoute, config, route })) {
|
||||
const routeWithoutAdmin = getRouteWithoutAdmin({ adminRoute, route })
|
||||
const routeSegments = routeWithoutAdmin.split('/').filter(Boolean)
|
||||
const [entityType, entitySlug, createOrID] = routeSegments
|
||||
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
|
||||
const globalSlug = entityType === 'globals' ? entitySlug : undefined
|
||||
@@ -47,7 +47,7 @@ export const handleAdminPage = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (!permissions.canAccessAdmin && !isAdminAuthRoute(config, route, adminRoute)) {
|
||||
if (!permissions.canAccessAdmin && !isAdminAuthRoute({ adminRoute, config, route })) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const handleAuthRedirect = ({
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
if (!isAdminAuthRoute(config, route, adminRoute)) {
|
||||
if (!isAdminAuthRoute({ adminRoute, config, route })) {
|
||||
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
|
||||
|
||||
const redirectRoute = encodeURIComponent(
|
||||
@@ -36,7 +36,7 @@ export const handleAuthRedirect = ({
|
||||
const customLoginRoute =
|
||||
typeof redirectUnauthenticatedUser === 'string' ? redirectUnauthenticatedUser : undefined
|
||||
|
||||
const loginRoute = isAdminRoute(route, adminRoute)
|
||||
const loginRoute = isAdminRoute({ adminRoute, config, route })
|
||||
? adminLoginRoute
|
||||
: customLoginRoute || loginRouteFromConfig
|
||||
|
||||
|
||||
@@ -11,16 +11,42 @@ const authRouteKeys: (keyof SanitizedConfig['admin']['routes'])[] = [
|
||||
'reset',
|
||||
]
|
||||
|
||||
export const isAdminRoute = (route: string, adminRoute: string) => {
|
||||
return route.startsWith(adminRoute)
|
||||
export const isAdminRoute = ({
|
||||
adminRoute,
|
||||
config,
|
||||
route,
|
||||
}: {
|
||||
adminRoute: string
|
||||
config: SanitizedConfig
|
||||
route: string
|
||||
}): boolean => {
|
||||
return route.startsWith(adminRoute) && !isAdminAuthRoute({ adminRoute, config, route })
|
||||
}
|
||||
|
||||
export const isAdminAuthRoute = (config: SanitizedConfig, route: string, adminRoute: string) => {
|
||||
export const isAdminAuthRoute = ({
|
||||
adminRoute,
|
||||
config,
|
||||
route,
|
||||
}: {
|
||||
adminRoute: string
|
||||
config: SanitizedConfig
|
||||
route: string
|
||||
}): boolean => {
|
||||
const authRoutes = config.admin?.routes
|
||||
? Object.entries(config.admin.routes)
|
||||
.filter(([key]) => authRouteKeys.includes(key as keyof SanitizedConfig['admin']['routes']))
|
||||
.map(([_, value]) => value)
|
||||
: []
|
||||
|
||||
return authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r))
|
||||
return authRoutes.some((r) => getRouteWithoutAdmin({ adminRoute, route }).startsWith(r))
|
||||
}
|
||||
|
||||
export const getRouteWithoutAdmin = ({
|
||||
adminRoute,
|
||||
route,
|
||||
}: {
|
||||
adminRoute: string
|
||||
route: string
|
||||
}): string => {
|
||||
return adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
value={compareValue}
|
||||
versionID={versionID}
|
||||
/>
|
||||
{localization && (
|
||||
{Boolean(localization) && (
|
||||
<SelectLocales onChange={setLocales} options={localeOptions} value={locales} />
|
||||
)}
|
||||
</div>
|
||||
@@ -138,7 +138,9 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
i18n={i18n}
|
||||
locales={
|
||||
locales
|
||||
? locales.map(({ label }) => (typeof label === 'string' ? label : undefined))
|
||||
? locales
|
||||
.map(({ value }) => (typeof value === 'string' ? value : undefined))
|
||||
.filter((label) => Boolean(label))
|
||||
: []
|
||||
}
|
||||
version={
|
||||
|
||||
@@ -266,11 +266,12 @@ export type NumberField = {
|
||||
/** Set a value for the number field to increment / decrement using browser controls. */
|
||||
step?: number
|
||||
} & Admin
|
||||
/** Maximum value accepted. Used in the default `validation` function. */
|
||||
/** Maximum value accepted. Used in the default `validate` function. */
|
||||
max?: number
|
||||
/** Minimum value accepted. Used in the default `validation` function. */
|
||||
/** Minimum value accepted. Used in the default `validate` function. */
|
||||
min?: number
|
||||
type: 'number'
|
||||
validate?: Validate<number | number[], unknown, unknown, NumberField>
|
||||
} & (
|
||||
| {
|
||||
/** Makes this field an ordered array of numbers instead of just a single number. */
|
||||
@@ -306,6 +307,7 @@ export type TextField = {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
type: 'text'
|
||||
validate?: Validate<string | string[], unknown, unknown, TextField>
|
||||
} & (
|
||||
| {
|
||||
/** Makes this field an ordered array of strings instead of just a single string. */
|
||||
@@ -338,6 +340,7 @@ export type EmailField = {
|
||||
placeholder?: Record<string, string> | string
|
||||
} & Admin
|
||||
type: 'email'
|
||||
validate?: Validate<string, unknown, unknown, EmailField>
|
||||
} & FieldBase
|
||||
|
||||
export type TextareaField = {
|
||||
@@ -355,6 +358,7 @@ export type TextareaField = {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
type: 'textarea'
|
||||
validate?: Validate<string, unknown, unknown, TextareaField>
|
||||
} & FieldBase
|
||||
|
||||
export type CheckboxField = {
|
||||
@@ -367,6 +371,7 @@ export type CheckboxField = {
|
||||
}
|
||||
} & Admin
|
||||
type: 'checkbox'
|
||||
validate?: Validate<unknown, unknown, unknown, CheckboxField>
|
||||
} & FieldBase
|
||||
|
||||
export type DateField = {
|
||||
@@ -381,6 +386,7 @@ export type DateField = {
|
||||
placeholder?: Record<string, string> | string
|
||||
} & Admin
|
||||
type: 'date'
|
||||
validate?: Validate<unknown, unknown, unknown, DateField>
|
||||
} & FieldBase
|
||||
|
||||
export type GroupField = {
|
||||
@@ -396,7 +402,8 @@ export type GroupField = {
|
||||
*/
|
||||
interfaceName?: string
|
||||
type: 'group'
|
||||
} & Omit<FieldBase, 'required' | 'validation'>
|
||||
validate?: Validate<unknown, unknown, unknown, GroupField>
|
||||
} & Omit<FieldBase, 'required'>
|
||||
|
||||
export type RowAdmin = Omit<Admin, 'description'>
|
||||
|
||||
@@ -404,7 +411,7 @@ export type RowField = {
|
||||
admin?: RowAdmin
|
||||
fields: Field[]
|
||||
type: 'row'
|
||||
} & Omit<FieldBase, 'admin' | 'label' | 'name'>
|
||||
} & Omit<FieldBase, 'admin' | 'label' | 'name' | 'validate'>
|
||||
|
||||
export type CollapsibleField = {
|
||||
fields: Field[]
|
||||
@@ -426,7 +433,7 @@ export type CollapsibleField = {
|
||||
label: Required<FieldBase['label']>
|
||||
}
|
||||
) &
|
||||
Omit<FieldBase, 'label' | 'name'>
|
||||
Omit<FieldBase, 'label' | 'name' | 'validate'>
|
||||
|
||||
export type TabsAdmin = Omit<Admin, 'description'>
|
||||
|
||||
@@ -435,7 +442,7 @@ type TabBase = {
|
||||
fields: Field[]
|
||||
interfaceName?: string
|
||||
saveToJWT?: boolean | string
|
||||
} & Omit<FieldBase, 'required' | 'validation'>
|
||||
} & Omit<FieldBase, 'required' | 'validate'>
|
||||
|
||||
export type NamedTab = {
|
||||
/** Customize generated GraphQL and Typescript schema names.
|
||||
@@ -521,6 +528,7 @@ export type UploadField = {
|
||||
maxDepth?: number
|
||||
relationTo: CollectionSlug
|
||||
type: 'upload'
|
||||
validate?: Validate<unknown, unknown, unknown, UploadField>
|
||||
} & FieldBase
|
||||
|
||||
type CodeAdmin = {
|
||||
@@ -537,6 +545,7 @@ export type CodeField = {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
type: 'code'
|
||||
validate?: Validate<string, unknown, unknown, CodeField>
|
||||
} & Omit<FieldBase, 'admin'>
|
||||
|
||||
type JSONAdmin = {
|
||||
@@ -555,6 +564,7 @@ export type JSONField = {
|
||||
uri: string
|
||||
}
|
||||
type: 'json'
|
||||
validate?: Validate<Record<string, unknown>, unknown, unknown, JSONField>
|
||||
} & Omit<FieldBase, 'admin'>
|
||||
|
||||
export type SelectField = {
|
||||
@@ -577,6 +587,7 @@ export type SelectField = {
|
||||
hasMany?: boolean
|
||||
options: Option[]
|
||||
type: 'select'
|
||||
validate?: Validate<string, unknown, unknown, SelectField>
|
||||
} & FieldBase
|
||||
|
||||
type SharedRelationshipProperties = {
|
||||
@@ -589,6 +600,7 @@ type SharedRelationshipProperties = {
|
||||
*/
|
||||
maxDepth?: number
|
||||
type: 'relationship'
|
||||
validate?: Validate<unknown, unknown, unknown, SharedRelationshipProperties>
|
||||
} & (
|
||||
| {
|
||||
hasMany: true
|
||||
@@ -627,12 +639,14 @@ type RelationshipAdmin = {
|
||||
}
|
||||
isSortable?: boolean
|
||||
} & Admin
|
||||
|
||||
export type PolymorphicRelationshipField = {
|
||||
admin?: {
|
||||
sortOptions?: { [collectionSlug: CollectionSlug]: string }
|
||||
} & RelationshipAdmin
|
||||
relationTo: CollectionSlug[]
|
||||
} & SharedRelationshipProperties
|
||||
|
||||
export type SingleRelationshipField = {
|
||||
admin?: {
|
||||
sortOptions?: string
|
||||
@@ -707,6 +721,7 @@ export type ArrayField = {
|
||||
maxRows?: number
|
||||
minRows?: number
|
||||
type: 'array'
|
||||
validate?: Validate<unknown[], unknown, unknown, ArrayField>
|
||||
} & FieldBase
|
||||
|
||||
export type RadioField = {
|
||||
@@ -727,6 +742,7 @@ export type RadioField = {
|
||||
enumName?: DBIdentifierName
|
||||
options: Option[]
|
||||
type: 'radio'
|
||||
validate?: Validate<string, unknown, unknown, RadioField>
|
||||
} & FieldBase
|
||||
|
||||
export type Block = {
|
||||
@@ -781,10 +797,12 @@ export type BlockField = {
|
||||
maxRows?: number
|
||||
minRows?: number
|
||||
type: 'blocks'
|
||||
validate?: Validate<string, unknown, unknown, BlockField>
|
||||
} & FieldBase
|
||||
|
||||
export type PointField = {
|
||||
type: 'point'
|
||||
validate?: Validate<unknown, unknown, unknown, PointField>
|
||||
} & FieldBase
|
||||
|
||||
export type Field =
|
||||
|
||||
@@ -121,7 +121,7 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (!skipValidationFromHere && field.validate) {
|
||||
if (!skipValidationFromHere && 'validate' in field && field.validate) {
|
||||
const valueToValidate = siblingData[field.name]
|
||||
let jsonError: object
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ export const syncWithSearch: SyncWithSearch = async (args) => {
|
||||
'doc.value': {
|
||||
equals: id,
|
||||
},
|
||||
'doc.relationTo': {
|
||||
equals: collection,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ export const SortColumn: React.FC<SortColumnProps> = (props) => {
|
||||
if (sort === desc) descClasses.push(`${baseClass}--active`)
|
||||
|
||||
const setSort = useCallback(
|
||||
(newSort) => {
|
||||
refineListData({
|
||||
async (newSort: string) => {
|
||||
await refineListData({
|
||||
sort: newSort,
|
||||
})
|
||||
},
|
||||
@@ -56,7 +56,7 @@ export const SortColumn: React.FC<SortColumnProps> = (props) => {
|
||||
label,
|
||||
})}
|
||||
className={[...ascClasses, `${baseClass}__button`].filter(Boolean).join(' ')}
|
||||
onClick={() => setSort(asc)}
|
||||
onClick={() => void setSort(asc)}
|
||||
type="button"
|
||||
>
|
||||
<ChevronIcon direction="up" />
|
||||
@@ -67,7 +67,7 @@ export const SortColumn: React.FC<SortColumnProps> = (props) => {
|
||||
label,
|
||||
})}
|
||||
className={[...descClasses, `${baseClass}__button`].filter(Boolean).join(' ')}
|
||||
onClick={() => setSort(desc)}
|
||||
onClick={() => void setSort(desc)}
|
||||
type="button"
|
||||
>
|
||||
<ChevronIcon />
|
||||
|
||||
@@ -143,8 +143,11 @@ export const WhereBuilder: React.FC<WhereBuilderProps> = (props) => {
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shouldUpdateQuery) {
|
||||
handleWhereChange({ or: conditions })
|
||||
setShouldUpdateQuery(false)
|
||||
async function handleChange() {
|
||||
await handleWhereChange({ or: conditions })
|
||||
setShouldUpdateQuery(false)
|
||||
}
|
||||
void handleChange()
|
||||
}
|
||||
}, [conditions, handleWhereChange, shouldUpdateQuery])
|
||||
|
||||
|
||||
@@ -13,12 +13,19 @@ import { useSearchParams } from '../SearchParams/index.js'
|
||||
|
||||
export type ColumnPreferences = Pick<Column, 'accessor' | 'active'>[]
|
||||
|
||||
type Handlers = {
|
||||
handlePageChange?: (page: number) => void
|
||||
handlePerPageChange?: (limit: number) => void
|
||||
handleSearchChange?: (search: string) => void
|
||||
handleSortChange?: (sort: string) => void
|
||||
handleWhereChange?: (where: Where) => void
|
||||
type PropHandlers = {
|
||||
handlePageChange?: (page: number) => Promise<void> | void
|
||||
handlePerPageChange?: (limit: number) => Promise<void> | void
|
||||
handleSearchChange?: (search: string) => Promise<void> | void
|
||||
handleSortChange?: (sort: string) => Promise<void> | void
|
||||
handleWhereChange?: (where: Where) => Promise<void> | void
|
||||
}
|
||||
type ContextHandlers = {
|
||||
handlePageChange?: (page: number) => Promise<void>
|
||||
handlePerPageChange?: (limit: number) => Promise<void>
|
||||
handleSearchChange?: (search: string) => Promise<void>
|
||||
handleSortChange?: (sort: string) => Promise<void>
|
||||
handleWhereChange?: (where: Where) => Promise<void>
|
||||
}
|
||||
|
||||
export type ListQueryProps = {
|
||||
@@ -28,14 +35,14 @@ export type ListQueryProps = {
|
||||
defaultSort?: string
|
||||
modifySearchParams?: boolean
|
||||
preferenceKey?: string
|
||||
} & Handlers
|
||||
} & PropHandlers
|
||||
|
||||
export type ListQueryContext = {
|
||||
data: PaginatedDocs
|
||||
defaultLimit?: number
|
||||
defaultSort?: string
|
||||
refineListData: (args: RefineOverrides) => void
|
||||
} & Handlers
|
||||
refineListData: (args: RefineOverrides) => Promise<void>
|
||||
} & ContextHandlers
|
||||
|
||||
const Context = createContext({} as ListQueryContext)
|
||||
|
||||
@@ -71,6 +78,9 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
async (query: RefineOverrides) => {
|
||||
if (!modifySearchParams) return
|
||||
|
||||
let pageQuery = 'page' in query ? query.page : currentQuery?.page
|
||||
if ('where' in query || 'search' in query) pageQuery = '1'
|
||||
|
||||
const updatedPreferences: Record<string, unknown> = {}
|
||||
let updatePreferences = false
|
||||
|
||||
@@ -88,7 +98,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
|
||||
const params = {
|
||||
limit: 'limit' in query ? query.limit : currentQuery?.limit,
|
||||
page: 'page' in query ? query.page : currentQuery?.page,
|
||||
page: pageQuery,
|
||||
search: 'search' in query ? query.search : currentQuery?.search,
|
||||
sort: 'sort' in query ? query.sort : currentQuery?.sort,
|
||||
where: 'where' in query ? query.where : currentQuery?.where,
|
||||
@@ -102,7 +112,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
const handlePageChange = React.useCallback(
|
||||
async (arg: number) => {
|
||||
if (typeof handlePageChangeFromProps === 'function') {
|
||||
handlePageChangeFromProps(arg)
|
||||
await handlePageChangeFromProps(arg)
|
||||
}
|
||||
await refineListData({ page: String(arg) })
|
||||
},
|
||||
@@ -111,7 +121,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
const handlePerPageChange = React.useCallback(
|
||||
async (arg: number) => {
|
||||
if (typeof handlePerPageChangeFromProps === 'function') {
|
||||
handlePerPageChangeFromProps(arg)
|
||||
await handlePerPageChangeFromProps(arg)
|
||||
}
|
||||
await refineListData({ limit: String(arg) })
|
||||
},
|
||||
@@ -120,7 +130,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
const handleSearchChange = React.useCallback(
|
||||
async (arg: string) => {
|
||||
if (typeof handleSearchChangeFromProps === 'function') {
|
||||
handleSearchChangeFromProps(arg)
|
||||
await handleSearchChangeFromProps(arg)
|
||||
}
|
||||
await refineListData({ search: arg })
|
||||
},
|
||||
@@ -129,7 +139,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
const handleSortChange = React.useCallback(
|
||||
async (arg: string) => {
|
||||
if (typeof handleSortChangeFromProps === 'function') {
|
||||
handleSortChangeFromProps(arg)
|
||||
await handleSortChangeFromProps(arg)
|
||||
}
|
||||
await refineListData({ sort: arg })
|
||||
},
|
||||
@@ -138,7 +148,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
|
||||
const handleWhereChange = React.useCallback(
|
||||
async (arg: Where) => {
|
||||
if (typeof handleWhereChangeFromProps === 'function') {
|
||||
handleWhereChangeFromProps(arg)
|
||||
await handleWhereChangeFromProps(arg)
|
||||
}
|
||||
await refineListData({ where: arg })
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
closeNav,
|
||||
ensureCompilationIsDone,
|
||||
exactText,
|
||||
getAdminRoutes,
|
||||
getRoutes,
|
||||
initPageConsoleErrorCatch,
|
||||
login,
|
||||
openDocControls,
|
||||
@@ -99,7 +99,7 @@ describe('access control', () => {
|
||||
routes: { logout: logoutRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
} = getAdminRoutes({})
|
||||
} = getRoutes({})
|
||||
|
||||
logoutURL = `${serverURL}${adminRoute}${logoutRoute}`
|
||||
})
|
||||
|
||||
BIN
test/admin-root/app/favicon.ico
Normal file
BIN
test/admin-root/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -12,6 +12,13 @@ const dirname = path.dirname(filename)
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [PostsCollection],
|
||||
admin: {
|
||||
autoLogin: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
prefillOnly: true,
|
||||
},
|
||||
},
|
||||
cors: ['http://localhost:3000', 'http://localhost:3001'],
|
||||
globals: [MenuGlobal],
|
||||
routes: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as path from 'path'
|
||||
import { adminRoute } from 'shared.js'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
|
||||
import { ensureCompilationIsDone, initPageConsoleErrorCatch, login } from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
@@ -29,6 +29,8 @@ test.describe('Admin Panel (Root)', () => {
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await login({ page, serverURL, customRoutes: { admin: adminRoute } })
|
||||
|
||||
await ensureCompilationIsDone({
|
||||
customRoutes: {
|
||||
admin: adminRoute,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
checkPageTitle,
|
||||
ensureCompilationIsDone,
|
||||
exactText,
|
||||
getAdminRoutes,
|
||||
getRoutes,
|
||||
initPageConsoleErrorCatch,
|
||||
openDocControls,
|
||||
openNav,
|
||||
@@ -76,7 +76,7 @@ describe('admin1', () => {
|
||||
let customFieldsURL: AdminUrlUtil
|
||||
let disableDuplicateURL: AdminUrlUtil
|
||||
let serverURL: string
|
||||
let adminRoutes: ReturnType<typeof getAdminRoutes>
|
||||
let adminRoutes: ReturnType<typeof getRoutes>
|
||||
let loginURL: string
|
||||
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
@@ -106,7 +106,7 @@ describe('admin1', () => {
|
||||
|
||||
await ensureCompilationIsDone({ customAdminRoutes, page, serverURL })
|
||||
|
||||
adminRoutes = getAdminRoutes({ customAdminRoutes })
|
||||
adminRoutes = getRoutes({ customAdminRoutes })
|
||||
|
||||
loginURL = `${serverURL}${adminRoutes.routes.admin}${adminRoutes.admin.routes.login}`
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { Config, Geo, Post } from '../../payload-types.js'
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
exactText,
|
||||
getAdminRoutes,
|
||||
getRoutes,
|
||||
initPageConsoleErrorCatch,
|
||||
openDocDrawer,
|
||||
openNav,
|
||||
@@ -44,7 +44,7 @@ describe('admin2', () => {
|
||||
let postsUrl: AdminUrlUtil
|
||||
|
||||
let serverURL: string
|
||||
let adminRoutes: ReturnType<typeof getAdminRoutes>
|
||||
let adminRoutes: ReturnType<typeof getRoutes>
|
||||
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
const prebuild = Boolean(process.env.CI)
|
||||
@@ -69,7 +69,7 @@ describe('admin2', () => {
|
||||
|
||||
await ensureCompilationIsDone({ customAdminRoutes, page, serverURL })
|
||||
|
||||
adminRoutes = getAdminRoutes({ customAdminRoutes })
|
||||
adminRoutes = getRoutes({ customAdminRoutes })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
@@ -421,6 +421,42 @@ describe('admin2', () => {
|
||||
await expect(page.getByPlaceholder('Enter a value')).toHaveValue('[object Object]')
|
||||
await expect(page.locator(tableRowLocator)).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('should reset page when filters are applied', async () => {
|
||||
await deleteAllPosts()
|
||||
await mapAsync([...Array(6)], async () => {
|
||||
await createPost()
|
||||
})
|
||||
await page.reload()
|
||||
await mapAsync([...Array(6)], async () => {
|
||||
await createPost({ title: 'test' })
|
||||
})
|
||||
await page.reload()
|
||||
|
||||
const pageInfo = page.locator('.collection-list__page-info')
|
||||
const perPage = page.locator('.per-page')
|
||||
const tableItems = page.locator(tableRowLocator)
|
||||
|
||||
await expect(tableItems).toHaveCount(10)
|
||||
await expect(pageInfo).toHaveText('1-10 of 12')
|
||||
await expect(perPage).toContainText('Per Page: 10')
|
||||
|
||||
// go to page 2
|
||||
await page.goto(`${postsUrl.list}?limit=10&page=2`)
|
||||
|
||||
// add filter
|
||||
await page.locator('.list-controls__toggle-where').click()
|
||||
await page.locator('.where-builder__add-first-filter').click()
|
||||
await page.locator('.condition__field .rs__control').click()
|
||||
const options = page.locator('.rs__option')
|
||||
await options.locator('text=Tab 1 > Title').click()
|
||||
await page.locator('.condition__operator .rs__control').click()
|
||||
await options.locator('text=equals').click()
|
||||
await page.locator('.condition__value input').fill('test')
|
||||
|
||||
// expect to be on page 1
|
||||
await expect(pageInfo).toHaveText('1-6 of 6')
|
||||
})
|
||||
})
|
||||
|
||||
describe('table columns', () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { Config } from './payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
getAdminRoutes,
|
||||
getRoutes,
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
} from '../helpers.js'
|
||||
@@ -49,7 +49,7 @@ const createFirstUser = async ({
|
||||
routes: { createFirstUser: createFirstUserRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
} = getAdminRoutes({
|
||||
} = getRoutes({
|
||||
customAdminRoutes,
|
||||
customRoutes,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { BrowserContext, ChromiumBrowserContext, Locator, Page } from '@playwright/test'
|
||||
import type { Config } from 'payload'
|
||||
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { expect } from '@playwright/test'
|
||||
import { defaults } from 'payload'
|
||||
import { wait } from 'payload/shared'
|
||||
@@ -65,7 +66,7 @@ export async function ensureCompilationIsDone({
|
||||
}): Promise<void> {
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
} = getAdminRoutes({ customAdminRoutes, customRoutes })
|
||||
} = getRoutes({ customAdminRoutes, customRoutes })
|
||||
|
||||
const adminURL = `${serverURL}${adminRoute}`
|
||||
|
||||
@@ -114,7 +115,7 @@ export async function firstRegister(args: FirstRegisterArgs): Promise<void> {
|
||||
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
} = getAdminRoutes({ customAdminRoutes, customRoutes })
|
||||
} = getRoutes({ customAdminRoutes, customRoutes })
|
||||
|
||||
await page.goto(`${serverURL}${adminRoute}`)
|
||||
await page.fill('#field-email', devUser.email)
|
||||
@@ -130,27 +131,37 @@ export async function login(args: LoginArgs): Promise<void> {
|
||||
|
||||
const {
|
||||
admin: {
|
||||
routes: { createFirstUser: createFirstUserRoute, login: loginRoute },
|
||||
routes: { createFirstUser, login: incomingLoginRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
} = getAdminRoutes({ customAdminRoutes, customRoutes })
|
||||
routes: { admin: incomingAdminRoute },
|
||||
} = getRoutes({ customAdminRoutes, customRoutes })
|
||||
|
||||
await page.goto(`${serverURL}${adminRoute}${loginRoute}`)
|
||||
await page.waitForURL(`${serverURL}${adminRoute}${loginRoute}`)
|
||||
const adminRoute = formatAdminURL({ serverURL, adminRoute: incomingAdminRoute, path: '' })
|
||||
const loginRoute = formatAdminURL({
|
||||
serverURL,
|
||||
adminRoute: incomingAdminRoute,
|
||||
path: incomingLoginRoute,
|
||||
})
|
||||
const createFirstUserRoute = formatAdminURL({
|
||||
serverURL,
|
||||
adminRoute: incomingAdminRoute,
|
||||
path: createFirstUser,
|
||||
})
|
||||
|
||||
await page.goto(loginRoute)
|
||||
await page.waitForURL(loginRoute)
|
||||
await wait(500)
|
||||
await page.fill('#field-email', data.email)
|
||||
await page.fill('#field-password', data.password)
|
||||
await wait(500)
|
||||
await page.click('[type=submit]')
|
||||
await page.waitForURL(`${serverURL}${adminRoute}`)
|
||||
await page.waitForURL(adminRoute)
|
||||
|
||||
await expect(() => expect(page.url()).not.toContain(`${adminRoute}${loginRoute}`)).toPass({
|
||||
await expect(() => expect(page.url()).not.toContain(loginRoute)).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
|
||||
await expect(() =>
|
||||
expect(page.url()).not.toContain(`${adminRoute}${createFirstUserRoute}`),
|
||||
).toPass({
|
||||
await expect(() => expect(page.url()).not.toContain(createFirstUserRoute)).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
}
|
||||
@@ -328,7 +339,7 @@ export function describeIfInCIOrHasLocalstack(): jest.Describe {
|
||||
|
||||
type AdminRoutes = Config['admin']['routes']
|
||||
|
||||
export function getAdminRoutes({
|
||||
export function getRoutes({
|
||||
customAdminRoutes,
|
||||
customRoutes,
|
||||
}: {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./test/admin/config.ts"
|
||||
"./test/_community/config.ts"
|
||||
],
|
||||
"@payloadcms/live-preview": [
|
||||
"./packages/live-preview/src"
|
||||
|
||||
Reference in New Issue
Block a user