The form component's `initializing` and `processing` states do not disable fields that are rendered outside of `DocumentFields`. Fields currently rely on the `readOnly` prop provided by `DocumentFields` and do not subscribe to these states for themselves. This means that fields that are rendered outright, such as within the bulk edit drawer, they do not receive a `readOnly` prop and are therefore never disabled. The fix is add a `disabled` property to the `useField` hook. This subscribes to the `initializing` and `processing` states in the same way as `DocumentFields`, however, now each field can determine its own disabled state instead of relying solely on the `readOnly` prop. Adding this new prop has no overhead as `processing` and `initializing` is already being subscribed to within `useField`.
193 lines
5.5 KiB
TypeScript
193 lines
5.5 KiB
TypeScript
import type { BrowserContext, Page } from '@playwright/test'
|
|
import type { PayloadTestSDK } from 'helpers/sdk/index.js'
|
|
|
|
import { expect, test } from '@playwright/test'
|
|
import { addBlock } from 'helpers/e2e/addBlock.js'
|
|
import { assertNetworkRequests } from 'helpers/e2e/assertNetworkRequests.js'
|
|
import * as path from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
import type { Config, Post } from './payload-types.js'
|
|
|
|
import {
|
|
ensureCompilationIsDone,
|
|
initPageConsoleErrorCatch,
|
|
saveDocAndAssert,
|
|
throttleTest,
|
|
} from '../helpers.js'
|
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
|
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
|
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
|
|
const title = 'Title'
|
|
let context: BrowserContext
|
|
let payload: PayloadTestSDK<Config>
|
|
let serverURL: string
|
|
|
|
test.describe('Form State', () => {
|
|
let page: Page
|
|
let postsUrl: AdminUrlUtil
|
|
|
|
test.beforeAll(async ({ browser }, testInfo) => {
|
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
|
;({ payload, serverURL } = await initPayloadE2ENoConfig({ dirname }))
|
|
postsUrl = new AdminUrlUtil(serverURL, 'posts')
|
|
|
|
context = await browser.newContext()
|
|
page = await context.newPage()
|
|
initPageConsoleErrorCatch(page)
|
|
await ensureCompilationIsDone({ page, serverURL })
|
|
})
|
|
test.beforeEach(async () => {
|
|
// await throttleTest({ page, context, delay: 'Fast 3G' })
|
|
})
|
|
|
|
test('should disable fields during initialization', async () => {
|
|
await page.goto(postsUrl.create, { waitUntil: 'commit' })
|
|
await expect(page.locator('#field-title')).toBeDisabled()
|
|
})
|
|
|
|
test('should disable fields while processing', async () => {
|
|
const doc = await createPost()
|
|
await page.goto(postsUrl.edit(doc.id))
|
|
await page.locator('#field-title').fill(title)
|
|
await page.click('#action-save', { delay: 100 })
|
|
await expect(page.locator('#field-title')).toBeDisabled()
|
|
})
|
|
|
|
test('should re-enable fields after save', async () => {
|
|
await page.goto(postsUrl.create)
|
|
await page.locator('#field-title').fill(title)
|
|
await saveDocAndAssert(page)
|
|
await expect(page.locator('#field-title')).toBeEnabled()
|
|
})
|
|
|
|
test('should only validate on submit via the `event` argument', async () => {
|
|
await page.goto(postsUrl.create)
|
|
await page.locator('#field-title').fill(title)
|
|
await page.locator('#field-validateUsingEvent').fill('Not allowed')
|
|
await saveDocAndAssert(page, '#action-save', 'error')
|
|
})
|
|
|
|
test('should fire a single network request for onChange events when manipulating blocks', async () => {
|
|
await page.goto(postsUrl.create)
|
|
|
|
await assertNetworkRequests(
|
|
page,
|
|
postsUrl.create,
|
|
async () => {
|
|
await addBlock({
|
|
page,
|
|
blockLabel: 'Text',
|
|
fieldName: 'blocks',
|
|
})
|
|
},
|
|
{
|
|
allowedNumberOfRequests: 1,
|
|
},
|
|
)
|
|
})
|
|
|
|
test('should not throw fields into an infinite rendering loop', async () => {
|
|
await page.goto(postsUrl.create)
|
|
await page.locator('#field-title').fill(title)
|
|
|
|
let numberOfRenders = 0
|
|
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'count' && msg.text().includes('Renders')) {
|
|
numberOfRenders++
|
|
}
|
|
})
|
|
|
|
const allowedNumberOfRenders = 25
|
|
const pollInterval = 200
|
|
const maxTime = 5000
|
|
|
|
let elapsedTime = 0
|
|
|
|
const intervalId = setInterval(() => {
|
|
if (numberOfRenders > allowedNumberOfRenders) {
|
|
clearInterval(intervalId)
|
|
throw new Error(`Render count exceeded the threshold of ${allowedNumberOfRenders}`)
|
|
}
|
|
|
|
elapsedTime += pollInterval
|
|
|
|
if (elapsedTime >= maxTime) {
|
|
clearInterval(intervalId)
|
|
}
|
|
}, pollInterval)
|
|
|
|
await page.waitForTimeout(maxTime)
|
|
|
|
expect(numberOfRenders).toBeLessThanOrEqual(allowedNumberOfRenders)
|
|
})
|
|
|
|
test('should debounce onChange events', async () => {
|
|
await page.goto(postsUrl.create)
|
|
const field = page.locator('#field-title')
|
|
|
|
await assertNetworkRequests(
|
|
page,
|
|
postsUrl.create,
|
|
async () => {
|
|
// Need to type _faster_ than the debounce rate (250ms)
|
|
await field.pressSequentially('Some text to type', { delay: 50 })
|
|
},
|
|
{
|
|
allowedNumberOfRequests: 1,
|
|
},
|
|
)
|
|
})
|
|
|
|
test('should queue onChange functions', async () => {
|
|
await page.goto(postsUrl.create)
|
|
const field = page.locator('#field-title')
|
|
await field.fill('Test')
|
|
|
|
// only throttle test after initial load to avoid timeouts
|
|
const cdpSession = await throttleTest({
|
|
page,
|
|
context,
|
|
delay: 'Slow 3G',
|
|
})
|
|
|
|
await assertNetworkRequests(
|
|
page,
|
|
postsUrl.create,
|
|
async () => {
|
|
await field.fill('')
|
|
// Need to type into a _slower_ than the debounce rate (250ms), but _faster_ than the network request
|
|
await field.pressSequentially('Some text to type', { delay: 275 })
|
|
},
|
|
{
|
|
allowedNumberOfRequests: 2,
|
|
timeout: 10000, // watch network for 10 seconds to allow requests to build up
|
|
},
|
|
)
|
|
|
|
await cdpSession.send('Network.emulateNetworkConditions', {
|
|
offline: false,
|
|
latency: 0,
|
|
downloadThroughput: -1,
|
|
uploadThroughput: -1,
|
|
})
|
|
|
|
await cdpSession.detach()
|
|
})
|
|
})
|
|
|
|
async function createPost(overrides?: Partial<Post>): Promise<Post> {
|
|
return payload.create({
|
|
collection: 'posts',
|
|
data: {
|
|
title: 'Post Title',
|
|
...overrides,
|
|
},
|
|
}) as unknown as Promise<Post>
|
|
}
|