fix(web): recover transient map SDK failures
This commit is contained in:
@@ -1,5 +1,18 @@
|
|||||||
import { expect, test } from 'vitest';
|
import { afterEach, expect, test, vi } from 'vitest';
|
||||||
import { buildAMapMarkerURL, gcj02ToWgs84, isValidAMapCoordinate, wgs84ToGcj02 } from './amap';
|
import {
|
||||||
|
buildAMapMarkerURL,
|
||||||
|
gcj02ToWgs84,
|
||||||
|
isValidAMapCoordinate,
|
||||||
|
loadAMap,
|
||||||
|
type AMapLike,
|
||||||
|
wgs84ToGcj02
|
||||||
|
} from './amap';
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete window.__LINGNIU_APP_CONFIG__;
|
||||||
|
delete window.AMapLoader;
|
||||||
|
document.querySelector('script[src="https://webapi.amap.com/loader.js"]')?.remove();
|
||||||
|
});
|
||||||
|
|
||||||
test('accepts coordinates inside the China vehicle service range', () => {
|
test('accepts coordinates inside the China vehicle service range', () => {
|
||||||
expect(isValidAMapCoordinate(116.397128, 39.916527)).toBe(true);
|
expect(isValidAMapCoordinate(116.397128, 39.916527)).toBe(true);
|
||||||
@@ -30,3 +43,31 @@ test('keeps coordinates outside China unchanged and converts AMap marker links a
|
|||||||
expect(position[0]).toBeCloseTo(116.40337, 5);
|
expect(position[0]).toBeCloseTo(116.40337, 5);
|
||||||
expect(position[1]).toBeCloseTo(39.91793, 5);
|
expect(position[1]).toBeCloseTo(39.91793, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('retries the SDK load after a transient AMap loader rejection', async () => {
|
||||||
|
const amap = {} as AMapLike;
|
||||||
|
const load = vi.fn()
|
||||||
|
.mockRejectedValueOnce(new Error('temporary network failure'))
|
||||||
|
.mockResolvedValueOnce(amap);
|
||||||
|
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
|
||||||
|
window.AMapLoader = { load };
|
||||||
|
|
||||||
|
await expect(loadAMap(['AMap.Geocoder'])).rejects.toThrow('temporary network failure');
|
||||||
|
await expect(loadAMap(['AMap.Geocoder'])).resolves.toBe(amap);
|
||||||
|
expect(load).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removes a failed loader script so a later request can recover', async () => {
|
||||||
|
const amap = {} as AMapLike;
|
||||||
|
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
|
||||||
|
|
||||||
|
const firstAttempt = loadAMap(['AMap.ToolBar']);
|
||||||
|
const failedScript = document.querySelector<HTMLScriptElement>('script[src="https://webapi.amap.com/loader.js"]');
|
||||||
|
expect(failedScript).not.toBeNull();
|
||||||
|
failedScript?.dispatchEvent(new Event('error'));
|
||||||
|
await expect(firstAttempt).rejects.toThrow('高德地图 Loader 加载失败');
|
||||||
|
expect(document.querySelector('script[src="https://webapi.amap.com/loader.js"]')).toBeNull();
|
||||||
|
|
||||||
|
window.AMapLoader = { load: vi.fn().mockResolvedValue(amap) };
|
||||||
|
await expect(loadAMap(['AMap.ToolBar'])).resolves.toBe(amap);
|
||||||
|
});
|
||||||
|
|||||||
@@ -104,16 +104,31 @@ function loadScript(src: string) {
|
|||||||
}
|
}
|
||||||
const existing = document.querySelector<HTMLScriptElement>(`script[src="${src}"]`);
|
const existing = document.querySelector<HTMLScriptElement>(`script[src="${src}"]`);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
if (existing.dataset.loadState === 'loaded') {
|
||||||
|
existing.remove();
|
||||||
|
loadScript(src).then(resolve, reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
existing.addEventListener('load', () => resolve(), { once: true });
|
existing.addEventListener('load', () => resolve(), { once: true });
|
||||||
existing.addEventListener('error', () => reject(new Error('高德地图 Loader 加载失败')), { once: true });
|
existing.addEventListener('error', () => {
|
||||||
|
existing.remove();
|
||||||
|
reject(new Error('高德地图 Loader 加载失败'));
|
||||||
|
}, { once: true });
|
||||||
if (window.AMapLoader) resolve();
|
if (window.AMapLoader) resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = src;
|
script.src = src;
|
||||||
script.async = true;
|
script.async = true;
|
||||||
script.onload = () => resolve();
|
script.dataset.loadState = 'loading';
|
||||||
script.onerror = () => reject(new Error('高德地图 Loader 加载失败'));
|
script.onload = () => {
|
||||||
|
script.dataset.loadState = 'loaded';
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.onerror = () => {
|
||||||
|
script.remove();
|
||||||
|
reject(new Error('高德地图 Loader 加载失败'));
|
||||||
|
};
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -221,16 +236,21 @@ export function loadAMap(plugins: AMapPlugin[] = ['AMap.Scale']) {
|
|||||||
} else if (config.securityJsCode) {
|
} else if (config.securityJsCode) {
|
||||||
window._AMapSecurityConfig = { securityJsCode: config.securityJsCode };
|
window._AMapSecurityConfig = { securityJsCode: config.securityJsCode };
|
||||||
}
|
}
|
||||||
const promise = loadScript('https://webapi.amap.com/loader.js').then(() => {
|
const promise = loadScript('https://webapi.amap.com/loader.js')
|
||||||
if (!window.AMapLoader) {
|
.then(() => {
|
||||||
throw new Error('高德地图 Loader 不可用');
|
if (!window.AMapLoader) {
|
||||||
}
|
throw new Error('高德地图 Loader 不可用');
|
||||||
return window.AMapLoader.load({
|
}
|
||||||
key: config.webJsKey,
|
return window.AMapLoader.load({
|
||||||
version: '2.0',
|
key: config.webJsKey,
|
||||||
plugins
|
version: '2.0',
|
||||||
}) as Promise<AMapLike>;
|
plugins
|
||||||
});
|
}) as Promise<AMapLike>;
|
||||||
|
})
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
amapLoaderPromises.delete(key);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
amapLoaderPromises.set(key, promise);
|
amapLoaderPromises.set(key, promise);
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user