forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdemRpc.test.ts
More file actions
186 lines (160 loc) · 5.78 KB
/
Copy pathdemRpc.test.ts
File metadata and controls
186 lines (160 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import { describe, it, expect, vi } from 'vitest'
import {
createDemRequestHandler,
WorkerDemManager,
type DemManagerLike,
type DemRequest,
type DemResponse,
type WorkerLike,
} from './demRpc'
// Both protocol ends wired directly to each other - no thread, no jsdom
// Worker gap. What a real page adds is only the postMessage boundary, and
// structured cloning of Blobs, typed arrays and ArrayBuffers is the
// platform's contract, not this module's.
function wire(manager: DemManagerLike): {
manager: WorkerDemManager
requests: DemRequest[]
} {
const requests: DemRequest[] = []
let deliver: (event: MessageEvent) => void = () => {}
const handler = createDemRequestHandler(manager, (message: DemResponse) => {
deliver({ data: message } as MessageEvent)
})
const worker: WorkerLike = {
postMessage(message: DemRequest) {
requests.push(message)
handler(message)
},
addEventListener(_type, listener) {
deliver = listener
},
}
return { manager: new WorkerDemManager(worker), requests }
}
function stubManager(overrides: Partial<DemManagerLike> = {}): DemManagerLike {
return {
loaded: Promise.resolve(),
fetchTile: vi.fn().mockResolvedValue({
data: new Blob([new Uint8Array([7])]),
cacheControl: 'max-age=60',
}),
fetchAndParseTile: vi.fn().mockResolvedValue({
width: 256,
height: 256,
data: new Float32Array([1.5, 2.5]),
}),
fetchContourTile: vi.fn().mockResolvedValue({
arrayBuffer: new Uint8Array([3, 4]).buffer,
}),
...overrides,
}
}
describe('the DEM worker protocol (#187)', () => {
it('round-trips a raw tile, bytes and caching headers intact', async () => {
const stub = stubManager()
const { manager } = wire(stub)
const tile = await manager.fetchTile(12, 1198, 1540, new AbortController())
expect(new Uint8Array(await tile.data.arrayBuffer())).toEqual(new Uint8Array([7]))
expect(tile.cacheControl).toBe('max-age=60')
expect(stub.fetchTile).toHaveBeenCalledWith(
12,
1198,
1540,
expect.any(AbortController),
)
})
it('round-trips a parsed tile as width, height and elevations', async () => {
const { manager } = wire(stubManager())
const tile = await manager.fetchAndParseTile(10, 1, 2, new AbortController())
expect(tile).toMatchObject({ width: 256, height: 256 })
expect(Array.from(tile.data)).toEqual([1.5, 2.5])
})
it('round-trips a contour tile, carrying the options through untouched', async () => {
const stub = stubManager()
const { manager } = wire(stub)
const options = { multiplier: 3.28084, levels: [40, 200] }
const tile = await manager.fetchContourTile(14, 5, 6, options, new AbortController())
expect(new Uint8Array(tile.arrayBuffer)).toEqual(new Uint8Array([3, 4]))
expect(stub.fetchContourTile).toHaveBeenCalledWith(
14,
5,
6,
options,
expect.any(AbortController),
)
})
it('marshals a worker-side failure back as a rejection with its message', async () => {
const { manager } = wire(
stubManager({
fetchTile: vi.fn().mockRejectedValue(new Error('DEM tile 1/2/3: HTTP 503')),
}),
)
await expect(manager.fetchTile(1, 2, 3, new AbortController())).rejects.toThrow(
'DEM tile 1/2/3: HTTP 503',
)
})
it('keeps concurrent requests apart by id', async () => {
const stub = stubManager({
fetchTile: vi
.fn()
.mockImplementation((z: number) =>
Promise.resolve({ data: new Blob([new Uint8Array([z])]) }),
),
})
const { manager } = wire(stub)
const [a, b] = await Promise.all([
manager.fetchTile(1, 0, 0, new AbortController()),
manager.fetchTile(2, 0, 0, new AbortController()),
])
expect(new Uint8Array(await a.data.arrayBuffer())).toEqual(new Uint8Array([1]))
expect(new Uint8Array(await b.data.arrayBuffer())).toEqual(new Uint8Array([2]))
})
it('rejects locally on abort and tells the worker to stop the work', async () => {
// The worker side holds the promise open so the abort arrives while the
// request is genuinely in flight.
let release: (tile: { data: Blob }) => void = () => {}
const workerSideAborts: boolean[] = []
const stub = stubManager({
fetchTile: vi.fn().mockImplementation(
(_z, _x, _y, abortController: AbortController) =>
new Promise((resolve) => {
abortController.signal.addEventListener('abort', () =>
workerSideAborts.push(true),
)
release = resolve as never
}),
),
})
const { manager, requests } = wire(stub)
const abortController = new AbortController()
const pending = manager.fetchTile(3, 4, 5, abortController)
abortController.abort()
await expect(pending).rejects.toMatchObject({ name: 'AbortError' })
// The abort really crossed the boundary and stopped the work.
expect(workerSideAborts).toEqual([true])
expect(requests.some((request) => request.kind === 'abort')).toBe(true)
// A late worker resolution answers nobody - and must not throw.
release({ data: new Blob() })
})
it('drops a late reply to an aborted request instead of resolving it', async () => {
let release: (tile: { data: Blob }) => void = () => {}
const posted: DemResponse[] = []
const handler = createDemRequestHandler(
stubManager({
fetchTile: vi.fn().mockImplementation(
() =>
new Promise((resolve) => {
release = resolve as never
}),
),
}),
(message) => posted.push(message),
)
handler({ id: 1, kind: 'fetchTile', z: 1, x: 2, y: 3 })
handler({ id: 1, kind: 'abort' })
release({ data: new Blob() })
await Promise.resolve()
await Promise.resolve()
expect(posted).toEqual([])
})
})