Release v2.14.1
This commit is contained in:
1 parent
635e1a9942
commit
c3299abaac
174 files changed
+4483
-1687
No files matched your search
@@ -0,0 +1,161 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
async function activateWorker(version, cacheKeys) {
|
||||
const listeners = new Map();
|
||||
const deleted = [];
|
||||
let claimed = false;
|
||||
const sandbox = {
|
||||
URL,
|
||||
self: {
|
||||
location: { href: 'http://localhost/sw.js?v=' + encodeURIComponent(version) },
|
||||
addEventListener(name, callback) { listeners.set(name, callback); },
|
||||
clients: { async claim() { claimed = true; } }
|
||||
},
|
||||
caches: {
|
||||
async keys() { return cacheKeys; },
|
||||
async delete(key) { deleted.push(key); return true; }
|
||||
}
|
||||
};
|
||||
const scriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'sw.js');
|
||||
vm.runInNewContext(fs.readFileSync(scriptPath, 'utf8'), sandbox, { filename: scriptPath });
|
||||
let activation;
|
||||
listeners.get('activate')({ waitUntil(promise) { activation = promise; } });
|
||||
await activation;
|
||||
assert.equal(claimed, true);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
test('player service worker activation preserves caches owned by other applications', async () => {
|
||||
const deleted = await activateWorker('2.14.0', ['other-app-assets', 'other-app-pages']);
|
||||
assert.deepEqual(deleted, []);
|
||||
});
|
||||
|
||||
test('player service worker retains its active version and the most recently created other generation', async () => {
|
||||
const cacheKeys = [
|
||||
'other-app-cache',
|
||||
'pulse-signage-player-pages-2.10.0',
|
||||
'pulse-signage-player-assets-2.10.0',
|
||||
'pulse-signage-player-media-2.10.0',
|
||||
'pulse-signage-player-playlists-2.10.0',
|
||||
'pulse-signage-player-pages-2.9.0',
|
||||
'pulse-signage-player-assets-2.11.0',
|
||||
'pulse-signage-player-pages-2.11.0'
|
||||
];
|
||||
const deleted = await activateWorker('2.10.0', cacheKeys);
|
||||
assert.deepEqual(deleted, ['pulse-signage-player-pages-2.9.0']);
|
||||
});
|
||||
|
||||
test('player service worker retention supports opaque versions before active caches exist', async () => {
|
||||
const deleted = await activateWorker('current-build', [
|
||||
'pulse-signage-player-pages-z-old-build',
|
||||
'pulse-signage-player-assets-z-old-build',
|
||||
'pulse-signage-player-pages-a-newer-build',
|
||||
'pulse-signage-player-playlists-a-newer-build'
|
||||
]);
|
||||
assert.deepEqual(deleted, [
|
||||
'pulse-signage-player-pages-z-old-build',
|
||||
'pulse-signage-player-assets-z-old-build'
|
||||
]);
|
||||
});
|
||||
|
||||
function createFetchWorker({ failure, cachedResponse = null, fetchError = false } = {}) {
|
||||
const networkResponse = new Response('fresh network body', {
|
||||
status: 200, headers: { ETag: '"fresh"', 'Content-Type': 'text/plain' }
|
||||
});
|
||||
const calls = { fetch: 0, writes: [] };
|
||||
const listeners = new Map();
|
||||
const sandbox = {
|
||||
URL,
|
||||
Response,
|
||||
Request,
|
||||
Headers,
|
||||
self: {
|
||||
location: { href: 'http://localhost/sw.js', origin: 'http://localhost' },
|
||||
addEventListener(name, callback) { listeners.set(name, callback); }
|
||||
},
|
||||
async fetch() {
|
||||
calls.fetch += 1;
|
||||
if (fetchError) throw new Error('Network unavailable');
|
||||
return networkResponse;
|
||||
},
|
||||
caches: {
|
||||
async match() { return cachedResponse; },
|
||||
async open() {
|
||||
if (failure === 'open') throw new Error('Cache storage unavailable');
|
||||
return {
|
||||
async put(key, response) {
|
||||
if (failure === 'put') throw new Error('QuotaExceededError');
|
||||
calls.writes.push({ key, response });
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
const scriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'sw.js');
|
||||
vm.runInNewContext(fs.readFileSync(scriptPath, 'utf8'), sandbox, { filename: scriptPath });
|
||||
return { sandbox, calls, networkResponse, listeners };
|
||||
}
|
||||
|
||||
test('successful network responses survive cache open and write failures', async () => {
|
||||
for (const failure of ['open', 'put']) {
|
||||
for (const strategy of ['networkFirst', 'cacheFirst', 'staleWhileRevalidate']) {
|
||||
for (const cached of strategy === 'networkFirst' ? [false, true] : [false]) {
|
||||
const cachedResponse = cached ? new Response('stale body') : null;
|
||||
const { sandbox, networkResponse } = createFetchWorker({ failure, cachedResponse });
|
||||
const response = await sandbox[strategy]({ url: 'http://localhost/screen/test' }, 'cache');
|
||||
assert.equal(response, networkResponse, strategy + ', failure=' + failure + ', cached=' + cached);
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(response.headers.get('ETag'), '"fresh"');
|
||||
assert.equal(await response.text(), 'fresh network body');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('successful cache writes retain the normalized key and do not consume the network body', async () => {
|
||||
const { sandbox, calls, networkResponse } = createFetchWorker();
|
||||
const request = { url: 'http://localhost/api/screens/test/playlist?ts=1' };
|
||||
const normalizedKey = { url: 'http://localhost/api/screens/test/playlist' };
|
||||
const response = await sandbox.networkFirst(request, 'cache', normalizedKey);
|
||||
assert.equal(response, networkResponse);
|
||||
assert.equal(calls.writes.length, 1);
|
||||
assert.equal(calls.writes[0].key, normalizedKey);
|
||||
assert.notEqual(calls.writes[0].response, networkResponse);
|
||||
assert.equal(await calls.writes[0].response.text(), 'fresh network body');
|
||||
assert.equal(await response.text(), 'fresh network body');
|
||||
});
|
||||
|
||||
test('asset cache hits do not fetch and offline pages still fall back to cached responses', async () => {
|
||||
const cachedResponse = new Response('cached body');
|
||||
const cacheHit = createFetchWorker({ cachedResponse });
|
||||
assert.equal(await cacheHit.sandbox.cacheFirst({}, 'cache'), cachedResponse);
|
||||
assert.equal(cacheHit.calls.fetch, 0);
|
||||
const offlineHit = createFetchWorker({ cachedResponse, fetchError: true });
|
||||
assert.equal(await offlineHit.sandbox.networkFirst({}, 'cache'), cachedResponse);
|
||||
const offlineMiss = createFetchWorker({ fetchError: true });
|
||||
assert.equal((await offlineMiss.sandbox.networkFirst({}, 'cache')).status, 504);
|
||||
});
|
||||
|
||||
test('player script assets refresh from network and retain offline cache fallback', async () => {
|
||||
const cachedResponse = new Response('old script body');
|
||||
const online = createFetchWorker({ cachedResponse });
|
||||
let onlineResponsePromise;
|
||||
online.listeners.get('fetch')({
|
||||
request: new Request('http://localhost/assets/player-script/rendering.js?v=2.14.0'),
|
||||
respondWith(promise) { onlineResponsePromise = promise; }
|
||||
});
|
||||
assert.equal(await onlineResponsePromise, online.networkResponse);
|
||||
assert.equal(online.calls.fetch, 1);
|
||||
|
||||
const offline = createFetchWorker({ cachedResponse: cachedResponse, fetchError: true });
|
||||
let offlineResponsePromise;
|
||||
offline.listeners.get('fetch')({
|
||||
request: new Request('http://localhost/assets/player-script/rendering.js?v=2.14.0'),
|
||||
respondWith(promise) { offlineResponsePromise = promise; }
|
||||
});
|
||||
assert.equal(await offlineResponsePromise, cachedResponse);
|
||||
});
|
||||
Reference in new issue
Block a user