Files
cinny/src/app/utils/pwaInstall.test.ts
T
jaredandClaude Opus 5 bd1e61e8cd
CI / Build & Quality Checks (push) Successful in 1m59s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 7s
CI / Trigger Desktop Build (push) Successful in 5s
CI / Playwright smoke (e2e) (push) Successful in 1m59s
feat(pwa): offer to install — Chromium prompt, iOS "Add to Home Screen" hint (#116)
The client never handled beforeinstallprompt and showed no install hint, so
phone users only got the PWA if they knew to dig through Share → Add to
Home Screen (iOS never prompts; Chromium's mini-infobar is easy to miss).

- utils/pwaInstall.ts (pure, 4 tests): show from the second visit, never in
  Tauri or an installed PWA (display-mode standalone / navigator.standalone),
  30-day snooze after a dismissal; kind = real prompt when the browser
  handed us a deferred beforeinstallprompt, Share-sheet instructions on iOS
  Safari, nothing elsewhere (Firefox desktop has no install path).
- hooks/usePwaInstallPrompt.ts: captures beforeinstallprompt/appinstalled,
  counts one visit per browser session, waits 6s for the prompt event before
  deciding, then enqueues a sticky toast; tap → prompt(), X → snooze.
- ToastNotif gains onDismiss (fired by the X button only) so the snooze is
  recorded however the toast is closed. Mounted from ClientNonUIFeatures for
  signed-in users only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-17 13:31:29 -04:00

35 lines
1.4 KiB
TypeScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { INSTALL_HINT_SNOOZE_MS, installHintKind, shouldShowInstallHint } from './pwaInstall';
const base = { isTauri: false, isStandalone: false, visits: 2, dismissedAt: null, now: 1_000_000 };
describe('shouldShowInstallHint', () => {
it('shows from the second visit in a plain browser tab', () => {
assert.equal(shouldShowInstallHint(base), true);
assert.equal(shouldShowInstallHint({ ...base, visits: 1 }), false);
});
it('never shows in the desktop app or an installed PWA', () => {
assert.equal(shouldShowInstallHint({ ...base, isTauri: true }), false);
assert.equal(shouldShowInstallHint({ ...base, isStandalone: true }), false);
});
it('snoozes for 30 days after a dismissal', () => {
const dismissedAt = base.now - INSTALL_HINT_SNOOZE_MS + 1;
assert.equal(shouldShowInstallHint({ ...base, dismissedAt }), false);
assert.equal(
shouldShowInstallHint({ ...base, dismissedAt: base.now - INSTALL_HINT_SNOOZE_MS }),
true,
);
});
});
describe('installHintKind', () => {
it('prefers a real prompt, falls back to iOS instructions, else nothing', () => {
assert.equal(installHintKind(true, true), 'prompt');
assert.equal(installHintKind(false, true), 'ios-share');
assert.equal(installHintKind(false, false), 'none');
});
});