mirror of
https://github.com/jason5ng32/MyIP.git
synced 2026-11-04 08:55:47 +00:00
People running uBlock Origin, Privacy Badger, a blocking browser, or a DNS / VPN / router filter had no way here to see what their setup lets through. The new tool probes a static list of widely used ad, analytics, social, error-monitoring and device-maker endpoints plus popular tracking scripts (fetched with no-cors and no cookies or referrer, never run), and places bait elements for cosmetic filters. Nothing runs until the visitor starts it; every group starts selected. A timeout reads as "no answer" and stays out of the score, and every listed host was checked to resolve, serve valid HTTPS and not redirect to http:// or in a loop, since any of those would fail like a block. Element hiding is left out of the score (DNS-level filters can never pass it) and instead tells browser-level from network-level blocking. The page copy says outright that blocking everything is not automatically better. Ships with the x shortcut, locale copy for every full locale and a changelog entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
370 lines
15 KiB
JavaScript
370 lines
15 KiB
JavaScript
// use-shortcuts depends on shortcut.js, which calls document.addEventListener at module load time.
|
|
// Here we inject minimal document / window stubs first, then dynamically import the tested module.
|
|
//
|
|
// pulse-beacon.js pulls `@/utils/...` (Vite alias) and the `p` shortcut gates
|
|
// on its build-time env flag — both are awkward in the Node runner. A load
|
|
// hook stubs that module with a mutable `hasPulseBackend` so we can cover
|
|
// both branches (the shortcut's other gate, configs.cloudFlare, is plain
|
|
// test input).
|
|
|
|
import assert from 'node:assert/strict';
|
|
import { describe, it, after } from 'node:test';
|
|
import { registerHooks } from 'node:module';
|
|
import { ref, computed, reactive, nextTick } from 'vue';
|
|
|
|
const documentHandlers = {};
|
|
// Per-test override (see "o" shortcut cases below). Default: no card is
|
|
// currently highlighted via J/K, so any lookup returns null.
|
|
let querySelectorImpl = () => null;
|
|
globalThis.document = {
|
|
addEventListener(type, handler) { documentHandlers[type] = handler; },
|
|
getElementById() { return null; },
|
|
querySelector(sel) { return querySelectorImpl(sel); },
|
|
};
|
|
globalThis.window = {
|
|
scrollTo() {},
|
|
scrollY: 0,
|
|
pageYOffset: 0,
|
|
};
|
|
|
|
registerHooks({
|
|
load(url, context, nextLoad) {
|
|
if (url.includes('/utils/features/pulse-beacon.js')) {
|
|
return {
|
|
format: 'module',
|
|
shortCircuit: true,
|
|
source: `
|
|
export const PULSE_BEACON_URL = 'https://pulse.test';
|
|
export const sendVisitBeacon = () => {};
|
|
export let hasPulseBackend = false;
|
|
globalThis.__setPulseBackendForTest = (v) => { hasPulseBackend = Boolean(v); };
|
|
`,
|
|
};
|
|
}
|
|
return nextLoad(url, context);
|
|
},
|
|
});
|
|
|
|
const { useShortcuts } = await import('../frontend/composables/use-shortcuts.js');
|
|
const { onAppEvent } = await import('../frontend/utils/app-events.js');
|
|
const { registerAppCommand } = await import('../frontend/utils/app-commands.js');
|
|
|
|
const t = (k) => `<${k}>`;
|
|
|
|
// The test-running shortcuts dispatch on the command bus rather than calling
|
|
// through refs. Register the five owner commands once, into a shared record
|
|
// that loadAndGetKeyMap resets per test.
|
|
const commandCalls = { ipRefresh: [], connectivity: [], webrtc: [], dnsleak: [], speedTest: 0 };
|
|
registerAppCommand('ipinfo:refresh', (payload) => { commandCalls.ipRefresh.push(payload.index); });
|
|
registerAppCommand('connectivity:run', (payload) => { commandCalls.connectivity.push(payload.trigger); });
|
|
registerAppCommand('webrtc:run', (payload) => { commandCalls.webrtc.push(payload.isRefresh); });
|
|
registerAppCommand('dnsleak:run', (payload) => { commandCalls.dnsleak.push(payload.isRefresh); });
|
|
registerAppCommand('speedtest:toggle', () => { commandCalls.speedTest += 1; });
|
|
|
|
const resetCommandCalls = () => {
|
|
commandCalls.ipRefresh = [];
|
|
commandCalls.connectivity = [];
|
|
commandCalls.webrtc = [];
|
|
commandCalls.dnsleak = [];
|
|
commandCalls.speedTest = 0;
|
|
};
|
|
|
|
function makeStoreStub() {
|
|
const state = reactive({
|
|
refreshRequested: false,
|
|
toggledSheets: [],
|
|
});
|
|
return {
|
|
state,
|
|
setRefreshEveryThing(v) { state.refreshRequested = v; },
|
|
toggleSheet(name) { state.toggledSheets.push(name); },
|
|
};
|
|
}
|
|
|
|
function makeRefs() {
|
|
// UI-chrome calls live here; the test-section calls land in commandCalls
|
|
// (merged into the returned record so assertions read one object).
|
|
const calls = {
|
|
queryOpen: 0,
|
|
helpOpen: 0,
|
|
shareOpen: 0,
|
|
advancedNavigate: [],
|
|
advancedFullScreen: 0,
|
|
mask: 0,
|
|
get speedTest() { return commandCalls.speedTest; },
|
|
get ipRefresh() { return commandCalls.ipRefresh; },
|
|
get connectivity() { return commandCalls.connectivity; },
|
|
get webrtc() { return commandCalls.webrtc; },
|
|
get dnsleak() { return commandCalls.dnsleak; },
|
|
};
|
|
|
|
const advancedToolsRef = ref({
|
|
openTool(slug) { calls.advancedNavigate.push(slug); },
|
|
fullScreen() { calls.advancedFullScreen += 1; },
|
|
});
|
|
|
|
return {
|
|
refs: {
|
|
queryIPRef: ref({ openModal: () => { calls.queryOpen += 1; } }),
|
|
helpModalRef: ref({ openModal: () => { calls.helpOpen += 1; }, keyMap: null }),
|
|
shareReportRef: ref({ openDialog: () => { calls.shareOpen += 1; } }),
|
|
advancedToolsRef,
|
|
isInfosLoaded: ref(true),
|
|
toggleInfoMask: () => { calls.mask += 1; },
|
|
},
|
|
calls,
|
|
};
|
|
}
|
|
|
|
after(() => {
|
|
globalThis.__setPulseBackendForTest?.(false);
|
|
});
|
|
|
|
function loadAndGetKeyMap({
|
|
originalSite = false,
|
|
ipHistoryEnabled = true,
|
|
pulseBackend = false,
|
|
cloudFlare = false,
|
|
} = {}) {
|
|
globalThis.__setPulseBackendForTest(pulseBackend);
|
|
resetCommandCalls();
|
|
const store = makeStoreStub();
|
|
const { refs, calls } = makeRefs();
|
|
const configs = computed(() => ({ originalSite, map: true, cloudFlare }));
|
|
const userPreferences = computed(() => ({ ipCardsToShow: 2, ipHistoryEnabled }));
|
|
|
|
const { loadShortcuts } = useShortcuts({
|
|
refs, store, t, configs, userPreferences,
|
|
});
|
|
loadShortcuts();
|
|
|
|
// helpModalRef.keyMap receives the global keyMap after loadShortcuts
|
|
const keyMap = refs.helpModalRef.value.keyMap || [];
|
|
return { keyMap, store, refs, calls };
|
|
}
|
|
|
|
describe('useShortcuts()', () => {
|
|
it('loadShortcuts() registers a keymap of 26+ entries on a non-original site', () => {
|
|
const { keyMap } = loadAndGetKeyMap({ originalSite: false, pulseBackend: false, cloudFlare: false });
|
|
// 26 base entries (no invisibility / enhanced-DNS / pulse).
|
|
const distinctKeys = new Set(keyMap.map((e) => e.keys));
|
|
assert.ok(distinctKeys.size >= 26, `expected ≥26 distinct shortcut keys, got ${distinctKeys.size}`);
|
|
assert.ok(distinctKeys.has('R'));
|
|
assert.ok(distinctKeys.has('?'));
|
|
assert.ok(distinctKeys.has('g'));
|
|
assert.ok(distinctKeys.has('o'));
|
|
assert.ok(distinctKeys.has('H'));
|
|
assert.ok(distinctKeys.has('x'), 'Ad & Tracker Blocking Test is a public tool');
|
|
assert.equal(distinctKeys.has('p'), false, 'pulse shortcut stays off without a pulse backend or cloudFlare key');
|
|
assert.equal(distinctKeys.has('P'), false, 'persona shortcut stays off a self-hosted instance');
|
|
assert.equal(distinctKeys.has('B'), false, 'blocklist shortcut stays off a self-hosted instance');
|
|
assert.equal(distinctKeys.has('A'), false, 'ASN Profile shortcut stays off without a cloudFlare key');
|
|
// Overlays take no keys at all (utils/shortcut.js), so there is no
|
|
// drawer-only entry left to register.
|
|
assert.equal(distinctKeys.has('f'), false, 'no shortcut acts on an open overlay');
|
|
});
|
|
|
|
it('originalSite=true adds invisibility ("i"), enhanced DNS-leak ("D"), blocklist ("B") and persona ("P") shortcuts', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap({ originalSite: true });
|
|
const hasInvisibility = keyMap.some((e) => e.keys === 'i');
|
|
assert.ok(hasInvisibility, 'key "i" should be present on originalSite');
|
|
const D = keyMap.findLast((e) => e.keys === 'D');
|
|
assert.ok(D, 'key "D" should be present on originalSite');
|
|
D.action();
|
|
assert.deepEqual(calls.advancedNavigate, ['enhanceddnsleaktest']);
|
|
// Uppercase B is its own key — lowercase b is Browser Info's.
|
|
const B = keyMap.findLast((e) => e.keys === 'B');
|
|
assert.ok(B, 'key "B" should be present on originalSite');
|
|
B.action();
|
|
// Uppercase P is its own key — lowercase p is Earth Online's.
|
|
const P = keyMap.findLast((e) => e.keys === 'P');
|
|
assert.ok(P, 'key "P" should be present on originalSite');
|
|
P.action();
|
|
assert.deepEqual(calls.advancedNavigate, ['enhanceddnsleaktest', 'blocklist', 'personacheck']);
|
|
});
|
|
|
|
it('configs.cloudFlare adds the ASN Profile shortcut ("A")', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap({ cloudFlare: true });
|
|
// Uppercase A is its own key — lowercase a is the IP calculator's.
|
|
const A = keyMap.findLast((e) => e.keys === 'A');
|
|
assert.ok(A, 'key "A" should be present when configs.cloudFlare is set');
|
|
A.action();
|
|
assert.deepEqual(calls.advancedNavigate, ['asn']);
|
|
});
|
|
|
|
it('"R" action triggers store.setRefreshEveryThing(true)', () => {
|
|
const { keyMap, store } = loadAndGetKeyMap();
|
|
const entry = keyMap.findLast((e) => e.keys === 'R');
|
|
assert.ok(entry, '"R" key should be registered');
|
|
entry.action();
|
|
assert.equal(store.state.refreshRequested, true);
|
|
});
|
|
|
|
it('advanced-tool shortcuts open via advancedToolsRef.openTool(slug)', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
const l = keyMap.findLast((e) => e.keys === 'l'); l.action();
|
|
const M = keyMap.findLast((e) => e.keys === 'M'); M.action();
|
|
const t_ = keyMap.findLast((e) => e.keys === 't'); t_.action();
|
|
assert.deepEqual(calls.advancedNavigate, ['pingtest', 'macchecker', 'mtrtest']);
|
|
});
|
|
|
|
it('numeric regex "[1-6]" passes through number argument to refreshCard', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
const entry = keyMap.findLast((e) => e.type === 'regex');
|
|
assert.ok(entry);
|
|
entry.action(2); // user typed "2"
|
|
assert.deepEqual(calls.ipRefresh, [1], 'action receives num; refreshCard(card, num-1)');
|
|
});
|
|
|
|
it('numeric shortcut respects userPreferences.ipCardsToShow upper bound', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
const entry = keyMap.findLast((e) => e.type === 'regex');
|
|
entry.action(6); // beyond ipCardsToShow (2)
|
|
assert.deepEqual(calls.ipRefresh, [], 'num > ipCardsToShow → no-op');
|
|
});
|
|
|
|
it('"c"/"w"/"d"/"s" dispatch the section commands with their payloads', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
keyMap.findLast((e) => e.keys === 'c').action();
|
|
keyMap.findLast((e) => e.keys === 'w').action();
|
|
keyMap.findLast((e) => e.keys === 'd').action();
|
|
keyMap.findLast((e) => e.keys === 's').action();
|
|
assert.deepEqual(calls.connectivity, ['manual']);
|
|
assert.deepEqual(calls.webrtc, [false]);
|
|
assert.deepEqual(calls.dnsleak, [true]);
|
|
assert.equal(calls.speedTest, 1, 'the s key stays a run/pause toggle');
|
|
});
|
|
|
|
it('"?" opens help modal and emits the shortcut:help-opened app event', () => {
|
|
// The achievement itself (CleverTrickery) is the engine's concern —
|
|
// use-shortcuts only emits the domain event. See achievement-rules tests.
|
|
let emitted = 0;
|
|
const off = onAppEvent('shortcut:help-opened', () => { emitted += 1; });
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
const entry = keyMap.findLast((e) => e.keys === '?');
|
|
entry.action();
|
|
off();
|
|
assert.equal(calls.helpOpen, 1);
|
|
assert.equal(emitted, 1);
|
|
});
|
|
|
|
it('"e" opens the share report dialog', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
const entry = keyMap.findLast((e) => e.keys === 'e');
|
|
assert.ok(entry, '"e" key should be registered');
|
|
entry.action();
|
|
assert.equal(calls.shareOpen, 1);
|
|
});
|
|
|
|
it('"h" toggles info mask only when infos loaded', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
const entry = keyMap.findLast((e) => e.keys === 'h');
|
|
entry.action();
|
|
assert.equal(calls.mask, 1);
|
|
});
|
|
|
|
it('"H" opens the IP history sheet when ipHistoryEnabled', () => {
|
|
const { keyMap, store } = loadAndGetKeyMap({ ipHistoryEnabled: true });
|
|
const entry = keyMap.findLast((e) => e.keys === 'H');
|
|
assert.ok(entry, '"H" key should be registered');
|
|
entry.action();
|
|
assert.deepEqual(store.state.toggledSheets, ['ipHistory']);
|
|
});
|
|
|
|
it('"H" is a no-op when ipHistoryEnabled is false', () => {
|
|
const { keyMap, store } = loadAndGetKeyMap({ ipHistoryEnabled: false });
|
|
const entry = keyMap.findLast((e) => e.keys === 'H');
|
|
entry.action();
|
|
assert.deepEqual(store.state.toggledSheets, []);
|
|
});
|
|
|
|
it('the pulse backend flag adds the pulse shortcut (key "p")', () => {
|
|
const { keyMap, store } = loadAndGetKeyMap({ pulseBackend: true });
|
|
const entry = keyMap.findLast((e) => e.keys === 'p');
|
|
assert.ok(entry, '"p" key should be present when hasPulseBackend');
|
|
entry.action();
|
|
assert.deepEqual(store.state.toggledSheets, ['pulse']);
|
|
});
|
|
|
|
it('configs.cloudFlare alone adds the pulse shortcut (outage-only fork)', () => {
|
|
const { keyMap, store } = loadAndGetKeyMap({ pulseBackend: false, cloudFlare: true });
|
|
const entry = keyMap.findLast((e) => e.keys === 'p');
|
|
assert.ok(entry, '"p" key should be present when configs.cloudFlare is set');
|
|
entry.action();
|
|
assert.deepEqual(store.state.toggledSheets, ['pulse']);
|
|
});
|
|
|
|
it('configs landing after registration re-registers the map (late "p")', async () => {
|
|
globalThis.__setPulseBackendForTest(false);
|
|
const store = makeStoreStub();
|
|
const { refs } = makeRefs();
|
|
// Reactive configs source so the watcher inside useShortcuts can fire.
|
|
const configsData = ref({ originalSite: false, map: true });
|
|
const configs = computed(() => configsData.value);
|
|
const userPreferences = computed(() => ({ ipCardsToShow: 2, ipHistoryEnabled: true }));
|
|
|
|
const { loadShortcuts } = useShortcuts({ refs, store, t, configs, userPreferences });
|
|
loadShortcuts();
|
|
const keyMap = refs.helpModalRef.value.keyMap;
|
|
assert.equal(keyMap.some((e) => e.keys === 'p'), false, 'no "p" before configs resolve');
|
|
|
|
configsData.value = { originalSite: false, map: true, cloudFlare: true };
|
|
await nextTick(); // flush the configs watcher
|
|
assert.equal(keyMap.some((e) => e.keys === 'p'), true, '"p" appears once cloudFlare lands');
|
|
});
|
|
|
|
it('"o" opens the highlighted advanced tool by its data-adv-slug', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
// Stub the highlighted card to be an advanced tool card pointing at mtrtest.
|
|
querySelectorImpl = (sel) => {
|
|
if (sel === '.keyboard-shortcut-card[data-keyboard-hover="true"]') {
|
|
return {
|
|
getAttribute: (name) => name === 'data-adv-slug' ? 'mtrtest' : null,
|
|
querySelector: () => null,
|
|
};
|
|
}
|
|
return null;
|
|
};
|
|
const entry = keyMap.findLast((e) => e.keys === 'o');
|
|
entry.action();
|
|
querySelectorImpl = () => null;
|
|
assert.deepEqual(calls.advancedNavigate, ['mtrtest']);
|
|
});
|
|
|
|
it('"o" clicks the highlighted Connectivity card\'s site link (a[data-card-open])', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
let clicked = 0;
|
|
querySelectorImpl = (sel) => {
|
|
if (sel === '.keyboard-shortcut-card[data-keyboard-hover="true"]') {
|
|
return {
|
|
getAttribute: () => null,
|
|
querySelector: (inner) => (inner === 'a[data-card-open][href]' ? { click: () => { clicked += 1; } } : null),
|
|
};
|
|
}
|
|
return null;
|
|
};
|
|
const entry = keyMap.findLast((e) => e.keys === 'o');
|
|
entry.action();
|
|
querySelectorImpl = () => null;
|
|
assert.equal(clicked, 1);
|
|
assert.deepEqual(calls.advancedNavigate, []);
|
|
});
|
|
|
|
it('"o" is a no-op when the highlighted card has no slug and no site link (e.g. IP card)', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
querySelectorImpl = () => ({ getAttribute: () => null, querySelector: () => null });
|
|
const entry = keyMap.findLast((e) => e.keys === 'o');
|
|
entry.action();
|
|
querySelectorImpl = () => null;
|
|
assert.deepEqual(calls.advancedNavigate, []);
|
|
});
|
|
|
|
it('"o" is a no-op when nothing is highlighted', () => {
|
|
const { keyMap, calls } = loadAndGetKeyMap();
|
|
// querySelectorImpl default returns null (nothing highlighted).
|
|
const entry = keyMap.findLast((e) => e.keys === 'o');
|
|
entry.action();
|
|
assert.deepEqual(calls.advancedNavigate, []);
|
|
});
|
|
});
|