fix: 兼容 Chrome 90 前端运行环境

- 为管理端与用户中心补齐旧浏览器运行时 API 和回归测试

- 增加现代 CSS 与选择器降级,保持现有界面效果

- 固定 Chrome 90 构建目标并补充兼容依赖
This commit is contained in:
2026-07-16 14:50:06 +08:00
parent 705e0faab6
commit 27e50a7624
34 changed files with 793 additions and 60 deletions

View File

@@ -0,0 +1,108 @@
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);
});
});