Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions e2e/pwa.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,19 @@ test.describe('the version link', () => {
});

test.describe('offline', () => {
test('says the site is ready to work offline once the first load has cached it, and not on later loads', async ({ page }) => {
await withSite(async (site) => {
await openAt(page, site);

await expect(page.locator('#offline-status')).toHaveText('Ready to work offline.', { timeout: RESULT_TIMEOUT_MS });

await page.reload();
await new Explorer(page).ready();

await expect(page.locator('#offline-status')).toHaveText('');
});
});

test('opens, runs an operation and shows the engine with no network once it has been loaded', async ({ page }) => {
await withSite(async (site) => {
const explorer = await controlledPage(page, site);
Expand Down
1 change: 1 addition & 0 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ <h3>One combination</h3>

<footer class="page-footer">
<p>Version <a id="build-link" href="https://github.com/ExaDev/combinatrix/releases">releases</a></p>
<p id="offline-status" role="status"></p>
</footer>
</body>
</html>
4 changes: 4 additions & 0 deletions site/src/pwa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function startUpdates(): void {
const prompt = byId("update-prompt", HTMLElement);
const update = byId("update-now", HTMLButtonElement);
const dismiss = byId("update-later", HTMLButtonElement);
const offlineStatus = byId("offline-status", HTMLElement);

watchForUpdates(
// Relative to the page, so it is found under the repository's project-page path and under a preview of the build.
Expand All @@ -29,6 +30,9 @@ function startUpdates(): void {
hide: () => {
prompt.hidden = true;
},
offlineReady: () => {
offlineStatus.textContent = "Ready to work offline.";
},
onUpdate: (listener) => {
update.addEventListener("click", listener);
},
Expand Down
10 changes: 10 additions & 0 deletions site/src/updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Workbox } from "workbox-window";
export interface UpdateSource {
/** Registers the worker. Rejects when the browser cannot. */
readonly register: () => Promise<void>;
/** Calls the listener when the first worker for this browser has installed and cached the site, so it will open with no network from now on. A later worker does not call it. */
readonly onFirstInstall: (listener: () => void) => void;
/** Calls the listener when a newer worker has installed and is waiting for the page to let it take over. */
readonly onWaiting: (listener: () => void) => void;
/** Calls the listener when a newer worker has taken over the page. */
Expand All @@ -18,6 +20,8 @@ export interface UpdateSource {
export interface UpdateView {
readonly show: () => void;
readonly hide: () => void;
/** Tells the user the site is cached and will open with no network. */
readonly offlineReady: () => void;
/** Calls the listener when the user asks to update now. */
readonly onUpdate: (listener: () => void) => void;
/** Calls the listener when the user puts the update off. */
Expand All @@ -41,6 +45,7 @@ export interface UpdateEnvironment {
export function watchForUpdates(source: UpdateSource, view: UpdateView, environment: UpdateEnvironment): void {
let updating = false;

source.onFirstInstall(view.offlineReady);
source.onWaiting(view.show);
source.onControlling(() => {
// A worker can also take over a page that never asked, such as the first one to install; only the update the user chose reloads it.
Expand All @@ -66,6 +71,11 @@ export function workboxSource(url: string): UpdateSource {
register: async () => {
await workbox.register();
},
onFirstInstall: (listener) => {
workbox.addEventListener("installed", (event) => {
if (event.isUpdate !== true) listener();
});
},
onWaiting: (listener) => {
workbox.addEventListener("waiting", listener);
},
Expand Down
21 changes: 19 additions & 2 deletions site/src/updates.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ async function outcome(failure: Error | undefined): Promise<void> {

/** The worker, the prompt and the page around the update logic, driven by hand: each records what the logic asks of it and exposes the events a browser or a user would cause. */
function setup(failures: { readonly register?: Error; readonly check?: Error } = {}) {
const listeners = { waiting: [] as Listener[], controlling: [] as Listener[], update: [] as Listener[], dismiss: [] as Listener[], visible: [] as Listener[] };
const seen = { registrations: 0, checks: 0, activations: 0, reloads: 0, shown: false, reported: [] as unknown[] };
const listeners = { firstInstall: [] as Listener[], waiting: [] as Listener[], controlling: [] as Listener[], update: [] as Listener[], dismiss: [] as Listener[], visible: [] as Listener[] };
const seen = { offlineReady: 0, registrations: 0, checks: 0, activations: 0, reloads: 0, shown: false, reported: [] as unknown[] };

watchForUpdates(
{
register: async () => {
seen.registrations += 1;
await outcome(failures.register);
},
onFirstInstall: (listener) => {
listeners.firstInstall.push(listener);
},
onWaiting: (listener) => {
listeners.waiting.push(listener);
},
Expand All @@ -47,6 +50,9 @@ function setup(failures: { readonly register?: Error; readonly check?: Error } =
hide: () => {
seen.shown = false;
},
offlineReady: () => {
seen.offlineReady += 1;
},
onUpdate: (listener) => {
listeners.update.push(listener);
},
Expand All @@ -69,6 +75,9 @@ function setup(failures: { readonly register?: Error; readonly check?: Error } =

return {
seen,
installFirst: () => {
fire(listeners.firstInstall);
},
installWaiting: () => {
fire(listeners.waiting);
},
Expand Down Expand Up @@ -101,6 +110,14 @@ describe("watchForUpdates", () => {
expect(seen.shown).toBe(false);
});

it("tells the user the site is ready to work offline when the first worker has installed, and offers no update", () => {
const { seen, installFirst } = setup();
installFirst();

expect(seen.offlineReady).toBe(1);
expect(seen.shown).toBe(false);
});

it("offers the update when a newer worker is waiting, without activating it", () => {
const { seen, installWaiting } = setup();
installWaiting();
Expand Down
Loading