Merge pull request #679 from PR0M3TH3AN/codex/update-zap-handler-and-test-for-login

Ensure zap dialog closes when login is required
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-19 18:46:24 -04:00
commit 1c5530899a
3 files changed
+203 -15

No files matched your search

+28 -6
View File
@@ -692,12 +692,22 @@ class Application {
};
this.boundVideoModalZapOpenHandler = (event) => {
const requiresLogin = Boolean(event?.detail?.requiresLogin);
if (requiresLogin) {
this.pendingModalZapOpen = true;
} else {
this.pendingModalZapOpen = false;
this.pendingModalZapOpen = requiresLogin;
const openResult = this.zapController?.open({ requiresLogin });
if (!openResult) {
event?.preventDefault?.();
if (!requiresLogin) {
this.pendingModalZapOpen = false;
}
this.videoModal?.closeZapDialog?.({
silent: true,
restoreFocus: false,
});
return;
}
this.zapController?.open({ requiresLogin });
this.pendingModalZapOpen = false;
};
this.boundVideoModalZapCloseHandler = () => {
this.zapController?.close();
@@ -3029,7 +3039,19 @@ class Application {
}
isUserLoggedIn() {
return Boolean(this.normalizeHexPubkey(this.pubkey));
const normalizedPubkey = this.normalizeHexPubkey(this.pubkey);
if (!normalizedPubkey) {
return false;
}
const sessionActorPubkey = this.normalizeHexPubkey(
nostrClient?.sessionActor?.pubkey,
);
if (sessionActorPubkey && sessionActorPubkey === normalizedPubkey) {
return false;
}
return true;
}
async updateActiveNwcSettings(partial = {}) {
+29 -8
View File
@@ -128,9 +128,14 @@ export class VideoModal {
this.eventTarget.removeEventListener(type, listener, options);
}
dispatch(type, detail) {
const event = new CustomEvent(type, { detail });
this.eventTarget.dispatchEvent(event);
dispatch(type, detail, options = {}) {
const config =
options && typeof options === "object" ? { ...options } : Object.create(null);
const event = new CustomEvent(type, {
detail,
cancelable: Boolean(config.cancelable),
});
return this.eventTarget.dispatchEvent(event);
}
getRoot() {
@@ -420,10 +425,14 @@ export class VideoModal {
}
if (this.modalZapRequiresLogin) {
this.dispatch("zap:open", {
video: this.activeVideo,
requiresLogin: true,
});
this.dispatch(
"zap:open",
{
video: this.activeVideo,
requiresLogin: true,
},
{ cancelable: false },
);
return;
}
@@ -446,9 +455,17 @@ export class VideoModal {
return;
}
const allowed = this.dispatch(
"zap:open",
{ video: this.activeVideo },
{ cancelable: true },
);
if (allowed === false) {
return;
}
this.modalZapPendingToggle = null;
this.openZapDialog();
this.dispatch("zap:open", { video: this.activeVideo });
});
}
@@ -1054,6 +1071,10 @@ export class VideoModal {
}
async openZapDialog() {
if (this.modalZapRequiresLogin) {
return Promise.resolve(false);
}
if (this.modalZapOpenPromise) {
return this.modalZapOpenPromise;
}
+146 -1
View File
@@ -182,10 +182,26 @@ function createVideoModalStub() {
return {
setCopyEnabled: noop,
setShareEnabled: noop,
setZapVisibility: noop,
setWalletPromptVisible: noop,
zapVisibilityCalls: [],
setZapVisibility(config) {
const payload =
config && typeof config === "object"
? { ...config }
: { visible: Boolean(config) };
this.zapVisibilityCalls.push(payload);
this.lastZapVisibility = payload;
},
dialogOpen: false,
closeZapDialogCalls: [],
closeZapDialog(options = {}) {
this.closeZapDialogCalls.push(options);
this.dialogOpen = false;
},
resetStats: noop,
updateViewCountLabel: noop,
setViewCountPointer: noop,
completedStates: [],
addEventListener(type, handler) {
listeners.set(type, handler);
},
@@ -202,6 +218,9 @@ function createVideoModalStub() {
setZapPending(value) {
this.setZapPendingCalls.push(Boolean(value));
},
setZapCompleted(value) {
this.completedStates.push(Boolean(value));
},
setZapStatus(message, tone) {
this.statusMessages.push({ message, tone });
},
@@ -297,6 +316,132 @@ async function createApp({ splitAndZap }) {
return { app, modalStub, creatorAddress, platformAddress };
}
await (async () => {
// Test: logged-out zap request closes modal and queues reopen
const { app, modalStub } = await createApp({
splitAndZap: async () => ({ receipts: [] }),
});
let loginNotificationCount = 0;
app.zapController.notifyLoginRequired = () => {
loginNotificationCount += 1;
};
modalStub.dialogOpen = true;
modalStub.closeZapDialogCalls = [];
app.pendingModalZapOpen = false;
app.boundVideoModalZapOpenHandler({ detail: { requiresLogin: true } });
assert.equal(
loginNotificationCount,
1,
"should request login notification when zapping logged out",
);
assert.equal(
app.pendingModalZapOpen,
true,
"zap should remain pending for post-login reopen",
);
assert.equal(
modalStub.closeZapDialogCalls.length,
1,
"zap dialog should close immediately when login required",
);
assert.deepEqual(modalStub.closeZapDialogCalls[0], {
silent: true,
restoreFocus: false,
});
assert.equal(modalStub.dialogOpen, false, "zap dialog should be closed");
app.destroy();
})();
await (async () => {
// Test: preventDefault is called when zap opening is rejected post-dispatch
const { app, modalStub } = await createApp({
splitAndZap: async () => ({ receipts: [] }),
});
let loginNotificationCount = 0;
app.zapController.notifyLoginRequired = () => {
loginNotificationCount += 1;
};
app.zapController.isUserLoggedIn = () => false;
modalStub.closeZapDialogCalls = [];
const preventCalls = [];
const event = {
detail: { requiresLogin: false },
preventDefault: () => {
preventCalls.push(true);
},
};
app.pendingModalZapOpen = true;
app.boundVideoModalZapOpenHandler(event);
assert.equal(
loginNotificationCount,
1,
"should notify login requirement when zap open is rejected",
);
assert.equal(
preventCalls.length,
1,
"should prevent default zap opening when controller rejects it",
);
assert.equal(
app.pendingModalZapOpen,
false,
"pending zap reopen flag should clear when rejection wasn't login-gated",
);
assert.deepEqual(modalStub.closeZapDialogCalls[0], {
silent: true,
restoreFocus: false,
});
app.destroy();
})();
await (async () => {
// Test: session actors should not be treated as logged-in users for zaps
const { app, modalStub } = await createApp({
splitAndZap: async () => ({ receipts: [] }),
});
const { nostrClient } = await import("../js/nostr.js");
const originalSessionActor = nostrClient.sessionActor;
const sessionPubkey = "a".repeat(64);
nostrClient.sessionActor = { pubkey: sessionPubkey, privateKey: "priv" };
app.pubkey = sessionPubkey;
modalStub.zapVisibilityCalls = [];
app.zapController.setVisibility(true);
const lastVisibility =
modalStub.zapVisibilityCalls[modalStub.zapVisibilityCalls.length - 1];
assert.deepEqual(
lastVisibility,
{ visible: true, requiresLogin: true },
"zap controller should require login when only a session actor is present",
);
assert.equal(
app.isUserLoggedIn(),
false,
"session actor pubkeys should not count as logged-in users",
);
app.pubkey = null;
nostrClient.sessionActor = originalSessionActor;
app.destroy();
})();
await (async () => {
// Test: wallet required before zapping
const splitCalls = [];