mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
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:
3 files changed
+243
-46
No files matched your search
@@ -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
@@ -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,
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user