Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: capricorn86/happy-dom
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v13.8.0
Choose a base ref
...
head repository: capricorn86/happy-dom
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 08cd42601d62f39d42d01d902a56d2441f7128e0
Choose a head ref
  • 2 commits
  • 7 files changed
  • 1 contributor

Commits on Mar 12, 2024

  1. fix: [#1153] Fixes problem with ClipboardItem not supporting text and…

    … Promise as data type, causing React tests to fail, as testing-library overrides the Clipboard implementation with an implementation relying on Promise as data type
    capricorn86 committed Mar 12, 2024
    Copy the full SHA
    03c7b92 View commit details
  2. Merge pull request #1300 from capricorn86/1153-copy-to-clipboard-fails

    fix: [#1153] Fixes problem with ClipboardItem not supporting text and…
    capricorn86 authored Mar 12, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    08cd426 View commit details
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion packages/happy-dom/src/clipboard/Clipboard.ts
Original file line number Diff line number Diff line change
@@ -54,7 +54,13 @@ export default class Clipboard {
let text = '';
for (const item of this.#data) {
if (item.types.includes('text/plain')) {
text += await (await item.getType('text/plain')).text();
const data = await item.getType('text/plain');
if (typeof data === 'string') {
text += data;
} else {
// Instance of Blob
text += await data.text();
}
}
}
return text;
11 changes: 3 additions & 8 deletions packages/happy-dom/src/clipboard/ClipboardItem.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import Blob from '../file/Blob.js';
*/
export default class ClipboardItem {
public readonly presentationStyle: 'unspecified' | 'inline' | 'attachment' = 'unspecified';
#data: { [mimeType: string]: Blob };
#data: { [mimeType: string]: Blob | string | Promise<Blob | string> };

/**
* Constructor.
@@ -19,14 +19,9 @@ export default class ClipboardItem {
* @param [options.presentationStyle] Presentation style.
*/
constructor(
data: { [mimeType: string]: Blob },
data: { [mimeType: string]: Blob | string | Promise<Blob | string> },
options?: { presentationStyle?: 'unspecified' | 'inline' | 'attachment' }
) {
for (const mimeType of Object.keys(data)) {
if (mimeType !== data[mimeType].type) {
throw new DOMException(`Type ${mimeType} does not match the blob's type`);
}
}
this.#data = data;
if (options?.presentationStyle) {
this.presentationStyle = options.presentationStyle;
@@ -48,7 +43,7 @@ export default class ClipboardItem {
* @param type Type.
* @returns Data.
*/
public async getType(type: string): Promise<Blob> {
public async getType(type: string): Promise<Blob | string> {
if (!this.#data[type]) {
throw new DOMException(
`Failed to execute 'getType' on 'ClipboardItem': The type '${type}' was not found`
41 changes: 36 additions & 5 deletions packages/happy-dom/test/clipboard/Clipboard.test.ts
Original file line number Diff line number Diff line change
@@ -15,15 +15,37 @@ describe('Clipboard', () => {
it('Reads from the clipboard.', async () => {
const items = [
new ClipboardItem({
'text/plain': new Blob(['test'], { type: 'text/plain' })
'text/plain': new Blob(['test-a'], { type: 'text/plain' })
}),
new ClipboardItem({
'text/html': new Blob(['<b>test</b>'], { type: 'text/html' })
'text/html': new Blob(['<b>test-b</b>'], { type: 'text/html' })
}),
new ClipboardItem({
'text/plain': 'test-c'
}),
new ClipboardItem({
'text/plain': Promise.resolve('test-d')
}),
new ClipboardItem({
'text/plain': Promise.resolve(new Blob(['test-e'], { type: 'text/plain' }))
})
];
await window.navigator.clipboard.write(items);
const data = await window.navigator.clipboard.read();
expect(data).toEqual(items);

let text = '';

for (const item of data) {
const data = await item.getType(item.types[0]);
if (typeof data === 'string') {
text += data;
} else {
text += await data.text();
}
}

expect(text).toBe('test-a<b>test-b</b>test-ctest-dtest-e');
});

it('Throws an error if the permission is denied.', async () => {
@@ -50,15 +72,24 @@ describe('Clipboard', () => {
it('Reads text from the clipboard.', async () => {
const items = [
new ClipboardItem({
'text/plain': new Blob(['test'], { type: 'text/plain' })
'text/plain': new Blob(['test-a'], { type: 'text/plain' })
}),
new ClipboardItem({
'text/html': new Blob(['<b>test</b>'], { type: 'text/html' })
'text/html': new Blob(['<b>test-b</b>'], { type: 'text/html' })
}),
new ClipboardItem({
'text/plain': 'test-c'
}),
new ClipboardItem({
'text/plain': Promise.resolve('test-d')
}),
new ClipboardItem({
'text/plain': Promise.resolve(new Blob(['test-e'], { type: 'text/plain' }))
})
];
await window.navigator.clipboard.write(items);
const data = await window.navigator.clipboard.readText();
expect(data).toBe('test');
expect(data).toBe('test-atest-ctest-dtest-e');
});

it('Throws an error if the permission is denied.', async () => {
1 change: 1 addition & 0 deletions packages/jest-environment/package.json
Original file line number Diff line number Diff line change
@@ -81,6 +81,7 @@
"prettier": "^2.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"usehooks-ts": "^3.0.1",
"rxjs": "^6.5.3",
"ts-jest": "^29.1.1",
"typescript": "^5.0.4",
15 changes: 14 additions & 1 deletion packages/jest-environment/test/react/React.test.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,12 @@ import ReactDOM from 'react-dom/client';
import * as ReactTestingLibrary from '@testing-library/react';
import ReactTestingLibraryUserEvent from '@testing-library/user-event';
import { act } from 'react-dom/test-utils';
import { ReactDivComponent, ReactSelectComponent, ReactInputComponent } from './ReactComponents';
import {
ReactDivComponent,
ReactSelectComponent,
ReactInputComponent,
ReactClipboardComponent
} from './ReactComponents';
import * as Select from '@radix-ui/react-select';

/* eslint-disable @typescript-eslint/consistent-type-assertions */
@@ -97,4 +102,12 @@ describe('React', () => {
'<app><button type="button" role="combobox" aria-controls="radix-:r0:" aria-expanded="false" aria-autocomplete="none" dir="ltr" data-state="closed" data-placeholder=""><span style="pointer-events: none;"></span><span aria-hidden="true">▼</span></button></app>'
);
});

it('Can use copy to clipboard hook component', async () => {
const { getByRole } = ReactTestingLibrary.render(<ReactClipboardComponent />);
expect(document.querySelector('p span').textContent).toBe('Nothing');
const button: HTMLButtonElement = getByRole('button') as HTMLButtonElement;
await TESTING_LIBRARY_USER.click(button);
expect(document.querySelector('p span').textContent).toBe('test');
});
});
21 changes: 21 additions & 0 deletions packages/jest-environment/test/react/ReactComponents.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { useCopyToClipboard } from 'usehooks-ts';

/* eslint-disable @typescript-eslint/consistent-type-assertions */

@@ -73,3 +74,23 @@ export class ReactInputComponent extends React.Component<{}, {}> {
return <input placeholder="input field" />;
}
}

/**
*
*/
export function ReactClipboardComponent(): React.ReactElement {
const [copiedText, copy] = useCopyToClipboard();

const handleCopy = (text: string) => () => {
copy(text);
};

return (
<>
<button onClick={handleCopy('test')}>A</button>
<p>
Copied value: <span>{copiedText ?? 'Nothing'}</span>
</p>
</>
);
}