109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
installArrayAtPolyfill,
|
|
installObjectHasOwnPolyfill,
|
|
installPromiseWithResolversPolyfill,
|
|
installStringAtPolyfill,
|
|
installStructuredClonePolyfill,
|
|
installUrlCanParsePolyfill,
|
|
} from './polyfills';
|
|
|
|
const descriptors = new Map<
|
|
string,
|
|
[object, PropertyKey, PropertyDescriptor | undefined]
|
|
>();
|
|
|
|
function remember(key: string, target: object, property: PropertyKey) {
|
|
descriptors.set(key, [
|
|
target,
|
|
property,
|
|
Object.getOwnPropertyDescriptor(target, property),
|
|
]);
|
|
}
|
|
|
|
function remove(target: object, property: PropertyKey) {
|
|
Reflect.deleteProperty(target, property);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
remember('arrayAt', Array.prototype, 'at');
|
|
remember('stringAt', String.prototype, 'at');
|
|
remember('hasOwn', Object, 'hasOwn');
|
|
remember('withResolvers', Promise, 'withResolvers');
|
|
remember('structuredClone', globalThis, 'structuredClone');
|
|
remember('canParse', URL, 'canParse');
|
|
});
|
|
|
|
afterEach(() => {
|
|
for (const [target, property, descriptor] of descriptors.values()) {
|
|
if (descriptor) Object.defineProperty(target, property, descriptor);
|
|
else remove(target, property);
|
|
}
|
|
descriptors.clear();
|
|
});
|
|
|
|
describe('chrome 90 browser polyfills', () => {
|
|
it('installs Array.prototype.at and String.prototype.at', () => {
|
|
remove(Array.prototype, 'at');
|
|
remove(String.prototype, 'at');
|
|
|
|
installArrayAtPolyfill();
|
|
installStringAtPolyfill();
|
|
|
|
expect(['first', 'last'].at(-1)).toBe('last');
|
|
expect('EasyFlow'.at(-1)).toBe('w');
|
|
expect('EasyFlow'.at(99)).toBeUndefined();
|
|
});
|
|
|
|
it('installs Object.hasOwn without accepting inherited properties', () => {
|
|
remove(Object, 'hasOwn');
|
|
installObjectHasOwnPolyfill();
|
|
|
|
const value = Object.create({ inherited: true }) as { own?: boolean };
|
|
value.own = true;
|
|
expect(Object.hasOwn(value, 'own')).toBe(true);
|
|
expect(Object.hasOwn(value, 'inherited')).toBe(false);
|
|
});
|
|
|
|
it('installs Promise.withResolvers and exposes working callbacks', async () => {
|
|
remove(Promise, 'withResolvers');
|
|
installPromiseWithResolversPolyfill();
|
|
|
|
// eslint-disable-next-line n/no-unsupported-features/es-syntax -- 测试目标就是验证该 API 的兼容实现。
|
|
const { promise, resolve } = Promise.withResolvers<string>();
|
|
resolve('ready');
|
|
await expect(promise).resolves.toBe('ready');
|
|
});
|
|
|
|
it('installs structuredClone with complex value and cycle support', () => {
|
|
remove(globalThis, 'structuredClone');
|
|
installStructuredClonePolyfill();
|
|
|
|
const source: {
|
|
createdAt: Date;
|
|
lookup: Map<string, number>;
|
|
self?: unknown;
|
|
} = {
|
|
createdAt: new Date('2026-07-15T00:00:00.000Z'),
|
|
lookup: new Map([['answer', 2026]]),
|
|
};
|
|
source.self = source;
|
|
const clone = structuredClone(source);
|
|
|
|
expect(clone).not.toBe(source);
|
|
expect(clone.self).toBe(clone);
|
|
expect(clone.createdAt).toEqual(source.createdAt);
|
|
expect(clone.lookup.get('answer')).toBe(2026);
|
|
});
|
|
|
|
it('installs URL.canParse for absolute and base-relative URLs', () => {
|
|
remove(URL, 'canParse');
|
|
installUrlCanParsePolyfill();
|
|
|
|
expect(URL.canParse('https://easyflow.example/chat')).toBe(true);
|
|
expect(URL.canParse('/chat', 'https://easyflow.example')).toBe(true);
|
|
expect(URL.canParse('/chat')).toBe(false);
|
|
});
|
|
});
|