This commit is contained in:
thePR0M3TH3AN committed 2025-11-12 11:06:10 -05:00
1 parent 000127f88c
commit 071d78dd6c
194 files changed
+28793 -3944

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
import test from "node:test";
import assert from "node:assert/strict";
import { JSDOM } from "jsdom";
import AppChromeController from "../../js/ui/appChromeController.js";
test("AppChromeController binds upload button when elements hydrate later", () => {
const dom = new JSDOM(`<!DOCTYPE html><body><button id="upload"></button></body>`, {
url: "https://example.com",
});
const { window } = dom;
const previousWindow = global.window;
const previousDocument = global.document;
global.window = window;
global.document = window.document;
try {
const openCalls = [];
const controller = new AppChromeController({
callbacks: {
openUploadModal: (detail) => {
openCalls.push(detail);
},
},
logger: {
error: () => {},
log: () => {},
},
});
controller.initialize();
const uploadButton = window.document.getElementById("upload");
controller.setElements({ uploadButton });
uploadButton.dispatchEvent(new window.Event("click", { bubbles: true }));
assert.equal(openCalls.length, 1);
assert.equal(openCalls[0]?.triggerElement, uploadButton);
const replacement = window.document.createElement("button");
window.document.body.appendChild(replacement);
controller.setElements({ uploadButton: replacement });
uploadButton.dispatchEvent(new window.Event("click", { bubbles: true }));
assert.equal(openCalls.length, 1);
replacement.dispatchEvent(new window.Event("click", { bubbles: true }));
assert.equal(openCalls.length, 2);
assert.equal(openCalls[1]?.triggerElement, replacement);
} finally {
global.window = previousWindow;
global.document = previousDocument;
}
});
+1
View File
@@ -0,0 +1 @@
import { ProfileModalController } from '../../js/ui/profileModalController.js';&#10;import { userLogger } from '../../js/utils/logger.js';&#10;&#10;describe('ProfileModalController - Direct Messages Error Handling', () =&gt; {&#10; let controller;&#10; let mockUserLoggerWarn;&#10;&#10; beforeEach(() =&gt; {&#10; jest.spyOn(userLogger, 'warn').mockImplementation(() =&gt; {});&#10; mockUserLoggerWarn = jest.spyOn(userLogger, 'warn');&#10; controller = new ProfileModalController({ /* minimal mocks for services/state */ });&#10; jest.spyOn(controller, 'setMessagesLoadingState');&#10; jest.spyOn(controller, 'setMessagesAnnouncement');&#10; jest.spyOn(controller, 'updateMessagesReloadState');&#10; controller.directMessagesCache = []; // Empty for error state&#10; });&#10;&#10; afterEach(() =&gt; {&#10; jest.restoreAllMocks();&#10; });&#10;&#10; it('handles &quot;no-decryptors&quot; with specific message, no warn log, and enables reload', () =&gt; {&#10; const detail = {&#10; context: { reason: 'no-decryptors' },&#10; error: new Error('Mock signer unlock error')&#10; };&#10; controller.handleDirectMessagesError(detail);&#10;&#10; expect(controller.setMessagesLoadingState).toHaveBeenCalledWith('error', {&#10; message: 'Unlock your signer to read direct messages.'&#10; });&#10; expect(mockUserLoggerWarn).not.toHaveBeenCalled(); // No log for this reason&#10; expect(controller.updateMessagesReloadState).toHaveBeenCalled(); // Reload affordance&#10; });&#10;&#10; it('handles other reasons with generic message, warn log, and enables reload', () =&gt; {&#10; const detail = {&#10; context: { reason: 'relay-failed' }, // Mock real transport error&#10; error: new Error('Mock relay error')&#10; };&#10; controller.handleDirectMessagesError(detail);&#10;&#10; expect(controller.setMessagesLoadingState).toHaveBeenCalledWith('error', {&#10; message: 'Unable to sync direct messages right now.'&#10; });&#10; expect(mockUserLoggerWarn).toHaveBeenCalledWith(&#10; '[profileModal] Direct message sync issue detected:',&#10; detail.error&#10; ); // Warn for transport errors&#10; expect(controller.updateMessagesReloadState).toHaveBeenCalled(); // Reload affordance&#10; });&#10;&#10; it('handles non-empty cache for &quot;no-decryptors&quot; (announcement only, no error state)', () =&gt; {&#10; controller.directMessagesCache = ['mock-message']; // Non-empty&#10; const detail = { context: { reason: 'no-decryptors' } };&#10; controller.handleDirectMessagesError(detail);&#10;&#10; // Uses announcement, not error state&#10; expect(controller.setMessagesAnnouncement).toHaveBeenCalledWith('Unable to sync direct messages right now.');&#10; expect(mockUserLoggerWarn).not.toHaveBeenCalled();&#10; });&#10;&#10; it('maintains behavior for active requests (ignores detail, no state change)', () =&gt; {&#10; controller.activeMessagesRequest = Symbol('active');&#10; const detail = { context: { reason: 'no-decryptors' } };&#10; controller.handleDirectMessagesError(detail);&#10;&#10; expect(controller.setMessagesLoadingState).not.toHaveBeenCalled();&#10; expect(controller.setMessagesAnnouncement).not.toHaveBeenCalled();&#10; expect(mockUserLoggerWarn).toHaveBeenCalled(); // Still logs for diagnostics&#10; });&#10;&#10;});&#10;&#10;// Adjust existing tests to cover &quot;no-decryptors&quot; in DM flows (e.g., error simulation in populateProfileMessages)
@@ -59,6 +59,97 @@ test("VideoListView renders sorted popular tag pills", () => {
);
});
test("VideoListView trims popular tags to available width", () => {
const { dom, document, listRoot, tagsRoot } = createViewDom();
const view = new VideoListView({ document });
view.mount(listRoot);
view.setPopularTagsContainer(tagsRoot);
const prototype = dom.window.HTMLElement.prototype;
const originalScrollWidth = Object.getOwnPropertyDescriptor(
prototype,
"scrollWidth",
);
Object.defineProperty(tagsRoot, "clientWidth", {
configurable: true,
value: 160,
});
Object.defineProperty(prototype, "scrollWidth", {
configurable: true,
get() {
if (this.classList && this.classList.contains("video-tag-strip")) {
return this.childElementCount * 80;
}
if (originalScrollWidth && typeof originalScrollWidth.get === "function") {
return originalScrollWidth.get.call(this);
}
return 0;
},
});
try {
const videos = [
{
id: "v1",
title: "First",
pubkey: "pub1",
created_at: 3,
nip71: { hashtags: ["Nostr", "Video"] },
},
{
id: "v2",
title: "Second",
pubkey: "pub2",
created_at: 2,
tags: [
["t", "nostr"],
["t", "Learning"],
],
},
{
id: "v3",
title: "Third",
pubkey: "pub3",
created_at: 1,
nip71: { hashtags: ["video"] },
},
];
view.render(videos);
let buttons = tagsRoot.querySelectorAll("button");
assert.equal(buttons.length, 2);
assert.deepEqual(
Array.from(buttons, (button) => button.dataset.tag),
["Nostr", "Video"],
);
Object.defineProperty(tagsRoot, "clientWidth", {
configurable: true,
value: 400,
});
view.renderPopularTagStrip();
buttons = tagsRoot.querySelectorAll("button");
assert.equal(buttons.length, 3);
assert.deepEqual(
Array.from(buttons, (button) => button.dataset.tag),
["Nostr", "Video", "Learning"],
);
} finally {
if (originalScrollWidth) {
Object.defineProperty(prototype, "scrollWidth", originalScrollWidth);
} else {
delete prototype.scrollWidth;
}
}
});
test("VideoListView applies tag preference variants to popular tags", () => {
const { document, listRoot, tagsRoot } = createViewDom();
const view = new VideoListView({ document });