Skip to content

Commit

Permalink
#833@trivial: Adds unit tests for Clipboard.
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 committed Oct 3, 2023
1 parent 83f5269 commit 3b97d1a
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 39 deletions.
10 changes: 7 additions & 3 deletions packages/happy-dom/src/clipboard/Clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,15 @@ export default class Clipboard {
name: 'clipboard-read'
});
if (permissionStatus.state === 'denied') {
throw new DOMException(`Failed to execute 'read' on 'Clipboard': The request is not allowed`);
throw new DOMException(
`Failed to execute 'readText' on 'Clipboard': The request is not allowed`
);
}
let text = '';
for (const item of this.#data) {
text += await (await item.getType('text/plain')).text();
if (item.types.includes('text/plain')) {
text += await (await item.getType('text/plain')).text();
}
}
return text;
}
Expand Down Expand Up @@ -84,7 +88,7 @@ export default class Clipboard {
});
if (permissionStatus.state === 'denied') {
throw new DOMException(
`Failed to execute 'write' on 'Clipboard': The request is not allowed`
`Failed to execute 'writeText' on 'Clipboard': The request is not allowed`
);
}
this.#data = [new ClipboardItem({ 'text/plain': new Blob([text], { type: 'text/plain' }) })];
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/clipboard/ClipboardItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default class ClipboardItem {
public async getType(type: string): Promise<Blob> {
if (!this.#data[type]) {
throw new DOMException(
"Failed to execute 'getType' on 'ClipboardItem': The type was not found"
`Failed to execute 'getType' on 'ClipboardItem': The type '${type}' was not found`
);
}
return this.#data[type];
Expand Down
32 changes: 32 additions & 0 deletions packages/happy-dom/src/permissions/PermissionNameEnum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
enum PermissionNameEnum {
geolocation = 'geolocation',
notifications = 'notifications',
push = 'push',
midi = 'midi',
camera = 'camera',
microphone = 'microphone',
backgroundFetch = 'background-fetch',
backgroundSync = 'background-sync',
persistentStorage = 'persistent-storage',
ambientLightSensor = 'ambient-light-sensor',
accelerometer = 'accelerometer',
gyroscope = 'gyroscope',
magnetometer = 'magnetometer',
screenWakeLock = 'screen-wake-lock',
nfc = 'nfc',
displayCapture = 'display-capture',
accessibilityEvents = 'accessibility-events',
clipboardRead = 'clipboard-read',
clipboardWrite = 'clipboard-write',
paymentHandler = 'payment-handler',
idleDetection = 'idle-detection',
periodicBackgroundSync = 'periodic-background-sync',
systemWakeLock = 'system-wake-lock',
storageAccess = 'storage-access',
windowManagement = 'window-management',
windowPlacement = 'window-placement',
localFonts = 'local-fonts',
topLevelStorageAccess = 'top-level-storage-access'
}

export default PermissionNameEnum;
2 changes: 1 addition & 1 deletion packages/happy-dom/src/permissions/PermissionStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Event from '../event/Event.js';
* https://developer.mozilla.org/en-US/docs/Web/API/PermissionStatus
*/
export default class PermissionStatus extends EventTarget {
public readonly state: 'granted' | 'denied' | 'prompt';
public state: 'granted' | 'denied' | 'prompt';
public onchange: ((event: Event) => void) | null = null;

/**
Expand Down
51 changes: 18 additions & 33 deletions packages/happy-dom/src/permissions/Permissions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PermissionStatus from './PermissionStatus.js';
import PermissionNameEnum from './PermissionNameEnum.js';

/**
* Permissions API.
Expand All @@ -7,6 +8,10 @@ import PermissionStatus from './PermissionStatus.js';
* https://developer.mozilla.org/en-US/docs/Web/API/Permissions.
*/
export default class Permissions {
#permissionStatus: {
[name in PermissionNameEnum]?: PermissionStatus;
} = {};

/**
* Returns scroll restoration.
*
Expand All @@ -21,40 +26,20 @@ export default class Permissions {
userVisibleOnly?: boolean;
sysex?: boolean;
}): Promise<PermissionStatus> {
switch (permissionDescriptor.name) {
case 'geolocation':
case 'notifications':
case 'push':
case 'midi':
case 'camera':
case 'microphone':
case 'background-fetch':
case 'background-sync':
case 'persistent-storage':
case 'ambient-light-sensor':
case 'accelerometer':
case 'gyroscope':
case 'magnetometer':
case 'screen-wake-lock':
case 'nfc':
case 'display-capture':
case 'accessibility-events':
case 'clipboard-read':
case 'clipboard-write':
case 'payment-handler':
case 'idle-detection':
case 'periodic-background-sync':
case 'system-wake-lock':
case 'storage-access':
case 'window-management':
case 'window-placement':
case 'local-fonts':
case 'top-level-storage-access':
return new PermissionStatus('granted');
if (this.#permissionStatus[permissionDescriptor.name]) {
return this.#permissionStatus[permissionDescriptor.name];
}

if (
!Object.values(PermissionNameEnum).includes(<PermissionNameEnum>permissionDescriptor.name)
) {
throw new Error(
`Failed to execute 'query' on 'Permissions': Failed to read the 'name' property from 'PermissionDescriptor': The provided value '${permissionDescriptor.name}' is not a valid enum value of type PermissionName.`
);
}

throw new Error(
`Failed to execute 'query' on 'Permissions': Failed to read the 'name' property from 'PermissionDescriptor': The provided value '${permissionDescriptor.name}' is not a valid enum value of type PermissionName.`
);
this.#permissionStatus[permissionDescriptor.name] = new PermissionStatus('granted');

return this.#permissionStatus[permissionDescriptor.name];
}
}
146 changes: 146 additions & 0 deletions packages/happy-dom/test/clipboard/Clipboard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import ClipboardItem from '../../src/clipboard/ClipboardItem.js';
import Blob from '../../src/file/Blob.js';
import Window from '../../src/window/Window.js';
import IWindow from '../../src/window/IWindow.js';
import { beforeEach, describe, it, expect } from 'vitest';

describe('Clipboard', () => {
let window: IWindow;

beforeEach(() => {
window = new Window();
});

describe('read()', () => {
it('Reads from the clipboard.', async () => {
const items = [
new ClipboardItem({
'text/plain': new Blob(['test'], { type: 'text/plain' })
}),
new ClipboardItem({
'text/html': new Blob(['<b>test</b>'], { type: 'text/html' })
})
];
await window.navigator.clipboard.write(items);
const data = await window.navigator.clipboard.read();
expect(data).toEqual(items);
});

it('Throws an error if the permission is denied.', async () => {
const permissionStatus = await window.navigator.permissions.query({
name: 'clipboard-read'
});
permissionStatus.state = 'denied';

let error: Error | null = null;

try {
await window.navigator.clipboard.read();
} catch (e) {
error = e;
}

expect(error?.message).toBe(
"Failed to execute 'read' on 'Clipboard': The request is not allowed"
);
});
});

describe('readText()', () => {
it('Reads text from the clipboard.', async () => {
const items = [
new ClipboardItem({
'text/plain': new Blob(['test'], { type: 'text/plain' })
}),
new ClipboardItem({
'text/html': new Blob(['<b>test</b>'], { type: 'text/html' })
})
];
await window.navigator.clipboard.write(items);
const data = await window.navigator.clipboard.readText();
expect(data).toBe('test');
});

it('Throws an error if the permission is denied.', async () => {
const permissionStatus = await window.navigator.permissions.query({
name: 'clipboard-read'
});
permissionStatus.state = 'denied';

let error: Error | null = null;

try {
await window.navigator.clipboard.readText();
} catch (e) {
error = e;
}

expect(error?.message).toBe(
"Failed to execute 'readText' on 'Clipboard': The request is not allowed"
);
});
});

describe('write()', () => {
it('Writes to the clipboard.', async () => {
const items = [
new ClipboardItem({
'text/plain': new Blob(['test'], { type: 'text/plain' })
}),
new ClipboardItem({
'text/html': new Blob(['<b>test</b>'], { type: 'text/html' })
})
];
await window.navigator.clipboard.write(items);
const data = await window.navigator.clipboard.read();
expect(data).toEqual(items);
});

it('Throws an error if the permission is denied.', async () => {
const permissionStatus = await window.navigator.permissions.query({
name: 'clipboard-write'
});
permissionStatus.state = 'denied';

let error: Error | null = null;

try {
await window.navigator.clipboard.write([]);
} catch (e) {
error = e;
}

expect(error?.message).toBe(
"Failed to execute 'write' on 'Clipboard': The request is not allowed"
);
});
});

describe('writeText()', () => {
it('Writes text to the clipboard.', async () => {
const text = 'test';
await window.navigator.clipboard.writeText(text);
const data = await window.navigator.clipboard.readText();
expect(data).toBe(text);
});

it('Throws an error if the permission is denied.', async () => {
const permissionStatus = await window.navigator.permissions.query({
name: 'clipboard-write'
});
permissionStatus.state = 'denied';

let error: Error | null = null;

try {
await window.navigator.clipboard.writeText('test');
} catch (e) {
error = e;
}

expect(error?.message).toBe(
"Failed to execute 'writeText' on 'Clipboard': The request is not allowed"
);
});
});
});
5 changes: 4 additions & 1 deletion packages/happy-dom/test/window/Window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import '../types.d.js';
import { beforeEach, afterEach, describe, it, expect, vi } from 'vitest';
import VirtualConsole from '../../src/console/VirtualConsole.js';
import VirtualConsolePrinter from '../../src/console/VirtualConsolePrinter.js';
import Permissions from '../../src/permissions/Permissions.js';
import Clipboard from '../../src/clipboard/Clipboard.js';
import PackageVersion from '../../src/version.js';
import { IHTMLDialogElement } from '../../src/index.js';

Expand Down Expand Up @@ -476,7 +478,8 @@ describe('Window', () => {
length: 0
},
onLine: true,
permissions: null,
permissions: new Permissions(),
clipboard: new Clipboard(window),
platform,
plugins: {
length: 0
Expand Down

0 comments on commit 3b97d1a

Please sign in to comment.