Merge branch 'main' into fix/localized-status-UI
This commit is contained in:
@@ -84,7 +84,7 @@ export interface Config {
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {
|
||||
menu: Menu;
|
||||
@@ -124,7 +124,7 @@ export interface UserAuthOperations {
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
id: string;
|
||||
title?: string | null;
|
||||
content?: {
|
||||
root: {
|
||||
@@ -149,7 +149,7 @@ export interface Post {
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: number;
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
@@ -193,7 +193,7 @@ export interface Media {
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
@@ -217,24 +217,24 @@ export interface User {
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: number;
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: number | Media;
|
||||
value: string | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -244,10 +244,10 @@ export interface PayloadLockedDocument {
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: number;
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
@@ -267,7 +267,7 @@ export interface PayloadPreference {
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: number;
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
@@ -393,7 +393,7 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
* via the `definition` "menu".
|
||||
*/
|
||||
export interface Menu {
|
||||
id: number;
|
||||
id: string;
|
||||
globalText?: string | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
|
||||
@@ -565,4 +565,109 @@ describe('Form State', () => {
|
||||
|
||||
expect(newState === currentState).toBe(true)
|
||||
})
|
||||
|
||||
it('should accept all values from the server regardless of local modifications, e.g. on submit', () => {
|
||||
const currentState = {
|
||||
title: {
|
||||
value: 'Test Post (modified on the client)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
computedTitle: {
|
||||
value: 'Test Post (computed on the client)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
}
|
||||
|
||||
const formStateAtTimeOfRequest = {
|
||||
...currentState,
|
||||
title: {
|
||||
value: 'Test Post (modified on the client 2)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
}
|
||||
|
||||
const incomingStateFromServer = {
|
||||
title: {
|
||||
value: 'Test Post (modified on the server)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
computedTitle: {
|
||||
value: 'Test Post (computed on the server)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
}
|
||||
|
||||
const newState = mergeServerFormState({
|
||||
acceptValues: true,
|
||||
currentState,
|
||||
formStateAtTimeOfRequest,
|
||||
incomingState: incomingStateFromServer,
|
||||
})
|
||||
|
||||
expect(newState).toStrictEqual(incomingStateFromServer)
|
||||
})
|
||||
|
||||
it('should not accept values from the server if they have been modified locally since the request was made, e.g. on autosave', () => {
|
||||
const currentState = {
|
||||
title: {
|
||||
value: 'Test Post (modified on the client 1)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
computedTitle: {
|
||||
value: 'Test Post',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
}
|
||||
|
||||
const formStateAtTimeOfRequest = {
|
||||
...currentState,
|
||||
title: {
|
||||
value: 'Test Post (modified on the client 2)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
}
|
||||
|
||||
const incomingStateFromServer = {
|
||||
title: {
|
||||
value: 'Test Post (modified on the server)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
computedTitle: {
|
||||
value: 'Test Post (modified on the server)',
|
||||
initialValue: 'Test Post',
|
||||
valid: true,
|
||||
passesCondition: true,
|
||||
},
|
||||
}
|
||||
|
||||
const newState = mergeServerFormState({
|
||||
acceptValues: { overrideLocalChanges: false },
|
||||
currentState,
|
||||
formStateAtTimeOfRequest,
|
||||
incomingState: incomingStateFromServer,
|
||||
})
|
||||
|
||||
expect(newState).toStrictEqual({
|
||||
...currentState,
|
||||
computedTitle: incomingStateFromServer.computedTitle, // This field was not modified locally, so should be updated from the server
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ import { reInitializeDB } from 'helpers/reInitializeDB.js'
|
||||
import * as path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { Config } from './payload-types.js'
|
||||
import type { Config, Post } from './payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
@@ -38,7 +38,6 @@ test.describe('Group By', () => {
|
||||
let serverURL: string
|
||||
let payload: PayloadTestSDK<Config>
|
||||
let user: any
|
||||
let category1Id: number | string
|
||||
|
||||
test.beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
@@ -695,42 +694,80 @@ test.describe('Group By', () => {
|
||||
).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('should show trashed docs in trash view when group-by is active', async () => {
|
||||
await page.goto(url.list)
|
||||
test.describe('Trash', () => {
|
||||
test('should show trashed docs in trash view when group-by is active', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
// Enable group-by on Category
|
||||
await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' })
|
||||
await expect(page.locator('.table-wrap')).toHaveCount(2) // We expect 2 groups initially
|
||||
// Enable group-by on Category
|
||||
await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' })
|
||||
await expect(page.locator('.table-wrap')).toHaveCount(2) // We expect 2 groups initially
|
||||
|
||||
// Trash the first document in the first group
|
||||
const firstTable = page.locator('.table-wrap').first()
|
||||
await firstTable.locator('.row-1 .cell-_select input').check()
|
||||
await firstTable.locator('.list-selection__button[aria-label="Delete"]').click()
|
||||
// Trash the first document in the first group
|
||||
const firstTable = page.locator('.table-wrap').first()
|
||||
await firstTable.locator('.row-1 .cell-_select input').check()
|
||||
await firstTable.locator('.list-selection__button[aria-label="Delete"]').click()
|
||||
|
||||
const firstGroupID = await firstTable
|
||||
.locator('.group-by-header__heading')
|
||||
.getAttribute('data-group-id')
|
||||
const firstGroupID = await firstTable
|
||||
.locator('.group-by-header__heading')
|
||||
.getAttribute('data-group-id')
|
||||
|
||||
const modalId = `[id^="${firstGroupID}-confirm-delete-many-docs"]`
|
||||
await expect(page.locator(modalId)).toBeVisible()
|
||||
const modalId = `[id^="${firstGroupID}-confirm-delete-many-docs"]`
|
||||
await expect(page.locator(modalId)).toBeVisible()
|
||||
|
||||
// Confirm trash (skip permanent delete)
|
||||
await page.locator(`${modalId} #confirm-action`).click()
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toHaveText(
|
||||
'1 Post moved to trash.',
|
||||
)
|
||||
// Confirm trash (skip permanent delete)
|
||||
await page.locator(`${modalId} #confirm-action`).click()
|
||||
await expect(page.locator('.payload-toast-container .toast-success')).toHaveText(
|
||||
'1 Post moved to trash.',
|
||||
)
|
||||
|
||||
// Go to the trash view
|
||||
await page.locator('#trash-view-pill').click()
|
||||
await expect(page).toHaveURL(/\/posts\/trash(\?|$)/)
|
||||
// Go to the trash view
|
||||
await page.locator('#trash-view-pill').click()
|
||||
await expect(page).toHaveURL(/\/posts\/trash(\?|$)/)
|
||||
|
||||
// Re-enable group-by on Category in trash view
|
||||
await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' })
|
||||
await expect(page.locator('.table-wrap')).toHaveCount(1) // Should only have Category 1 (or the trashed doc's category)
|
||||
// Re-enable group-by on Category in trash view
|
||||
await addGroupBy(page, { fieldLabel: 'Category', fieldPath: 'category' })
|
||||
await expect(page.locator('.table-wrap')).toHaveCount(1) // Should only have Category 1 (or the trashed doc's category)
|
||||
|
||||
// Ensure the trashed doc is visible
|
||||
await expect(
|
||||
page.locator('.table-wrap tbody tr td.cell-title', { hasText: 'Find me' }),
|
||||
).toBeVisible()
|
||||
// Ensure the trashed doc is visible
|
||||
await expect(
|
||||
page.locator('.table-wrap tbody tr td.cell-title', { hasText: 'Find me' }),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should properly clear group-by in trash view', async () => {
|
||||
await createTrashedPostDoc({ title: 'Trashed Post 1' })
|
||||
await page.goto(url.trash)
|
||||
|
||||
// Enable group-by on Title
|
||||
await addGroupBy(page, { fieldLabel: 'Title', fieldPath: 'title' })
|
||||
await expect(page.locator('.table-wrap')).toHaveCount(1)
|
||||
await expect(page.locator('.group-by-header')).toHaveText('Trashed Post 1')
|
||||
|
||||
await page.locator('#group-by--reset').click()
|
||||
await expect(page.locator('.group-by-header')).toBeHidden()
|
||||
})
|
||||
|
||||
test('should properly navigate to trashed doc edit view from group-by in trash view', async () => {
|
||||
await createTrashedPostDoc({ title: 'Trashed Post 1' })
|
||||
await page.goto(url.trash)
|
||||
|
||||
// Enable group-by on Title
|
||||
await addGroupBy(page, { fieldLabel: 'Title', fieldPath: 'title' })
|
||||
await expect(page.locator('.table-wrap')).toHaveCount(1)
|
||||
await expect(page.locator('.group-by-header')).toHaveText('Trashed Post 1')
|
||||
|
||||
await page.locator('.table-wrap tbody tr td.cell-title a').click()
|
||||
await expect(page).toHaveURL(/\/posts\/trash\/\d+/)
|
||||
})
|
||||
})
|
||||
|
||||
async function createTrashedPostDoc(data: Partial<Post>): Promise<Post> {
|
||||
return payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
...data,
|
||||
deletedAt: new Date().toISOString(), // Set the post as trashed
|
||||
},
|
||||
}) as unknown as Promise<Post>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -44,7 +44,15 @@ export default buildConfigWithDefaults({
|
||||
isGlobal: true,
|
||||
},
|
||||
},
|
||||
tenantSelectorLabel: { en: 'Site', es: 'Site in es' },
|
||||
i18n: {
|
||||
translations: {
|
||||
en: {
|
||||
'field-assignedTenant-label': 'Currently Assigned Site',
|
||||
'nav-tenantSelector-label': 'Filter By Site',
|
||||
'confirm-modal-tenant-switch--heading': 'Confirm Site Change',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
typescript: {
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
// throttleTest,
|
||||
} from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { clickListMenuItem, openListMenu } from '../helpers/e2e/toggleListMenu.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
import { assertURLParams } from './helpers/assertURLParams.js'
|
||||
@@ -190,9 +189,8 @@ describe('Query Presets', () => {
|
||||
test('should delete a preset, clear selection, and reset changes', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await openListMenu({ page })
|
||||
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Delete' })
|
||||
await page.locator('#delete-preset').click()
|
||||
|
||||
await page.locator('#confirm-delete-preset #confirm-action').click()
|
||||
|
||||
@@ -249,75 +247,29 @@ describe('Query Presets', () => {
|
||||
|
||||
test('should only show "edit" and "delete" controls when there is an active preset', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await openListMenu({ page })
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Edit'),
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Delete'),
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await expect(page.locator('#edit-preset')).toBeHidden()
|
||||
await expect(page.locator('#delete-preset')).toBeHidden()
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await openListMenu({ page })
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Edit'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Delete'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
await expect(page.locator('#edit-preset')).toBeVisible()
|
||||
await expect(page.locator('#delete-preset')).toBeVisible()
|
||||
})
|
||||
|
||||
test('should only show "reset" and "save" controls when there is an active preset and changes have been made', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
|
||||
await openListMenu({ page })
|
||||
await expect(page.locator('#reset-preset')).toBeHidden()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Reset'),
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Update for everyone'),
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Save'),
|
||||
}),
|
||||
).toBeHidden()
|
||||
await expect(page.locator('#save-preset')).toBeHidden()
|
||||
|
||||
await selectPreset({ page, presetTitle: seededData.onlyMe.title })
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
await openListMenu({ page })
|
||||
await expect(page.locator('#reset-preset')).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Reset'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Save'),
|
||||
page.locator('#save-preset', {
|
||||
hasText: exactText('Save changes'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
})
|
||||
@@ -329,12 +281,12 @@ describe('Query Presets', () => {
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
await openListMenu({ page })
|
||||
|
||||
// When not shared, the label is "Save"
|
||||
await expect(page.locator('#save-preset')).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
hasText: exactText('Save'),
|
||||
page.locator('#save-preset', {
|
||||
hasText: exactText('Save changes'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
|
||||
@@ -342,11 +294,9 @@ describe('Query Presets', () => {
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
await openListMenu({ page })
|
||||
|
||||
// When shared, the label is "Update for everyone"
|
||||
await expect(
|
||||
page.locator('#list-menu .popup__content .popup-button-list__button', {
|
||||
page.locator('#save-preset', {
|
||||
hasText: exactText('Update for everyone'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
@@ -362,27 +312,28 @@ describe('Query Presets', () => {
|
||||
hasText: exactText('ID'),
|
||||
})
|
||||
|
||||
await openListMenu({ page })
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Reset' })
|
||||
await page.locator('#reset-preset').click()
|
||||
|
||||
await openListColumns(page, {})
|
||||
await expect(column).toHaveClass(/pill-selector__pill--selected/)
|
||||
})
|
||||
|
||||
test('should only enter modified state when changes are made to an active preset', async () => {
|
||||
test.skip('should only enter modified state when changes are made to an active preset', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
await expect(page.locator('.list-controls__modified')).toBeVisible()
|
||||
await openListMenu({ page })
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Update for everyone' })
|
||||
|
||||
await page.locator('#save-preset').click()
|
||||
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
await expect(page.locator('.list-controls__modified')).toBeVisible()
|
||||
await openListMenu({ page })
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Reset' })
|
||||
|
||||
await page.locator('#reset-preset').click()
|
||||
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
})
|
||||
|
||||
@@ -392,8 +343,7 @@ describe('Query Presets', () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await openListMenu({ page })
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Edit' })
|
||||
await page.locator('#edit-preset').click()
|
||||
|
||||
const drawer = page.locator('[id^=doc-drawer_payload-query-presets_0_]')
|
||||
const titleValue = drawer.locator('input[name="title"]')
|
||||
@@ -427,8 +377,7 @@ describe('Query Presets', () => {
|
||||
|
||||
const presetTitle = 'New Preset'
|
||||
|
||||
await openListMenu({ page })
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Create New' })
|
||||
await page.locator('#create-new-preset').click()
|
||||
const modal = page.locator('[id^=doc-drawer_payload-query-presets_0_]')
|
||||
await expect(modal).toBeVisible()
|
||||
await modal.locator('input[name="title"]').fill(presetTitle)
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
||||
|
||||
it('can auto-schedule through local API and autorun jobs', async () => {
|
||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||
await payload.jobs.handleSchedules()
|
||||
await payload.jobs.handleSchedules({ queue: 'autorunSecond' })
|
||||
|
||||
// Do not call payload.jobs.run{silent: true})
|
||||
|
||||
@@ -88,9 +88,50 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
||||
expect(allSimples?.docs?.[0]?.title).toBe('This task runs every second')
|
||||
})
|
||||
|
||||
it('can auto-schedule through local API and autorun jobs when passing allQueues', async () => {
|
||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||
await payload.jobs.handleSchedules({ queue: 'autorunSecond', allQueues: true })
|
||||
|
||||
// Do not call payload.jobs.run{silent: true})
|
||||
|
||||
await waitUntilAutorunIsDone({
|
||||
payload,
|
||||
queue: 'autorunSecond',
|
||||
onlyScheduled: true,
|
||||
})
|
||||
|
||||
const allSimples = await payload.find({
|
||||
collection: 'simple',
|
||||
limit: 100,
|
||||
})
|
||||
|
||||
expect(allSimples.totalDocs).toBe(1)
|
||||
expect(allSimples?.docs?.[0]?.title).toBe('This task runs every second')
|
||||
})
|
||||
|
||||
it('should not auto-schedule through local API and autorun jobs when not passing queue and schedule is not set on the default queue', async () => {
|
||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||
await payload.jobs.handleSchedules()
|
||||
|
||||
// Do not call payload.jobs.run{silent: true})
|
||||
|
||||
await waitUntilAutorunIsDone({
|
||||
payload,
|
||||
queue: 'autorunSecond',
|
||||
onlyScheduled: true,
|
||||
})
|
||||
|
||||
const allSimples = await payload.find({
|
||||
collection: 'simple',
|
||||
limit: 100,
|
||||
})
|
||||
|
||||
expect(allSimples.totalDocs).toBe(0)
|
||||
})
|
||||
|
||||
it('can auto-schedule through handleSchedules REST API and autorun jobs', async () => {
|
||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||
await restClient.GET('/payload-jobs/handle-schedules', {
|
||||
await restClient.GET('/payload-jobs/handle-schedules?queue=autorunSecond', {
|
||||
headers: {
|
||||
Authorization: `JWT ${token}`,
|
||||
},
|
||||
@@ -115,7 +156,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
||||
|
||||
it('can auto-schedule through run REST API and autorun jobs', async () => {
|
||||
// Do not call payload.jobs.queue() - the `EverySecond` task should be scheduled here
|
||||
await restClient.GET('/payload-jobs/run?silent=true', {
|
||||
await restClient.GET('/payload-jobs/run?silent=true&allQueues=true', {
|
||||
headers: {
|
||||
Authorization: `JWT ${token}`,
|
||||
},
|
||||
@@ -161,7 +202,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
||||
it('ensure scheduler does not schedule more jobs than needed if executed sequentially', async () => {
|
||||
await withoutAutoRun(async () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await payload.jobs.handleSchedules()
|
||||
await payload.jobs.handleSchedules({ allQueues: true })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -192,7 +233,7 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await payload.jobs.handleSchedules()
|
||||
await payload.jobs.handleSchedules({ allQueues: true })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -271,8 +312,8 @@ describe('Queues - scheduling, without automatic scheduling handling', () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await withoutAutoRun(async () => {
|
||||
// Call it twice to test that it only schedules one
|
||||
await payload.jobs.handleSchedules()
|
||||
await payload.jobs.handleSchedules()
|
||||
await payload.jobs.handleSchedules({ allQueues: true })
|
||||
await payload.jobs.handleSchedules({ allQueues: true })
|
||||
})
|
||||
// Advance time to satisfy the waitUntil of newly scheduled jobs
|
||||
timeTravel(20)
|
||||
|
||||
@@ -16,7 +16,7 @@ const AutosavePosts: CollectionConfig = {
|
||||
maxPerDoc: 35,
|
||||
drafts: {
|
||||
autosave: {
|
||||
interval: 2000,
|
||||
interval: 100,
|
||||
},
|
||||
schedulePublish: true,
|
||||
},
|
||||
@@ -53,12 +53,30 @@ const AutosavePosts: CollectionConfig = {
|
||||
unique: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'computedTitle',
|
||||
label: 'Computed Title',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
beforeChange: [({ data }) => data?.title],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -1285,6 +1285,63 @@ describe('Versions', () => {
|
||||
// Remove listener
|
||||
page.removeListener('dialog', acceptAlert)
|
||||
})
|
||||
|
||||
test('- with autosave - applies field hooks to form state after autosave runs', async () => {
|
||||
const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
|
||||
await page.goto(url.create)
|
||||
const titleField = page.locator('#field-title')
|
||||
await titleField.fill('Initial')
|
||||
|
||||
await waitForAutoSaveToRunAndComplete(page)
|
||||
|
||||
const computedTitleField = page.locator('#field-computedTitle')
|
||||
await expect(computedTitleField).toHaveValue('Initial')
|
||||
})
|
||||
|
||||
test('- with autosave - does not override local changes to form state after autosave runs', async () => {
|
||||
const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
|
||||
await page.goto(url.create)
|
||||
const titleField = page.locator('#field-title')
|
||||
|
||||
// press slower than the autosave interval, but not faster than the response and processing
|
||||
await titleField.pressSequentially('Initial', {
|
||||
delay: 150,
|
||||
})
|
||||
|
||||
await waitForAutoSaveToRunAndComplete(page)
|
||||
|
||||
await expect(titleField).toHaveValue('Initial')
|
||||
const computedTitleField = page.locator('#field-computedTitle')
|
||||
await expect(computedTitleField).toHaveValue('Initial')
|
||||
})
|
||||
|
||||
test('- with autosave - does not display success toast after autosave complete', async () => {
|
||||
const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
|
||||
await page.goto(url.create)
|
||||
const titleField = page.locator('#field-title')
|
||||
await titleField.fill('Initial')
|
||||
|
||||
let hasDisplayedToast = false
|
||||
|
||||
const startTime = Date.now()
|
||||
const timeout = 5000
|
||||
const interval = 100
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
const isHidden = await page.locator('.payload-toast-item').isHidden()
|
||||
console.log(`Toast is hidden: ${isHidden}`)
|
||||
|
||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||
if (!isHidden) {
|
||||
hasDisplayedToast = true
|
||||
break
|
||||
}
|
||||
|
||||
await wait(interval)
|
||||
}
|
||||
|
||||
expect(hasDisplayedToast).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Globals - publish individual locale', () => {
|
||||
|
||||
@@ -197,7 +197,14 @@ export interface Post {
|
||||
export interface AutosavePost {
|
||||
id: string;
|
||||
title: string;
|
||||
computedTitle?: string | null;
|
||||
description: string;
|
||||
array?:
|
||||
| {
|
||||
text?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
@@ -366,7 +373,6 @@ export interface Diff {
|
||||
textInNamedTab1InBlock?: string | null;
|
||||
};
|
||||
textInUnnamedTab2InBlock?: string | null;
|
||||
textInUnnamedTab2InBlockAccessFalse?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'TabsBlock';
|
||||
@@ -469,7 +475,6 @@ export interface Diff {
|
||||
};
|
||||
textInUnnamedTab2?: string | null;
|
||||
text?: string | null;
|
||||
textCannotRead?: string | null;
|
||||
textArea?: string | null;
|
||||
upload?: (string | null) | Media;
|
||||
uploadHasMany?: (string | Media)[] | null;
|
||||
@@ -787,7 +792,14 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
*/
|
||||
export interface AutosavePostsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
computedTitle?: T;
|
||||
description?: T;
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
@@ -960,7 +972,6 @@ export interface DiffSelect<T extends boolean = true> {
|
||||
textInNamedTab1InBlock?: T;
|
||||
};
|
||||
textInUnnamedTab2InBlock?: T;
|
||||
textInUnnamedTab2InBlockAccessFalse?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
@@ -995,7 +1006,6 @@ export interface DiffSelect<T extends boolean = true> {
|
||||
};
|
||||
textInUnnamedTab2?: T;
|
||||
text?: T;
|
||||
textCannotRead?: T;
|
||||
textArea?: T;
|
||||
upload?: T;
|
||||
uploadHasMany?: T;
|
||||
|
||||
Reference in New Issue
Block a user