Abort requests [#608] (#625)

* Properly abort requests using AbortController

* add basic adapter tests

---------

Co-authored-by: Andrew Dassonville <dassonville.andrew@gmail.com>
This commit is contained in:
Brandon Liu
2025-12-24 17:33:11 +08:00
committed by GitHub
parent a5f8a89256
commit cabe8a898a
6 changed files with 536 additions and 405 deletions

View File

@@ -215,12 +215,13 @@ export class Protocol {
} }
if (this.metadata) { if (this.metadata) {
return { const data = await instance.getTileJson(params.url);
data: await instance.getTileJson(params.url), abortController.signal.throwIfAborted();
}; return { data };
} }
const h = await instance.getHeader(); const h = await instance.getHeader();
abortController.signal.throwIfAborted();
if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) { if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {
console.error( console.error(
@@ -255,6 +256,7 @@ export class Protocol {
const header = await instance.getHeader(); const header = await instance.getHeader();
const resp = await instance?.getZxy(+z, +x, +y, abortController.signal); const resp = await instance?.getZxy(+z, +x, +y, abortController.signal);
abortController.signal.throwIfAborted();
if (resp) { if (resp) {
return { return {
data: new Uint8Array(resp.data), data: new Uint8Array(resp.data),

View File

@@ -355,7 +355,7 @@ export class FetchSource implements Source {
let userAgent = ""; let userAgent = "";
if ("navigator" in globalThis) { if ("navigator" in globalThis) {
//biome-ignore lint: cf workers //biome-ignore lint: cf workers
userAgent = (globalThis as any).navigator.userAgent || ""; userAgent = (globalThis as any).navigator?.userAgent ?? "";
} }
const isWindows = userAgent.indexOf("Windows") > -1; const isWindows = userAgent.indexOf("Windows") > -1;
const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent); const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent);

119
js/test/adapter.test.ts Normal file
View File

@@ -0,0 +1,119 @@
import assert from "node:assert";
import { describe, mock, test } from "node:test";
import { PMTiles, Protocol } from "../src";
import { mockServer } from "./utils";
describe("Protocol", () => {
test("get TileJSON", async () => {
mockServer.reset();
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
const protocol = new Protocol();
protocol.add(pmtiles);
const result = await protocol.tilev4(
{
url: "pmtiles://http://localhost:1337/example.pmtiles",
type: "json",
},
new AbortController()
);
assert.deepStrictEqual(result.data, {
tiles: ["pmtiles://http://localhost:1337/example.pmtiles/{z}/{x}/{y}"],
minzoom: 0,
maxzoom: 0,
bounds: [0, 0, 0.9999999, 1],
});
assert.equal(mockServer.numRequests, 1);
});
test("get tile data", async () => {
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
const protocol = new Protocol();
protocol.add(pmtiles);
const result = await protocol.tilev4(
{
url: "pmtiles://http://localhost:1337/example.pmtiles/0/0/0",
type: "arrayBuffer",
},
new AbortController()
);
assert.ok(result.data instanceof Uint8Array);
assert.strictEqual(result.data.length, 49);
});
test("returns empty data for missing tile if errorOnMissingTile is false", async () => {
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
const protocol = new Protocol({ errorOnMissingTile: false });
protocol.add(pmtiles);
const result = await protocol.tilev4(
{
url: "pmtiles://http://localhost:1337/example.pmtiles/25/0/0",
type: "arrayBuffer",
},
new AbortController()
);
assert.ok(result.data instanceof Uint8Array);
assert.strictEqual(result.data.length, 0);
});
test("throws error for missing tile if errorOnMissingTile is true", async () => {
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
const protocol = new Protocol({ errorOnMissingTile: true });
protocol.add(pmtiles);
const promise = protocol.tilev4(
{
url: "pmtiles://http://localhost:1337/example.pmtiles/25/0/0",
type: "arrayBuffer",
},
new AbortController()
);
assert.rejects(promise, { message: "Tile not found." });
});
test("throws AbortError when AbortController is signaled while accessing TileJSON", async () => {
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
const protocol = new Protocol();
protocol.add(pmtiles);
const abortController = new AbortController();
const resultPromise = protocol.tilev4(
{
url: "pmtiles://http://localhost:1337/example.pmtiles",
type: "json",
},
abortController
);
abortController.abort();
await assert.rejects(resultPromise, { name: "AbortError" });
});
test("throws AbortError when AbortController is signaled while accessing tile data", async () => {
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
const protocol = new Protocol();
protocol.add(pmtiles);
const abortController = new AbortController();
const resultPromise = protocol.tilev4(
{
url: "pmtiles://http://localhost:1337/example.pmtiles/0/0/0",
type: "arrayBuffer",
},
abortController
);
abortController.abort();
await assert.rejects(resultPromise, { name: "AbortError" });
});
});

View File

@@ -1 +1,2 @@
import "./adapter.test";
import "./v3.test"; import "./v3.test";

44
js/test/utils.ts Normal file
View File

@@ -0,0 +1,44 @@
import fs from "fs";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
class MockServer {
etag?: string;
numRequests: number;
lastCache?: string;
reset() {
this.numRequests = 0;
this.etag = undefined;
}
constructor() {
this.numRequests = 0;
this.etag = undefined;
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
const server = setupServer(
http.get(
"http://localhost:1337/example.pmtiles",
({ request, params }) => {
this.lastCache = request.cache;
this.numRequests++;
const range = request.headers.get("range")?.substr(6).split("-");
if (!range) {
throw new Error("invalid range");
}
const offset = +range[0];
const length = +range[1];
const body = serverBuffer.slice(offset, offset + length - 1);
return new HttpResponse(body, {
status: 206,
statusText: "OK",
headers: { etag: this.etag } as HeadersInit,
});
}
)
);
server.listen({ onUnhandledRequest: "error" });
}
}
export const mockServer = new MockServer();

View File

@@ -1,8 +1,7 @@
import fs from "fs"; import fs from "fs";
import assert from "node:assert"; import assert from "node:assert";
import { afterEach, beforeEach, describe, it, test } from "node:test"; import { afterEach, beforeEach, describe, it, test } from "node:test";
import { http, HttpResponse } from "msw"; import { mockServer } from "./utils";
import { setupServer } from "msw/node";
import { import {
BufferPosition, BufferPosition,
@@ -20,48 +19,8 @@ import {
zxyToTileId, zxyToTileId,
} from "../src/index"; } from "../src/index";
class MockServer { describe("pmtiles v3", () => {
etag?: string; test("varint", () => {
numRequests: number;
lastCache?: string;
reset() {
this.numRequests = 0;
this.etag = undefined;
}
constructor() {
this.numRequests = 0;
this.etag = undefined;
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
const server = setupServer(
http.get(
"http://localhost:1337/example.pmtiles",
({ request, params }) => {
this.lastCache = request.cache;
this.numRequests++;
const range = request.headers.get("range")?.substr(6).split("-");
if (!range) {
throw new Error("invalid range");
}
const offset = +range[0];
const length = +range[1];
const body = serverBuffer.slice(offset, offset + length - 1);
return new HttpResponse(body, {
status: 206,
statusText: "OK",
headers: { etag: this.etag } as HeadersInit,
});
}
)
);
server.listen({ onUnhandledRequest: "error" });
}
}
const mockserver = new MockServer();
test("varint", () => {
let b: BufferPosition = { let b: BufferPosition = {
buf: new Uint8Array([0, 1, 127, 0xe5, 0x8e, 0x26]), buf: new Uint8Array([0, 1, 127, 0xe5, 0x8e, 0x26]),
pos: 0, pos: 0,
@@ -75,27 +34,27 @@ test("varint", () => {
pos: 0, pos: 0,
}; };
assert.strictEqual(readVarint(b), 9007199254740991); assert.strictEqual(readVarint(b), 9007199254740991);
}); });
test("zxy to tile id", () => { test("zxy to tile id", () => {
assert.strictEqual(zxyToTileId(0, 0, 0), 0); assert.strictEqual(zxyToTileId(0, 0, 0), 0);
assert.strictEqual(zxyToTileId(1, 0, 0), 1); assert.strictEqual(zxyToTileId(1, 0, 0), 1);
assert.strictEqual(zxyToTileId(1, 0, 1), 2); assert.strictEqual(zxyToTileId(1, 0, 1), 2);
assert.strictEqual(zxyToTileId(1, 1, 1), 3); assert.strictEqual(zxyToTileId(1, 1, 1), 3);
assert.strictEqual(zxyToTileId(1, 1, 0), 4); assert.strictEqual(zxyToTileId(1, 1, 0), 4);
assert.strictEqual(zxyToTileId(2, 0, 0), 5); assert.strictEqual(zxyToTileId(2, 0, 0), 5);
}); });
test("tile id to zxy", () => { test("tile id to zxy", () => {
assert.deepEqual(tileIdToZxy(0), [0, 0, 0]); assert.deepEqual(tileIdToZxy(0), [0, 0, 0]);
assert.deepEqual(tileIdToZxy(1), [1, 0, 0]); assert.deepEqual(tileIdToZxy(1), [1, 0, 0]);
assert.deepEqual(tileIdToZxy(2), [1, 0, 1]); assert.deepEqual(tileIdToZxy(2), [1, 0, 1]);
assert.deepEqual(tileIdToZxy(3), [1, 1, 1]); assert.deepEqual(tileIdToZxy(3), [1, 1, 1]);
assert.deepEqual(tileIdToZxy(4), [1, 1, 0]); assert.deepEqual(tileIdToZxy(4), [1, 1, 0]);
assert.deepEqual(tileIdToZxy(5), [2, 0, 0]); assert.deepEqual(tileIdToZxy(5), [2, 0, 0]);
}); });
test("a lot of tiles", () => { test("a lot of tiles", () => {
for (let z = 0; z < 9; z++) { for (let z = 0; z < 9; z++) {
for (let x = 0; x < 1 << z; x++) { for (let x = 0; x < 1 << z; x++) {
for (let y = 0; y < 1 << z; y++) { for (let y = 0; y < 1 << z; y++) {
@@ -106,9 +65,9 @@ test("a lot of tiles", () => {
} }
} }
} }
}); });
test("tile extremes", () => { test("tile extremes", () => {
for (let z = 0; z < 27; z++) { for (let z = 0; z < 27; z++) {
const dim = 2 ** z - 1; const dim = 2 ** z - 1;
const tl = tileIdToZxy(zxyToTileId(z, 0, 0)); const tl = tileIdToZxy(zxyToTileId(z, 0, 0));
@@ -120,9 +79,9 @@ test("tile extremes", () => {
const br = tileIdToZxy(zxyToTileId(z, dim, dim)); const br = tileIdToZxy(zxyToTileId(z, dim, dim));
assert.deepEqual([z, dim, dim], br); assert.deepEqual([z, dim, dim], br);
} }
}); });
test("invalid tiles", () => { test("invalid tiles", () => {
assert.throws(() => { assert.throws(() => {
tileIdToZxy(Number.MAX_SAFE_INTEGER); tileIdToZxy(Number.MAX_SAFE_INTEGER);
}); });
@@ -134,14 +93,14 @@ test("invalid tiles", () => {
assert.throws(() => { assert.throws(() => {
zxyToTileId(0, 1, 1); zxyToTileId(0, 1, 1);
}); });
}); });
test("tile search for missing entry", () => { test("tile search for missing entry", () => {
const entries: Entry[] = []; const entries: Entry[] = [];
assert.strictEqual(findTile(entries, 101), null); assert.strictEqual(findTile(entries, 101), null);
}); });
test("tile search for first entry == id", () => { test("tile search for first entry == id", () => {
const entries: Entry[] = [ const entries: Entry[] = [
{ tileId: 100, offset: 1, length: 1, runLength: 1 }, { tileId: 100, offset: 1, length: 1, runLength: 1 },
]; ];
@@ -149,19 +108,21 @@ test("tile search for first entry == id", () => {
assert.strictEqual(entry?.offset, 1); assert.strictEqual(entry?.offset, 1);
assert.strictEqual(entry?.length, 1); assert.strictEqual(entry?.length, 1);
assert.strictEqual(findTile(entries, 101), null); assert.strictEqual(findTile(entries, 101), null);
}); });
test("tile search with runlength", () => { test("tile search with runlength", () => {
const entries: Entry[] = [ const entries: Entry[] = [
{ tileId: 3, offset: 3, length: 1, runLength: 2 }, { tileId: 3, offset: 3, length: 1, runLength: 2 },
{ tileId: 5, offset: 5, length: 1, runLength: 2 }, { tileId: 5, offset: 5, length: 1, runLength: 2 },
]; ];
const entry = findTile(entries, 4); const entry = findTile(entries, 4);
assert.strictEqual(entry?.offset, 3); assert.strictEqual(entry?.offset, 3);
}); });
test("tile search with multiple tile entries", () => { test("tile search with multiple tile entries", () => {
let entries: Entry[] = [{ tileId: 100, offset: 1, length: 1, runLength: 2 }]; let entries: Entry[] = [
{ tileId: 100, offset: 1, length: 1, runLength: 2 },
];
let entry = findTile(entries, 101); let entry = findTile(entries, 101);
assert.strictEqual(entry?.offset, 1); assert.strictEqual(entry?.offset, 1);
assert.strictEqual(entry?.length, 1); assert.strictEqual(entry?.length, 1);
@@ -182,19 +143,19 @@ test("tile search with multiple tile entries", () => {
entry = findTile(entries, 51); entry = findTile(entries, 51);
assert.strictEqual(entry?.offset, 1); assert.strictEqual(entry?.offset, 1);
assert.strictEqual(entry?.length, 1); assert.strictEqual(entry?.length, 1);
}); });
test("leaf search", () => { test("leaf search", () => {
const entries: Entry[] = [ const entries: Entry[] = [
{ tileId: 100, offset: 1, length: 1, runLength: 0 }, { tileId: 100, offset: 1, length: 1, runLength: 0 },
]; ];
const entry = findTile(entries, 150); const entry = findTile(entries, 150);
assert.strictEqual(entry?.offset, 1); assert.strictEqual(entry?.offset, 1);
assert.strictEqual(entry?.length, 1); assert.strictEqual(entry?.length, 1);
}); });
// inefficient method only for testing // inefficient method only for testing
class TestNodeFileSource implements Source { class TestNodeFileSource implements Source {
buffer: ArrayBuffer; buffer: ArrayBuffer;
path: string; path: string;
key: string; key: string;
@@ -220,10 +181,10 @@ class TestNodeFileSource implements Source {
.buffer; .buffer;
return { data: slice, etag: this.etag }; return { data: slice, etag: this.etag };
} }
} }
// echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_1.pmtiles // echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_1.pmtiles
test("cache getHeader", async () => { test("cache getHeader", async () => {
const source = new TestNodeFileSource( const source = new TestNodeFileSource(
"test/data/test_fixture_1.pmtiles", "test/data/test_fixture_1.pmtiles",
"1" "1"
@@ -251,9 +212,9 @@ test("cache getHeader", async () => {
assert.strictEqual(header.minLat, 0); assert.strictEqual(header.minLat, 0);
// assert.strictEqual(header.maxLon,1); // TODO fix me // assert.strictEqual(header.maxLon,1); // TODO fix me
assert.strictEqual(header.maxLat, 1); assert.strictEqual(header.maxLat, 1);
}); });
test("getUint64", async () => { test("getUint64", async () => {
const view = new DataView(new ArrayBuffer(8)); const view = new DataView(new ArrayBuffer(8));
view.setBigUint64(0, 0n, true); view.setBigUint64(0, 0n, true);
assert.strictEqual(getUint64(view, 0), 0); assert.strictEqual(getUint64(view, 0), 0);
@@ -261,33 +222,33 @@ test("getUint64", async () => {
assert.strictEqual(getUint64(view, 0), 1); assert.strictEqual(getUint64(view, 0), 1);
view.setBigUint64(0, 9007199254740991n, true); view.setBigUint64(0, 9007199254740991n, true);
assert.strictEqual(getUint64(view, 0), 9007199254740991); assert.strictEqual(getUint64(view, 0), 9007199254740991);
}); });
test("cache check against empty", async () => { test("cache check against empty", async () => {
const source = new TestNodeFileSource("test/data/empty.pmtiles", "1"); const source = new TestNodeFileSource("test/data/empty.pmtiles", "1");
const cache = new SharedPromiseCache(); const cache = new SharedPromiseCache();
assert.rejects(async () => { assert.rejects(async () => {
await cache.getHeader(source); await cache.getHeader(source);
}); });
}); });
test("cache check magic number", async () => { test("cache check magic number", async () => {
const source = new TestNodeFileSource("test/data/invalid.pmtiles", "1"); const source = new TestNodeFileSource("test/data/invalid.pmtiles", "1");
const cache = new SharedPromiseCache(); const cache = new SharedPromiseCache();
assert.rejects(async () => { assert.rejects(async () => {
await cache.getHeader(source); await cache.getHeader(source);
}); });
}); });
test("cache check future spec version", async () => { test("cache check future spec version", async () => {
const source = new TestNodeFileSource("test/data/invalid_v4.pmtiles", "1"); const source = new TestNodeFileSource("test/data/invalid_v4.pmtiles", "1");
const cache = new SharedPromiseCache(); const cache = new SharedPromiseCache();
assert.rejects(async () => { assert.rejects(async () => {
await cache.getHeader(source); await cache.getHeader(source);
}); });
}); });
test("cache getDirectory", async () => { test("cache getDirectory", async () => {
const source = new TestNodeFileSource( const source = new TestNodeFileSource(
"test/data/test_fixture_1.pmtiles", "test/data/test_fixture_1.pmtiles",
"1" "1"
@@ -314,9 +275,9 @@ test("cache getDirectory", async () => {
for (const v of cache.cache.values()) { for (const v of cache.cache.values()) {
assert.ok(v.lastUsed > 0); assert.ok(v.lastUsed > 0);
} }
}); });
test("multiple sources in a single cache", async () => { test("multiple sources in a single cache", async () => {
const cache = new SharedPromiseCache(); const cache = new SharedPromiseCache();
const source1 = new TestNodeFileSource( const source1 = new TestNodeFileSource(
"test/data/test_fixture_1.pmtiles", "test/data/test_fixture_1.pmtiles",
@@ -330,34 +291,35 @@ test("multiple sources in a single cache", async () => {
assert.strictEqual(cache.cache.size, 2); assert.strictEqual(cache.cache.size, 2);
await cache.getHeader(source2); await cache.getHeader(source2);
assert.strictEqual(cache.cache.size, 4); assert.strictEqual(cache.cache.size, 4);
}); });
test("etag change", async () => { test("etag change", async () => {
mockServer.reset();
const p = new PMTiles("http://localhost:1337/example.pmtiles"); const p = new PMTiles("http://localhost:1337/example.pmtiles");
const tile = await p.getZxy(0, 0, 0); await p.getZxy(0, 0, 0);
// header + tile // header + tile
assert.strictEqual(2, mockserver.numRequests); assert.strictEqual(2, mockServer.numRequests);
mockserver.etag = "etag_2"; mockServer.etag = "etag_2";
await p.getZxy(0, 0, 0); await p.getZxy(0, 0, 0);
// tile + header again + tile // tile + header again + tile
assert.strictEqual(5, mockserver.numRequests); assert.strictEqual(5, mockServer.numRequests);
}); });
test("weak etags", async () => { test("weak etags", async () => {
mockserver.reset(); mockServer.reset();
const p = new PMTiles("http://localhost:1337/example.pmtiles"); const p = new PMTiles("http://localhost:1337/example.pmtiles");
const tile = await p.getZxy(0, 0, 0);
// header + tile
assert.strictEqual(2, mockserver.numRequests);
mockserver.etag = "W/weak_etag";
await p.getZxy(0, 0, 0); await p.getZxy(0, 0, 0);
assert.strictEqual(3, mockserver.numRequests); // header + tile
}); assert.strictEqual(2, mockServer.numRequests);
mockServer.etag = "W/weak_etag";
await p.getZxy(0, 0, 0);
assert.strictEqual(3, mockServer.numRequests);
});
// handle < 16384 bytes archive case // handle < 16384 bytes archive case
// handle DigitalOcean case returning 200 instead of 206 // handle DigitalOcean case returning 200 instead of 206
test("cache pruning by byte size", async () => { test("cache pruning by byte size", async () => {
const cache = new SharedPromiseCache(2); const cache = new SharedPromiseCache(2);
cache.cache.set("0", { lastUsed: 0, data: Promise.resolve([]) }); cache.cache.set("0", { lastUsed: 0, data: Promise.resolve([]) });
cache.cache.set("1", { lastUsed: 1, data: Promise.resolve([]) }); cache.cache.set("1", { lastUsed: 1, data: Promise.resolve([]) });
@@ -367,9 +329,9 @@ test("cache pruning by byte size", async () => {
assert.ok(cache.cache.get("2")); assert.ok(cache.cache.get("2"));
assert.ok(cache.cache.get("1")); assert.ok(cache.cache.get("1"));
assert.ok(!cache.cache.get("0")); assert.ok(!cache.cache.get("0"));
}); });
test("pmtiles get metadata", async () => { test("pmtiles get metadata", async () => {
const source = new TestNodeFileSource( const source = new TestNodeFileSource(
"test/data/test_fixture_1.pmtiles", "test/data/test_fixture_1.pmtiles",
"1" "1"
@@ -377,20 +339,20 @@ test("pmtiles get metadata", async () => {
const p = new PMTiles(source); const p = new PMTiles(source);
const metadata = await p.getMetadata(); const metadata = await p.getMetadata();
assert.ok((metadata as { name: string }).name); assert.ok((metadata as { name: string }).name);
}); });
// echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_2.pmtiles // echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_2.pmtiles
test("get file extension", async () => { test("get file extension", async () => {
assert.equal("", tileTypeExt(TileType.Unknown)); assert.equal("", tileTypeExt(TileType.Unknown));
assert.equal(".mvt", tileTypeExt(TileType.Mvt)); assert.equal(".mvt", tileTypeExt(TileType.Mvt));
assert.equal(".png", tileTypeExt(TileType.Png)); assert.equal(".png", tileTypeExt(TileType.Png));
assert.equal(".jpg", tileTypeExt(TileType.Jpeg)); assert.equal(".jpg", tileTypeExt(TileType.Jpeg));
assert.equal(".webp", tileTypeExt(TileType.Webp)); assert.equal(".webp", tileTypeExt(TileType.Webp));
assert.equal(".avif", tileTypeExt(TileType.Avif)); assert.equal(".avif", tileTypeExt(TileType.Avif));
}); });
interface TileJsonLike { interface TileJsonLike {
tilejson: string; tilejson: string;
scheme: string; scheme: string;
tiles: string[]; tiles: string[];
@@ -398,9 +360,9 @@ interface TileJsonLike {
name?: string; name?: string;
attribution?: string; attribution?: string;
version?: string; version?: string;
} }
test("pmtiles get TileJSON", async () => { test("pmtiles get TileJSON", async () => {
const source = new TestNodeFileSource( const source = new TestNodeFileSource(
"test/data/test_fixture_1.pmtiles", "test/data/test_fixture_1.pmtiles",
"1" "1"
@@ -416,28 +378,31 @@ test("pmtiles get TileJSON", async () => {
assert.equal("test_fixture_1.pmtiles", tilejson.description); assert.equal("test_fixture_1.pmtiles", tilejson.description);
assert.equal("test_fixture_1.pmtiles", tilejson.name); assert.equal("test_fixture_1.pmtiles", tilejson.name);
assert.equal("2", tilejson.version); assert.equal("2", tilejson.version);
}); });
const originalUserAgent = navigator.userAgent; const originalUserAgent = navigator.userAgent;
describe("user agent", async () => { describe("user agent", async () => {
beforeEach(() => { beforeEach(() => {
Object.defineProperty(globalThis.navigator, "userAgent", { Object.defineProperty(global, "navigator", {
value: "Windows Chrome", value: { userAgent: "Windows Chrome" },
configurable: true, configurable: true,
writable: true,
}); });
}); });
afterEach(() => { afterEach(() => {
Object.defineProperty(globalThis.navigator, "userAgent", { Object.defineProperty(global, "navigator", {
value: originalUserAgent, value: undefined,
configurable: true, configurable: true,
writable: true,
}); });
}); });
it("works around caching bug on chrome on windows", async () => { it("works around caching bug on chrome on windows", async () => {
const p = new PMTiles("http://localhost:1337/example.pmtiles"); const p = new PMTiles("http://localhost:1337/example.pmtiles");
await p.getZxy(0, 0, 0); await p.getZxy(0, 0, 0);
assert.equal("no-store", mockserver.lastCache); assert.equal("no-store", mockServer.lastCache);
});
}); });
}); });