Files
pezkuwi-dev/packages/dev-test/src/env/browser.spec.ts
T
Satoshi Qazi Muhammed 4e6a40ad14 dev-test: expose location in the browser environment
The browser environment copied window, document, navigator and localStorage
onto the global, but not location. Code that reads the bare global, such as
the apps URL type decoder, threw "location is not defined" while an
ApiPromise was being created, so the api never became ready. location is now
exposed with the other window objects.
2026-10-11 07:22:37 +04:00

114 lines
3.9 KiB
TypeScript

// Copyright 2017-2026 @pezkuwi/dev-test authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { createHash } from 'node:crypto';
import { createServer } from 'node:http';
import { browser } from './browser.js';
const all = browser();
describe('browser', () => {
it('contains window', () => {
expect(all.window).toBeDefined();
});
it('contains a crypto implementation', () => {
expect(all.crypto).toBeTruthy();
expect(typeof all.crypto.getRandomValues).toBe('function');
});
it('contains the top-level objects', () => {
expect(all.document).toBeDefined();
expect(all.navigator).toBeDefined();
expect(location.href).toBe('http://localhost/');
});
it('contains HTML*Element', () => {
expect(typeof all.HTMLElement).toBe('function');
});
it('contains the DOM node hierarchy', () => {
const div = document.createElement('div');
expect(div).toBeInstanceOf(Element);
expect(div).toBeInstanceOf(Node);
expect(document.createTextNode('text')).toBeInstanceOf(Text);
expect(document.createDocumentFragment()).toBeInstanceOf(DocumentFragment);
expect(document.createElementNS('http://www.w3.org/2000/svg', 'svg')).toBeInstanceOf(SVGElement);
expect(div.attachShadow({ mode: 'open' })).toBeInstanceOf(ShadowRoot);
expect(document).toBeInstanceOf(Document);
});
it('contains the UI event constructors', () => {
const div = document.createElement('div');
expect(() => div.dispatchEvent(new MouseEvent('click'))).not.toThrow();
expect(() => div.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))).not.toThrow();
expect(new FocusEvent('focus')).toBeInstanceOf(UIEvent);
});
it('contains getComputedStyle and the observers', () => {
const div = document.createElement('div');
div.style.display = 'none';
document.body.appendChild(div);
expect(getComputedStyle(div).display).toBe('none');
expect(() => new MutationObserver(() => undefined).observe(div, { childList: true })).not.toThrow();
expect(() => new ResizeObserver(() => undefined).observe(div)).not.toThrow();
div.remove();
});
it('dispatches the global CustomEvent and Event on window', () => {
expect(() => window.dispatchEvent(new CustomEvent('custom', { detail: 1 }))).not.toThrow();
expect(() => window.dispatchEvent(new Event('plain'))).not.toThrow();
});
it('opens a WebSocket and exchanges a message', async () => {
// the smallest server that completes the RFC 6455 handshake and sends one text frame
const server = createServer();
// an upgraded socket leaves the server's tracking, so it is closed here
const sockets: { destroy: () => void }[] = [];
server.on('upgrade', (req, socket) => {
sockets.push(socket);
const accept = createHash('sha1')
.update(`${String(req.headers['sec-websocket-key'])}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
.digest('base64');
socket.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n`);
socket.write(Buffer.from([0x81, 0x02, 0x68, 0x69]));
});
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
const { port } = server.address() as { port: number };
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
try {
const received = await new Promise<string>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('no message within 5s')), 5_000);
ws.onmessage = (event) => {
clearTimeout(timer);
resolve(String(event.data));
};
ws.onerror = () => {
clearTimeout(timer);
reject(new Error('WebSocket error'));
};
});
expect(received).toBe('hi');
} finally {
ws.close();
sockets.forEach((socket) => socket.destroy());
await new Promise((resolve) => server.close(resolve));
}
});
});