From 4f6a433dc8209749cc0e5d2d61ea8c6cfdaf3c16 Mon Sep 17 00:00:00 2001 From: VariableVince <24507472+VariableVince@users.noreply.github.com> Date: Mon, 15 Dec 2025 03:38:25 +0100 Subject: [PATCH] Fixes & cleans up Username input (#2619) ## Description: **Fix:** Error box like "Username must be at least 3 characters long" sometimes still remains after the game starts. See bug report with screenshot: https://discord.com/channels/1284581928254701718/1449169462426079343/1449169462426079343 This is while in main.ts, element username-validation-error is already set to hidden. Most of the time this works but apparently sometimes there's a re-render which removes the 'hidden' tag again. Most probable solution for this edge case: clear validationError first. Then to make sure, still hide element username-validation-error. **Cleanup username.ts:** - Remove cat and clover emojis from validPattern. For single player games, the only other validity checks are done from GameRunner calling sanitize() from Util.ts. And from GameImpl where from addPlayer the PlayerImpl are created, which uses sanitizeUserName also from username.ts, which uses validPattern again. Both GameRunner and PlayerImpl therefor allow emoji. But for muliplayer there's an extra step where ClientJoinMessageSchema enforces UserNameSchema = SafeString. Which does NOT allow cat or clover emoji. So for multiplayer you get an error and can't join a game with cat or clover emoji. To align their behavior more, just disallow the emojis from validPattern from the start. Almost no-one ever used them anyway, they were once put in because a developer liked to use them before the existance of ClientJoinMessageSchema. There's more consolidation/refactoring possible but this is an important first step. - Remove sending arg max: MAX_USERNAME_LENGTH for translation key invalid_chars, since the translation string doesn't contain params **Cleanup UserNameInput.ts:** - Remove userSettings: isn't used anywhere in the code - Remove dispatchUsernameEvent: nowhere in the repo is event 'username-change' listened to. Also, dispatchUsernameEvent is only called from connectedCallBack but not anywhere else. It seems like handleChange was made for this. Also main.ts calls usernameInput.getCurrentUsername() and doesn't listen for the event either. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 --- src/client/Main.ts | 4 ++++ src/client/UsernameInput.ts | 13 ------------- src/core/validations/username.ts | 6 ++---- tests/Censor.test.ts | 6 +++--- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/client/Main.ts b/src/client/Main.ts index 0461690d1..b163bb2a6 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -502,6 +502,10 @@ class Client { () => { console.log("Closing modals"); document.getElementById("settings-button")?.classList.add("hidden"); + if (this.usernameInput) { + // fix edge case where username-validation-error is re-rendered and hidden tag removed + this.usernameInput.validationError = ""; + } document .getElementById("username-validation-error") ?.classList.add("hidden"); diff --git a/src/client/UsernameInput.ts b/src/client/UsernameInput.ts index 3b3c24b7d..37e26dc00 100644 --- a/src/client/UsernameInput.ts +++ b/src/client/UsernameInput.ts @@ -2,7 +2,6 @@ import { LitElement, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { v4 as uuidv4 } from "uuid"; import { translateText } from "../client/Utils"; -import { UserSettings } from "../core/game/UserSettings"; import { MAX_USERNAME_LENGTH, validateUsername, @@ -15,7 +14,6 @@ export class UsernameInput extends LitElement { @state() private username: string = ""; @property({ type: String }) validationError: string = ""; private _isValid: boolean = true; - private userSettings: UserSettings = new UserSettings(); // Remove static styles since we're using Tailwind @@ -31,7 +29,6 @@ export class UsernameInput extends LitElement { connectedCallback() { super.connectedCallback(); this.username = this.getStoredUsername(); - this.dispatchUsernameEvent(); } render() { @@ -83,16 +80,6 @@ export class UsernameInput extends LitElement { } } - private dispatchUsernameEvent() { - this.dispatchEvent( - new CustomEvent("username-change", { - detail: { username: this.username }, - bubbles: true, - composed: true, - }), - ); - } - private generateNewUsername(): string { const newUsername = "Anon" + this.uuidToThreeDigits(); this.storeUsername(newUsername); diff --git a/src/core/validations/username.ts b/src/core/validations/username.ts index 5d0c26b55..2096c0ae1 100644 --- a/src/core/validations/username.ts +++ b/src/core/validations/username.ts @@ -22,7 +22,7 @@ const matcher = new RegExpMatcher({ export const MIN_USERNAME_LENGTH = 3; export const MAX_USERNAME_LENGTH = 27; -const validPattern = /^[a-zA-Z0-9_[\] 🐈🍀üÜ]+$/u; +const validPattern = /^[a-zA-Z0-9_[\] üÜ]+$/u; // Don't allow more than in UserNameSchema const shadowNames = [ "NicePeopleOnly", @@ -123,9 +123,7 @@ export function validateUsername(username: string): { if (!validPattern.test(username)) { return { isValid: false, - error: translateText("username.invalid_chars", { - max: MAX_USERNAME_LENGTH, - }), + error: translateText("username.invalid_chars"), }; } diff --git a/tests/Censor.test.ts b/tests/Censor.test.ts index 74f9cccc2..a937d4e71 100644 --- a/tests/Censor.test.ts +++ b/tests/Censor.test.ts @@ -105,8 +105,8 @@ describe("username.ts functions", () => { const res = validateUsername("Good_Name123"); expect(res.isValid).toBe(true); }); - test("accepts allowed Unicode like 🐈 or ü", () => { - const res = validateUsername("Cat🐈Üser"); + test("accepts allowed Unicode like ü", () => { + const res = validateUsername("Üser"); expect(res.isValid).toBe(true); }); }); @@ -123,7 +123,7 @@ describe("username.ts functions", () => { .slice(0, MAX_USERNAME_LENGTH), }, { input: "", expected: "xxx" }, - { input: "Ünicode🐈Test!", expected: "Ünicode🐈Test" }, + { input: "Ünicode Test!", expected: "Ünicode Test" }, ])('sanitizeUsername("%s") → "%s"', ({ input, expected }) => { const out = sanitizeUsername(input); expect(out).toBe(expected);