Skip to content

Commit 8c3c4b6

Browse files
authored
Merge branch 'main' into all-contributors/add-tbertrand7
2 parents 30e1d40 + 126d2e7 commit 8c3c4b6

File tree

15 files changed

+104
-61
lines changed

15 files changed

+104
-61
lines changed

src/clipboard/paste.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import {Config, Instance} from '../setup'
2-
import {createDataTransfer, readDataTransferFromClipboard} from '../utils'
2+
import {
3+
createDataTransfer,
4+
getWindow,
5+
readDataTransferFromClipboard,
6+
} from '../utils'
37

48
export async function paste(
59
this: Instance,
@@ -10,7 +14,7 @@ export async function paste(
1014

1115
const dataTransfer: DataTransfer =
1216
(typeof clipboardData === 'string'
13-
? getClipboardDataFromString(clipboardData)
17+
? getClipboardDataFromString(doc, clipboardData)
1418
: clipboardData) ??
1519
(await readDataTransferFromClipboard(doc).catch(() => {
1620
throw new Error(
@@ -23,8 +27,8 @@ export async function paste(
2327
})
2428
}
2529

26-
function getClipboardDataFromString(text: string) {
27-
const dt = createDataTransfer()
30+
function getClipboardDataFromString(doc: Document, text: string) {
31+
const dt = createDataTransfer(getWindow(doc))
2832
dt.setData('text', text)
2933
return dt
3034
}

src/event/behavior/click.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import {blur, cloneEvent, focus, isElementType, isFocusable} from '../../utils'
1+
import {
2+
blur,
3+
cloneEvent,
4+
focus,
5+
getWindow,
6+
isElementType,
7+
isFocusable,
8+
} from '../../utils'
29
import {dispatchEvent} from '../dispatchEvent'
310
import {behavior} from './registry'
411

@@ -17,7 +24,7 @@ behavior.click = (event, target, config) => {
1724
// blur fires when the file selector pops up
1825
blur(target)
1926

20-
target.dispatchEvent(new Event('fileDialog'))
27+
target.dispatchEvent(new (getWindow(target).Event)('fileDialog'))
2128

2229
// focus fires after the file selector has been closed
2330
focus(target)

src/pointer/pointerPress.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ function mousedownDefaultBehavior({
315315
offset: end,
316316
})
317317

318-
const range = new Range()
318+
const range = target.ownerDocument.createRange()
319319
range.setStart(startNode, startOffset)
320320
range.setEnd(endNode, endOffset)
321321

src/utils/dataTransfer/Blob.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// jsdom does not implement Blob.text()
22

3-
export function readBlobText(blob: Blob) {
3+
export function readBlobText(blob: Blob, FileReader: {new (): FileReader}) {
44
return new Promise<string>((res, rej) => {
55
const fr = new FileReader()
66
fr.onerror = rej

src/utils/dataTransfer/Clipboard.ts

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
// Clipboard is not available in jsdom
22

33
import {createDataTransfer, getBlobFromDataTransferItem, readBlobText} from '..'
4+
import {getWindow} from '../misc/getWindow'
45

56
// Clipboard API is only fully available in secure context or for browser extensions.
67

8+
const Window = Symbol('Window reference')
9+
710
type ItemData = Record<string, Blob | string | Promise<Blob | string>>
811

912
class ClipboardItemStub implements ClipboardItem {
@@ -25,13 +28,17 @@ class ClipboardItemStub implements ClipboardItem {
2528
)
2629
}
2730

28-
return data instanceof Blob ? data : new Blob([data], {type})
31+
return data instanceof this[Window].Blob
32+
? data
33+
: new this[Window].Blob([data], {type})
2934
}
35+
36+
[Window] = window
3037
}
3138

3239
const ClipboardStubControl = Symbol('Manage ClipboardSub')
3340

34-
class ClipboardStub extends EventTarget implements Clipboard {
41+
class ClipboardStub extends window.EventTarget implements Clipboard {
3542
private items: ClipboardItem[] = []
3643

3744
async read() {
@@ -45,7 +52,9 @@ class ClipboardStub extends EventTarget implements Clipboard {
4552
? 'text/plain'
4653
: item.types.find(t => t.startsWith('text/'))
4754
if (type) {
48-
text += await item.getType(type).then(b => readBlobText(b))
55+
text += await item
56+
.getType(type)
57+
.then(b => readBlobText(b, this[Window].FileReader))
4958
}
5059
}
5160
return text
@@ -56,9 +65,10 @@ class ClipboardStub extends EventTarget implements Clipboard {
5665
}
5766

5867
async writeText(text: string) {
59-
this.items = [createClipboardItem(text)]
68+
this.items = [createClipboardItem(this[Window], text)]
6069
}
6170

71+
[Window] = window;
6272
[ClipboardStubControl]: {
6373
resetClipboardStub: () => void
6474
detachClipboardStub: () => void
@@ -69,22 +79,25 @@ class ClipboardStub extends EventTarget implements Clipboard {
6979
// lib.dom.d.ts lists only Promise<Blob|string>
7080
// https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/ClipboardItem#syntax
7181
export function createClipboardItem(
82+
window: Window & typeof globalThis,
7283
...blobs: Array<Blob | string>
7384
): ClipboardItem {
74-
// use real ClipboardItem if available
75-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
76-
const constructor =
77-
typeof ClipboardItem === 'undefined'
78-
? ClipboardItemStub
79-
: /* istanbul ignore next */ ClipboardItem
80-
return new constructor(
81-
Object.fromEntries(
82-
blobs.map(b => [
83-
typeof b === 'string' ? 'text/plain' : b.type,
84-
Promise.resolve(b),
85-
]),
86-
),
85+
const data = Object.fromEntries(
86+
blobs.map(b => [
87+
typeof b === 'string' ? 'text/plain' : b.type,
88+
Promise.resolve(b),
89+
]),
8790
)
91+
92+
// use real ClipboardItem if available
93+
/* istanbul ignore else */
94+
if (typeof window.ClipboardItem === 'undefined') {
95+
const item = new ClipboardItemStub(data)
96+
item[Window] = window
97+
return item
98+
} else {
99+
return new window.ClipboardItem(data)
100+
}
88101
}
89102

90103
export function attachClipboardStubToView(window: Window & typeof globalThis) {
@@ -98,9 +111,11 @@ export function attachClipboardStubToView(window: Window & typeof globalThis) {
98111
)
99112

100113
let stub = new ClipboardStub()
114+
stub[Window] = window
101115
const control = {
102116
resetClipboardStub: () => {
103117
stub = new ClipboardStub()
118+
stub[Window] = window
104119
stub[ClipboardStubControl] = control
105120
},
106121
detachClipboardStub: () => {
@@ -140,17 +155,21 @@ export function detachClipboardStubFromView(
140155
}
141156

142157
export async function readDataTransferFromClipboard(document: Document) {
143-
const clipboard = document.defaultView?.navigator.clipboard
158+
const window = document.defaultView
159+
const clipboard = window?.navigator.clipboard
144160
const items = clipboard && (await clipboard.read())
145161

146162
if (!items) {
147163
throw new Error('The Clipboard API is unavailable.')
148164
}
149165

150-
const dt = createDataTransfer()
166+
const dt = createDataTransfer(window)
151167
for (const item of items) {
152168
for (const type of item.types) {
153-
dt.setData(type, await item.getType(type).then(b => readBlobText(b)))
169+
dt.setData(
170+
type,
171+
await item.getType(type).then(b => readBlobText(b, window.FileReader)),
172+
)
154173
}
155174
}
156175
return dt
@@ -160,13 +179,14 @@ export async function writeDataTransferToClipboard(
160179
document: Document,
161180
clipboardData: DataTransfer,
162181
) {
163-
const clipboard = document.defaultView?.navigator.clipboard
182+
const window = getWindow(document)
183+
const clipboard = window.navigator.clipboard as Clipboard | undefined
164184

165185
const items = []
166186
for (let i = 0; i < clipboardData.items.length; i++) {
167187
const dtItem = clipboardData.items[i]
168-
const blob = getBlobFromDataTransferItem(dtItem)
169-
items.push(createClipboardItem(blob))
188+
const blob = getBlobFromDataTransferItem(window, dtItem)
189+
items.push(createClipboardItem(window, blob))
170190
}
171191

172192
const written =

src/utils/dataTransfer/DataTransfer.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,26 +133,31 @@ class DataTransferStub implements DataTransfer {
133133
setDragImage() {}
134134
}
135135

136-
export function createDataTransfer(files: File[] = []): DataTransfer {
136+
export function createDataTransfer(
137+
window: Window & typeof globalThis,
138+
files: File[] = [],
139+
): DataTransfer {
137140
// Use real DataTransfer if available
138-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
139141
const dt =
140-
typeof DataTransfer === 'undefined'
142+
typeof window.DataTransfer === 'undefined'
141143
? (new DataTransferStub() as DataTransfer)
142-
: /* istanbul ignore next */ new DataTransfer()
144+
: /* istanbul ignore next */ new window.DataTransfer()
143145

144146
Object.defineProperty(dt, 'files', {get: () => createFileList(files)})
145147

146148
return dt
147149
}
148150

149-
export function getBlobFromDataTransferItem(item: DataTransferItem) {
151+
export function getBlobFromDataTransferItem(
152+
window: Window & typeof globalThis,
153+
item: DataTransferItem,
154+
) {
150155
if (item.kind === 'file') {
151156
return item.getAsFile() as File
152157
}
153158
let data: string = ''
154159
item.getAsString(s => {
155160
data = s
156161
})
157-
return new Blob([data], {type: item.type})
162+
return new window.Blob([data], {type: item.type})
158163
}

src/utils/focus/copySelection.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {getUISelection, getUIValue} from '../../document'
22
import {createDataTransfer} from '../dataTransfer/DataTransfer'
33
import {EditableInputType} from '../edit/isEditable'
4+
import {getWindow} from '../misc/getWindow'
45
import {hasOwnSelection} from './selection'
56

67
export function copySelection(target: Element) {
@@ -9,7 +10,7 @@ export function copySelection(target: Element) {
910
: // TODO: We could implement text/html copying of DOM nodes here
1011
{'text/plain': String(target.ownerDocument.getSelection())}
1112

12-
const dt = createDataTransfer()
13+
const dt = createDataTransfer(getWindow(target))
1314
for (const type in data) {
1415
if (data[type]) {
1516
dt.setData(type, data[type])

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export * from './misc/cloneEvent'
3131
export * from './misc/eventWrapper'
3232
export * from './misc/findClosest'
3333
export * from './misc/getDocumentFromNode'
34+
export * from './misc/getWindow'
3435
export * from './misc/isDescendantOrSelf'
3536
export * from './misc/isElementType'
3637
export * from './misc/isVisible'

src/utils/misc/getWindow.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {getWindowFromNode} from '@testing-library/dom/dist/helpers.js'
2+
3+
export function getWindow(node: Node) {
4+
return getWindowFromNode(node) as Window & typeof globalThis
5+
}

src/utils/misc/isVisible.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {getWindowFromNode} from '@testing-library/dom/dist/helpers.js'
1+
import {getWindow} from './getWindow'
22

33
export function isVisible(element: Element): boolean {
4-
const window = getWindowFromNode(element)
4+
const window = getWindow(element)
55

66
for (
77
let el: Element | null = element;

0 commit comments

Comments
 (0)