Skip to content

Commit fc73437

Browse files
authoredFeb 29, 2024
fix(form): prevent drop event propagating outside of EditPortal component (#5813)
* feat(core): allow `onDrop` prop in `Dialog` component * fix(form): prevent `drop` event propagating outside of `EditPortal` component * feat(e2e): add e2e helper for creating `DataTransfer` instances for buffers * test(core): ensure `drop` event does not propagate outside of `EditPortal` component * test(core): fix typo * test(core): reorder assertions for clarity
1 parent 16a5434 commit fc73437

File tree

6 files changed

+94
-1
lines changed

6 files changed

+94
-1
lines changed
 

‎packages/sanity/src/core/form/components/EditPortal.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ function onDragEnter(event: DragEvent<HTMLDivElement>) {
2424
return event.stopPropagation()
2525
}
2626

27+
function onDrop(event: DragEvent<HTMLDivElement>) {
28+
return event.stopPropagation()
29+
}
30+
2731
export function EditPortal(props: Props): ReactElement {
2832
const {
2933
children,
@@ -56,6 +60,7 @@ export function EditPortal(props: Props): ReactElement {
5660
onClickOutside={onClose}
5761
onClose={onClose}
5862
onDragEnter={onDragEnter}
63+
onDrop={onDrop}
5964
width={width}
6065
contentRef={setDocumentScrollElement}
6166
__unstable_autoFocus={autofocus}

‎packages/sanity/src/ui-components/dialog/Dialog.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const Dialog = forwardRef(function Dialog(
5656
footer,
5757
padding = true,
5858
...props
59-
}: DialogProps & Pick<React.HTMLProps<HTMLDivElement>, 'onDragEnter'>,
59+
}: DialogProps & Pick<React.HTMLProps<HTMLDivElement>, 'onDragEnter' | 'onDrop'>,
6060
ref: React.Ref<HTMLDivElement>,
6161
) {
6262
const {t} = useTranslation()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {type JSHandle, type Page} from '@playwright/test'
2+
3+
interface Context {
4+
page: Page
5+
}
6+
7+
interface Options {
8+
buffer: Buffer
9+
fileName: string
10+
fileOptions: FilePropertyBag
11+
}
12+
13+
/**
14+
* Create a `DataTransfer` handle containing the provided buffer.
15+
*
16+
* @internal
17+
**/
18+
export function createFileDataTransferHandle(
19+
{page}: Context,
20+
options: Options,
21+
): Promise<JSHandle<DataTransfer>> {
22+
return page.evaluateHandle(
23+
({fileData, fileName, fileOptions}) => {
24+
const dataTransfer = new DataTransfer()
25+
dataTransfer.items.add(new File([new Uint8Array(fileData)], fileName, fileOptions))
26+
return dataTransfer
27+
},
28+
{
29+
...options,
30+
fileData: options.buffer.toJSON().data,
31+
},
32+
)
33+
}

‎test/e2e/helpers/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './constants'
2+
export * from './createFileDataTransferHandle'
23
export * from './createUniqueDocument'
34
export * from './sanityClient'

‎test/e2e/resources/capybara.jpg

2.84 KB
Loading

‎test/e2e/tests/inputs/array.spec.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {readFileSync} from 'node:fs'
2+
import path from 'node:path'
3+
4+
import {expect} from '@playwright/test'
5+
import {test} from '@sanity/test'
6+
7+
import {createFileDataTransferHandle} from '../../helpers'
8+
9+
const fileName = 'capybara.jpg'
10+
const image = readFileSync(path.join(__dirname, '..', '..', 'resources', fileName))
11+
12+
test(`file drop event should not propagate to dialog parent`, async ({
13+
page,
14+
createDraftDocument,
15+
}) => {
16+
await createDraftDocument('/test/content/input-standard;arraysTest')
17+
18+
const list = page.getByTestId('field-arrayOfMultipleTypes').locator('#arrayOfMultipleTypes')
19+
const item = list.locator('[data-ui="Grid"] > div')
20+
21+
const dataTransfer = await createFileDataTransferHandle(
22+
{page},
23+
{
24+
buffer: image,
25+
fileName,
26+
fileOptions: {
27+
type: 'image/jpeg',
28+
},
29+
},
30+
)
31+
32+
// Drop the file.
33+
await list.dispatchEvent('drop', {dataTransfer})
34+
35+
// Ensure the list contains one item.
36+
expect(item).toHaveCount(1)
37+
38+
// Open the dialog.
39+
await page.getByRole('button', {name: fileName}).click()
40+
await expect(page.getByRole('dialog')).toBeVisible()
41+
42+
// Drop the file again; this time, while the dialog is open.
43+
//
44+
// - The drop event should not propagate to the parent.
45+
// - Therefore, the drop event should not cause the image to be added to the list again.
46+
await page.getByRole('dialog').dispatchEvent('drop', {dataTransfer})
47+
48+
// Close the dialog.
49+
await page.keyboard.press('Escape')
50+
await expect(page.getByRole('dialog')).not.toBeVisible()
51+
52+
// Ensure the list still contains one item.
53+
expect(item).toHaveCount(1)
54+
})

0 commit comments

Comments
 (0)
Please sign in to comment.