feat: v0.4.1 — untrack map-asset pack (~100 MB): compose volume mount + runtime fallback to remote OFM styles
This commit is contained in:
+48
-2
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { DEFAULT_STYLE_ID, STYLES, isStyleId, rewriteStyle, styleUrl } from './mapStyles';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { DEFAULT_STYLE_ID, REMOTE_STYLE_BASE, STYLES, isStyleId, rewriteStyle, styleUrl, styleUrlFor } from './mapStyles';
|
||||
|
||||
// Mirrors the real OFM style JSONs (verified against tiles.openfreemap.org): same
|
||||
// glyphs/sprite values and source shapes, no attribution anywhere.
|
||||
@@ -76,3 +76,49 @@ describe('rewriteStyle', () => {
|
||||
expect(JSON.stringify(RAW_OFM_STYLE)).toBe(before);
|
||||
});
|
||||
});
|
||||
|
||||
describe('styleUrlFor', () => {
|
||||
it('points at the local style file when the asset pack is available', () => {
|
||||
for (const s of STYLES) {
|
||||
expect(styleUrlFor(s.id, true)).toBe(`/styles/${s.id}.json`);
|
||||
}
|
||||
});
|
||||
|
||||
it('points at the exact remote OFM style url otherwise', () => {
|
||||
for (const s of STYLES) {
|
||||
expect(styleUrlFor(s.id, false)).toBe(`${REMOTE_STYLE_BASE}/${s.id}`);
|
||||
expect(styleUrlFor(s.id, false)).toBe(`https://tiles.openfreemap.org/styles/${s.id}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('localAssetsAvailable', () => {
|
||||
// The probe caches its promise at module level, so each test gets a fresh module
|
||||
// instance (resetModules + dynamic import) with its own stubbed fetch.
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it('resolves false on non-200 responses', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false, status: 404 }));
|
||||
const mod = await import('./mapStyles');
|
||||
await expect(mod.localAssetsAvailable()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('resolves true on 200 and caches the probe (one fetch across awaits)', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const mod = await import('./mapStyles');
|
||||
const [a, b] = await Promise.all([mod.localAssetsAvailable(), mod.localAssetsAvailable()]);
|
||||
expect(a).toBe(true);
|
||||
expect(b).toBe(true);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('resolves false on network errors', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('offline')));
|
||||
const mod = await import('./mapStyles');
|
||||
await expect(mod.localAssetsAvailable()).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,6 +38,33 @@ export function styleUrl(id: string): string {
|
||||
return `/styles/${id}.json`;
|
||||
}
|
||||
|
||||
// Remote fallback base for styles when the local asset pack is absent (fresh clone
|
||||
// without `npm run assets:download` / no volume mount). The raw OFM styles already
|
||||
// point glyphs/sprites at remote URLs, so they work as-is — only vector tiles were
|
||||
// ever remote. A future self-hosted tile pack plugs into the same availability
|
||||
// mechanism below (the manifest describes the whole local asset set).
|
||||
export const REMOTE_STYLE_BASE = 'https://tiles.openfreemap.org/styles';
|
||||
|
||||
export function styleUrlFor(id: string, localAssetsAvailable: boolean): string {
|
||||
return localAssetsAvailable ? `/styles/${id}.json` : `${REMOTE_STYLE_BASE}/${id}`;
|
||||
}
|
||||
|
||||
// The manifest is written LAST by scripts/download-map-assets.mjs, so its presence
|
||||
// means the whole pack (styles + fonts + sprites — and any future asset class) is in
|
||||
// place. Probed once per page life via a module-level cached promise: any non-200 or
|
||||
// network error → false (fall back to remote styles).
|
||||
const MANIFEST_URL = '/map-assets-manifest.json';
|
||||
let assetsProbePromise: Promise<boolean> | null = null;
|
||||
|
||||
export function localAssetsAvailable(): Promise<boolean> {
|
||||
if (!assetsProbePromise) {
|
||||
assetsProbePromise = fetch(MANIFEST_URL, { cache: 'no-store' })
|
||||
.then((res) => res.ok)
|
||||
.catch(() => false);
|
||||
}
|
||||
return assetsProbePromise;
|
||||
}
|
||||
|
||||
type Source = Record<string, unknown>;
|
||||
|
||||
// Takes a raw OFM style object and returns a new one with glyphs/sprite pointed at the
|
||||
|
||||
Reference in New Issue
Block a user