Files
MyIP/tests/composable-nav-target.test.js
Claude 96525d2fc7 Chore: trim the branch — leftovers, redundancy, comments
A pass over every file the branch touched, after the iOS animation hunt
left tries and reverts behind it. Removed what nothing reaches: the
view-transition rule for a transition no code starts, an unreachable
guard in the transition hook, a no-op KeepAlive key branch, a sign-in
guard the menu already enforces, usage-dialog wrappers that only
forwarded to the store, a duplicated modifier-click check and card
filter (isPlainClick / listedTools), a test that hand-rolled
listedTools, a drawer-era fixture parameter. auth-flow.js folded into
auth-redirect.js. Comments cut to what the code cannot say itself —
file headers of a few lines, the measured iOS reasons, non-obvious
orderings — and stale ones fixed (Nav under KeepAlive, Home.vue
reusing DEFAULT_META, "from Nav", enabledCards). AGENTS.md sections
tightened to the code as it is now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
2026-10-05 16:50:16 +00:00

112 lines
4.3 KiB
JavaScript

// useNavTarget() with an injected route and router: the section scroll must
// land after the router's own scroll (next tick) and a frame.
import assert from 'node:assert/strict';
import { describe, it, beforeEach, afterEach } from 'node:test';
import { nextTick } from 'vue';
import { useNavTarget } from '../frontend/composables/use-nav-target.js';
import { SECTION_SCROLL_OFFSET } from '../frontend/utils/scroll-to.js';
let log;
let sectionTop;
let withMain;
const saved = {};
beforeEach(() => {
log = [];
sectionTop = 1000;
withMain = false;
for (const key of ['window', 'document', 'requestAnimationFrame', 'ResizeObserver']) saved[key] = globalThis[key];
const listeners = new Map();
globalThis.window = {
scrollY: 500,
scrollTo: (opts) => log.push(['scrollTo', opts]),
addEventListener: (type, fn) => listeners.set(type, fn),
removeEventListener: (type) => listeners.delete(type),
listeners,
};
globalThis.document = {
getElementById: (id) => {
if (id === 'Connectivity') return { getBoundingClientRect: () => ({ top: sectionTop }) };
if (id === 'mainpart' && withMain) return { id };
return null;
},
};
globalThis.requestAnimationFrame = (cb) => {
log.push(['frame']);
setTimeout(cb, 0);
};
});
afterEach(() => {
for (const [key, value] of Object.entries(saved)) globalThis[key] = value;
});
const fakeRouter = (failure) => ({
push: async (to) => {
log.push(['push', to]);
// The router schedules its own scroll for after the next tick.
nextTick(() => log.push(['router-scroll']));
return failure;
},
});
describe('useNavTarget()', () => {
it('scrolls straight to the section on the homepage', async () => {
const { navigateTo } = useNavTarget({ route: { name: 'home' }, router: fakeRouter() });
await navigateTo({ section: 'Connectivity' });
assert.deepEqual(log, [
['scrollTo', { top: 1500 - SECTION_SCROLL_OFFSET, behavior: 'smooth' }],
]);
});
it('goes home, lets the router scroll, then scrolls to the section', async () => {
const { navigateTo } = useNavTarget({ route: { name: 'tool' }, router: fakeRouter() });
await navigateTo({ section: 'Connectivity' });
assert.deepEqual(log.map(([step]) => step), ['push', 'router-scroll', 'frame', 'scrollTo']);
assert.equal(log[0][1], '/');
});
it('scrolls nothing when going home is refused', async () => {
const router = fakeRouter(new Error('aborted'));
const { navigateTo } = useNavTarget({ route: { name: 'privacy' }, router });
await navigateTo({ section: 'Connectivity' });
assert.deepEqual(log.map(([step]) => step), ['push', 'router-scroll']);
});
it("pushes a tool's page and scrolls nothing", async () => {
const { navigateTo } = useNavTarget({ route: { name: 'home' }, router: fakeRouter() });
await navigateTo({ tool: 'whois' });
await nextTick();
assert.deepEqual(log, [['push', '/tools/whois'], ['router-scroll']]);
});
it('ignores an unknown item', async () => {
const { navigateTo } = useNavTarget({ route: { name: 'home' }, router: fakeRouter() });
await navigateTo({});
assert.deepEqual(log, []);
});
it('re-aims while the homepage resizes, until the visitor scrolls', async () => {
withMain = true;
const observers = [];
globalThis.ResizeObserver = class {
constructor(cb) { this.cb = cb; this.on = false; observers.push(this); }
observe() { this.on = true; }
disconnect() { this.on = false; }
};
const { navigateTo } = useNavTarget({ route: { name: 'tool' }, router: fakeRouter() });
await navigateTo({ section: 'Connectivity' });
const [observer] = observers;
assert.equal(observer.on, true);
sectionTop = 1100; // content above the section grew
observer.cb();
assert.deepEqual(log.at(-1), ['scrollTo', { top: 1600 - SECTION_SCROLL_OFFSET, behavior: 'smooth' }]);
window.listeners.get('wheel')();
assert.equal(observer.on, false, 'stops on the first user scroll');
assert.equal(window.listeners.size, 0, 'and drops its listeners');
});
});