| … | … | @@ -707,3 +707,58 @@ test('★ a staged bundle OLDER than the installed app is not an update', (t) => |
| 707 | 707 | t.ok(withUpdateBanner(page, { running: null, staged: 'bbbb' }).includes('update-bar'), |
| 708 | 708 | 'undated on both sides stays a candidate') |
| 709 | 709 | }) |
| 710 | |
| 711 | // ── the update banner must not disagree with the shell ──────────────────────────────────────────── |
| 712 | // |
| 713 | // ⛔ OBSERVED ON A HANDSET: "an update is ready, restart to apply", shown for ever, while restarting |
| 714 | // changed nothing. This function had re-implemented a decision it does not own — |
| 715 | // `if (u.running) return u.staged !== u.running` — with no build-time comparison and no sight of |
| 716 | // failed.json, which lives on the shell side and which a page cannot read. So it answered YES on |
| 717 | // exactly the bundles chooseBundle refuses, and offered a button that could never work. |
| 718 | // |
| 719 | // The shell already runs chooseBundle at boot and now reports its verdict. This renders that. |
| 720 | import { updateWaiting, updateBlockedReason } from '../lib/pages.js' |
| 721 | |
| 722 | test('a bundle the shell REFUSED is never announced as waiting', (t) => { |
| 723 | const u = { |
| 724 | staged: 'a'.repeat(64), |
| 725 | running: 'b'.repeat(64), |
| 726 | refused: { hash: 'a'.repeat(64), reason: 'the app was updated more recently than the staged bundle' } |
| 727 | } |
| 728 | t.absent(updateWaiting(u), 'no banner for something that cannot be applied') |
| 729 | t.ok(updateBlockedReason(u).includes('more recently'), 'and the reason is available to show instead') |
| 730 | }) |
| 731 | |
| 732 | test('a refusal naming a DIFFERENT bundle does not suppress a real update', (t) => { |
| 733 | // An old refusal must not hide a newly staged bundle that will apply. |
| 734 | const u = { |
| 735 | staged: 'c'.repeat(64), |
| 736 | running: 'b'.repeat(64), |
| 737 | refused: { hash: 'a'.repeat(64), reason: 'stale' } |
| 738 | } |
| 739 | t.ok(updateWaiting(u), 'the new one is still announced') |
| 740 | t.is(updateBlockedReason(u), null) |
| 741 | }) |
| 742 | |
| 743 | test('with no verdict from the shell the old rules still apply', (t) => { |
| 744 | // An older shell sends no `refused`. Behaviour must not change for it. |
| 745 | t.ok(updateWaiting({ staged: 'a'.repeat(64), running: 'b'.repeat(64) }), 'differs from running') |
| 746 | t.absent(updateWaiting({ staged: 'a'.repeat(64), running: 'a'.repeat(64) }), 'same as running') |
| 747 | t.absent(updateWaiting({ staged: 'a'.repeat(64), stagedBuiltAt: 1000, builtAt: 9000 }), 'APK newer') |
| 748 | t.ok(updateWaiting({ staged: 'a'.repeat(64), stagedBuiltAt: 9000, builtAt: 1000 }), 'staged newer') |
| 749 | }) |
| 750 | |
| 751 | test('nothing staged is never waiting and never blocked', (t) => { |
| 752 | t.absent(updateWaiting({ staged: null, refused: { hash: 'a'.repeat(64), reason: 'x' } })) |
| 753 | t.is(updateBlockedReason({ staged: null }), null) |
| 754 | }) |
| 755 | |
| 756 | test('a blocked update gets a line that does NOT offer a restart', (t) => { |
| 757 | const u = { |
| 758 | staged: 'a'.repeat(64), running: 'b'.repeat(64), |
| 759 | refused: { hash: 'a'.repeat(64), reason: 'the app was updated more recently than the staged bundle' } |
| 760 | } |
| 761 | const html = withUpdateBanner('<body>page</body>', u) |
| 762 | t.ok(html.includes('cannot be applied'), 'says so') |
| 763 | t.absent(html.includes('restart to apply'), 'and never offers the button that does nothing') |
| 764 | }) |