Fix Plus voting hotkeys firing while typing or using shortcuts

This commit is contained in:
Kalle
2026-10-16 17:59:23 +03:00
parent 2790543d05
commit 5d362ca990
5 changed files with 84 additions and 0 deletions

View File

@@ -127,6 +127,8 @@ function resolveInitialVotingState(
function useVoteWithKeysEffect(vote: (type: "upvote" | "downvote") => void) {
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!isVoteHotkey(e)) return;
if (e.code === "KeyS") {
vote("downvote");
} else if (e.code === "KeyK") {
@@ -142,6 +144,24 @@ function useVoteWithKeysEffect(vote: (type: "upvote" | "downvote") => void) {
}, [vote]);
}
/** A bare key press not meant for a shortcut, a text field or an open dialog. */
function isVoteHotkey(e: KeyboardEvent) {
if (e.repeat || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) {
return false;
}
const target = e.target;
if (!(target instanceof HTMLElement)) return true;
return !(
target.isContentEditable ||
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLSelectElement ||
target.closest("[popover], dialog")
);
}
function previousUser({
usersForVoting,
votes,

View File

@@ -0,0 +1,5 @@
---
navItem: plus
type: bug
---
Plus Server voting no longer casts votes when typing in search or using keyboard shortcuts like Ctrl+K

View File

@@ -21,10 +21,19 @@ export class PlusVotingPage {
await navigate({ page: this.page, url: PLUS_VOTING_PAGE });
}
progress({ voted, total }: { voted: number; total: number }) {
return this.page.getByTitle(`Voting progress ${voted} out of ${total}`);
}
async upvoteCurrent() {
await this.locators.upvoteButton.click();
}
/** Votes with the desktop keyboard shortcuts: S for -1, K for +1. */
async voteWithKey(type: "upvote" | "downvote") {
await this.page.keyboard.press(type === "upvote" ? "k" : "s");
}
async submitVotes() {
await submit(this.page, this.locators.submitVotesButton);
}

View File

@@ -31,6 +31,19 @@ export class GlobalSearchDialog {
await this.locators.dialog.waitFor({ state: "visible" });
}
/** Desktop Chrome's user agent is not a Mac's, so the shortcut is Ctrl+K. */
async openWithShortcut() {
await this.page.keyboard.press("Control+k");
await this.locators.dialog.waitFor({ state: "visible" });
}
async close() {
// Escape on a filled search input clears it instead of closing the dialog
await this.locators.input.clear();
await this.page.keyboard.press("Escape");
await this.locators.dialog.waitFor({ state: "hidden" });
}
/** Which kind of entity the query searches; remembered across dialog openings. */
async selectType(type: keyof typeof TYPE_LABELS) {
await this.locators.dialog.getByText(TYPE_LABELS[type]).click();
@@ -40,6 +53,11 @@ export class GlobalSearchDialog {
await this.locators.input.fill(query);
}
/** Types key by key, firing keydown events unlike {@link search}. */
async type(query: string) {
await this.locators.input.pressSequentially(query);
}
async selectOption(name: string) {
const option = this.option(name);
await option.waitFor({ state: "visible" });

View File

@@ -20,6 +20,7 @@ import { SuggestionCommentPage } from "./pages/plus/suggestion-comment-page";
import { PlusSuggestionsPage } from "./pages/plus/suggestions-page";
import { PlusVotingPage } from "./pages/plus/voting-page";
import { PlusVotingResultsPage } from "./pages/plus/voting-results-page";
import { GlobalSearchDialog } from "./pages/search/global-search-dialog";
const SUGGESTED_NAME = "SuggestedSue";
const SUGGESTED_DISCORD_ID = "1000000000000000001";
@@ -208,4 +209,35 @@ test.describe("Plus Server", () => {
await expect(voting.locators.votingOngoingInfo).toBeVisible();
await isNotVisible(voting.locators.upvoteButton);
});
test("votes with the S and K keys, but not while using global search", async ({
page,
factories,
}) => {
await factories.UserFactory.grant(ADMIN_ID, { plusTier: 1 });
await factories.UserFactory.grant(NZAP_TEST_ID, { plusTier: 1 });
for (const user of await factories.UserFactory.createMany(2)) {
await factories.UserFactory.grant(user.id, { plusTier: 1 });
}
await impersonate(page);
await setPlusVotingActive(page, true);
const voting = new PlusVotingPage(page);
await voting.goto();
await expect(voting.progress({ voted: 0, total: 3 })).toBeVisible();
await voting.voteWithKey("upvote");
await expect(voting.progress({ voted: 1, total: 3 })).toBeVisible();
const search = new GlobalSearchDialog(page);
await search.openWithShortcut();
await search.type("sk");
await expect(search.locators.input).toHaveValue("sk");
await search.close();
await expect(voting.progress({ voted: 1, total: 3 })).toBeVisible();
await voting.voteWithKey("downvote");
await expect(voting.progress({ voted: 2, total: 3 })).toBeVisible();
});
});