Merge pull request #1355 from PR0M3TH3AN/codex/locate-and-add-routing-tests

Extract hash-change handler and add tests for default view selection
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-18 20:36:56 -05:00
commit 7299f60336
3 files changed
+243 -46

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
import { devLogger as defaultDevLogger, userLogger as defaultUserLogger } from "./utils/logger.js";
const createLoggerFacade = (logger, fallback) => {
if (logger && typeof logger === "object") {
return {
log: typeof logger.log === "function" ? logger.log.bind(logger) : fallback.log,
warn:
typeof logger.warn === "function" ? logger.warn.bind(logger) : fallback.warn,
error:
typeof logger.error === "function"
? logger.error.bind(logger)
: fallback.error,
};
}
return fallback;
};
const noopLogger = {
log() {},
warn() {},
error() {},
};
export function createHashChangeHandler({
getApplication,
getApplicationReady,
loadView,
viewInitRegistry,
devLogger = defaultDevLogger,
userLogger = defaultUserLogger,
} = {}) {
const devLog = createLoggerFacade(devLogger, noopLogger);
const userLog = createLoggerFacade(userLogger, noopLogger);
const getReadyPromise = () => {
if (typeof getApplicationReady === "function") {
return getApplicationReady();
}
return Promise.resolve();
};
const resolveIsLoggedIn = () => {
if (typeof getApplication !== "function") {
return false;
}
const app = getApplication();
if (!app || typeof app.isUserLoggedIn !== "function") {
return false;
}
return Boolean(app.isUserLoggedIn());
};
return async function handleHashChange() {
const currentHash =
typeof window !== "undefined" && window.location
? window.location.hash
: undefined;
devLog.log("handleHashChange called, current hash =", currentHash);
try {
await getReadyPromise();
} catch (error) {
userLog.warn(
"Proceeding with hash handling despite application initialization failure:",
error,
);
}
const hash =
typeof window !== "undefined" && window.location && window.location.hash
? window.location.hash
: "";
const match = hash.match(/^#view=([^&]+)/);
try {
if (!match || !match[1]) {
const defaultViewName = resolveIsLoggedIn()
? "for-you"
: "most-recent-videos";
await loadView(`views/${defaultViewName}.html`);
const initFn = viewInitRegistry?.[defaultViewName];
if (typeof initFn === "function") {
await initFn();
}
return;
}
const viewName = match[1];
if (typeof viewName === "string" && viewName.toLowerCase() === "history") {
}
const viewUrl = `views/${viewName}.html`;
await loadView(viewUrl);
const initFn = viewInitRegistry?.[viewName];
if (typeof initFn === "function") {
await initFn();
}
} catch (error) {
userLog.error("Failed to handle hash change:", error);
}
};
}
+14 -46
View File
@@ -3,7 +3,11 @@
import { validateInstanceConfig } from "../config/validate-config.js";
import { ASSET_VERSION } from "../config/asset-version.js";
import "./bufferPolyfill.js";
import { setApplication, setApplicationReady } from "./applicationContext.js";
import {
getApplication,
setApplication,
setApplicationReady,
} from "./applicationContext.js";
import nostrService from "./services/nostrService.js";
import r2Service from "./services/r2Service.js";
import { loadView, viewInitRegistry } from "./viewManager.js";
@@ -29,6 +33,7 @@ import {
TIP_JAR_URL,
isLockdownMode,
} from "./config.js";
import { createHashChangeHandler } from "./hashChangeHandler.js";
import AuthService from "./services/authService.js";
import getAuthProvider, {
providers as authProviders,
@@ -1357,48 +1362,11 @@ function handleQueryParams() {
}
}
async function handleHashChange() {
devLogger.log("handleHashChange called, current hash =", window.location.hash);
try {
await applicationReadyPromise;
} catch (error) {
userLogger.warn(
"Proceeding with hash handling despite application initialization failure:",
error
);
}
const hash = window.location.hash || "";
// Use a regex that captures up to the first ampersand or end of string.
// E.g. "#view=channel-profile&npub=..." => viewName = "channel-profile"
const match = hash.match(/^#view=([^&]+)/);
try {
if (!match || !match[1]) {
// No valid "#view=..." => default to "for-you" when logged in
const isLoggedIn = getApplication()?.isUserLoggedIn();
const defaultViewName = isLoggedIn ? "for-you" : "most-recent-videos";
await loadView(`views/${defaultViewName}.html`);
const initFn = viewInitRegistry[defaultViewName];
if (typeof initFn === "function") {
await initFn();
}
return;
}
const viewName = match[1]; // only the chunk before any '&'
if (typeof viewName === "string" && viewName.toLowerCase() === "history") {
}
const viewUrl = `views/${viewName}.html`;
// Now dynamically load that partial, then call its init function
await loadView(viewUrl);
const initFn = viewInitRegistry[viewName];
if (typeof initFn === "function") {
await initFn();
}
} catch (error) {
userLogger.error("Failed to handle hash change:", error);
}
}
const handleHashChange = createHashChangeHandler({
getApplication,
getApplicationReady: () => applicationReadyPromise,
loadView,
viewInitRegistry,
devLogger,
userLogger,
});
+126
View File
@@ -0,0 +1,126 @@
import test from "node:test";
import assert from "node:assert/strict";
import { JSDOM } from "jsdom";
import { createHashChangeHandler } from "../../js/hashChangeHandler.js";
const setupDom = (url) => {
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>", { url });
const { window } = dom;
const previousWindow = global.window;
const previousDocument = global.document;
const previousHTMLElement = global.HTMLElement;
global.window = window;
global.document = window.document;
global.HTMLElement = window.HTMLElement;
return () => {
global.window = previousWindow;
global.document = previousDocument;
if (previousHTMLElement === undefined) {
delete global.HTMLElement;
} else {
global.HTMLElement = previousHTMLElement;
}
};
};
const createLogger = () => ({
log() {},
warn() {},
error() {},
});
test("handleHashChange defaults to for-you when logged in", async () => {
const restore = setupDom("https://example.com");
try {
const loadViewCalls = [];
let initCalled = false;
const handleHashChange = createHashChangeHandler({
getApplication: () => ({ isUserLoggedIn: () => true }),
getApplicationReady: () => Promise.resolve(),
loadView: async (viewUrl) => {
loadViewCalls.push(viewUrl);
},
viewInitRegistry: {
"for-you": async () => {
initCalled = true;
},
},
devLogger: createLogger(),
userLogger: createLogger(),
});
await handleHashChange();
assert.deepEqual(loadViewCalls, ["views/for-you.html"]);
assert.equal(initCalled, true);
} finally {
restore();
}
});
test("handleHashChange defaults to most-recent-videos when logged out", async () => {
const restore = setupDom("https://example.com");
try {
const loadViewCalls = [];
let initCalled = false;
const handleHashChange = createHashChangeHandler({
getApplication: () => ({ isUserLoggedIn: () => false }),
getApplicationReady: () => Promise.resolve(),
loadView: async (viewUrl) => {
loadViewCalls.push(viewUrl);
},
viewInitRegistry: {
"most-recent-videos": async () => {
initCalled = true;
},
},
devLogger: createLogger(),
userLogger: createLogger(),
});
await handleHashChange();
assert.deepEqual(loadViewCalls, ["views/most-recent-videos.html"]);
assert.equal(initCalled, true);
} finally {
restore();
}
});
test("handleHashChange respects explicit view regardless of login state", async () => {
const restore = setupDom("https://example.com/#view=subscriptions");
try {
const loadViewCalls = [];
let initCalled = false;
const handleHashChange = createHashChangeHandler({
getApplication: () => ({ isUserLoggedIn: () => true }),
getApplicationReady: () => Promise.resolve(),
loadView: async (viewUrl) => {
loadViewCalls.push(viewUrl);
},
viewInitRegistry: {
subscriptions: async () => {
initCalled = true;
},
},
devLogger: createLogger(),
userLogger: createLogger(),
});
await handleHashChange();
assert.deepEqual(loadViewCalls, ["views/subscriptions.html"]);
assert.equal(initCalled, true);
} finally {
restore();
}
});