mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
4f6a433dc8
## 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
135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
// Mocking the obscenity library to control its behavior in tests.
|
|
jest.mock("obscenity", () => {
|
|
return {
|
|
RegExpMatcher: class {
|
|
private dummy: string[] = ["foo", "bar", "leet", "code"];
|
|
constructor(_opts: any) {}
|
|
hasMatch(input: string): boolean {
|
|
const lower = input.toLowerCase();
|
|
const decoded = lower
|
|
.replace(/4/g, "a")
|
|
.replace(/3/g, "e")
|
|
.replace(/1/g, "i")
|
|
.replace(/0/g, "o")
|
|
.replace(/5/g, "s")
|
|
.replace(/7/g, "t");
|
|
return this.dummy.some((token) => decoded.includes(token));
|
|
}
|
|
},
|
|
collapseDuplicatesTransformer: () => ({}),
|
|
englishRecommendedTransformers: {},
|
|
englishDataset: { build: () => ({}) },
|
|
resolveConfusablesTransformer: () => ({}),
|
|
resolveLeetSpeakTransformer: () => ({}),
|
|
skipNonAlphabeticTransformer: () => ({}),
|
|
};
|
|
});
|
|
|
|
// Mocks the output of translation functions to return predictable values.
|
|
jest.mock("../src/client/Utils", () => ({
|
|
translateText: (key: string, vars?: any) =>
|
|
vars ? `${key}:${JSON.stringify(vars)}` : key,
|
|
}));
|
|
|
|
import {
|
|
fixProfaneUsername,
|
|
isProfaneUsername,
|
|
MAX_USERNAME_LENGTH,
|
|
MIN_USERNAME_LENGTH,
|
|
sanitizeUsername,
|
|
validateUsername,
|
|
} from "../src/core/validations/username";
|
|
|
|
describe("username.ts functions", () => {
|
|
const shadowNames = [
|
|
"NicePeopleOnly",
|
|
"BeKindPlz",
|
|
"LearningManners",
|
|
"StayClassy",
|
|
"BeNicer",
|
|
"NeedHugs",
|
|
"MakeFriends",
|
|
];
|
|
|
|
describe("isProfaneUsername & fixProfaneUsername with leet decoding (mocked)", () => {
|
|
test.each([
|
|
{ username: "l33t", profane: true }, // decodes to "leet"
|
|
{ username: "L33T", profane: true },
|
|
{ username: "l33tc0de", profane: true }, // decodes to "leetcode", contains "leet" and "code"
|
|
{ username: "L33TC0DE", profane: true },
|
|
{ username: "foo123", profane: true }, // contains "foo"
|
|
{ username: "b4r", profane: true }, // decodes to "bar"
|
|
{ username: "safeName", profane: false },
|
|
{ username: "s4f3", profane: false }, // decodes to "safe" but "safe" not in dummy list
|
|
])('isProfaneUsername("%s") → %s', ({ username, profane }) => {
|
|
expect(isProfaneUsername(username)).toBe(profane);
|
|
});
|
|
|
|
test.each([
|
|
{ username: "safeName" },
|
|
{ username: "l33t" },
|
|
{ username: "b4rUser" },
|
|
])('fixProfaneUsername("%s") behavior', ({ username }) => {
|
|
const profane = isProfaneUsername(username);
|
|
const fixed = fixProfaneUsername(username);
|
|
if (!profane) {
|
|
expect(fixed).toBe(username);
|
|
} else {
|
|
// When profane: result should be one of shadowNames
|
|
expect(shadowNames).toContain(fixed);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("validateUsername", () => {
|
|
test("rejects non-string", () => {
|
|
// @ts-expect-error: Testing non-string input to validateUsername on purpose
|
|
const res = validateUsername(123);
|
|
expect(res.isValid).toBe(false);
|
|
expect(res.error).toBeDefined();
|
|
});
|
|
test("rejects too short", () => {
|
|
const res = validateUsername("ab");
|
|
expect(res.isValid).toBe(false);
|
|
});
|
|
test("rejects too long", () => {
|
|
const long = "a".repeat(MAX_USERNAME_LENGTH + 1);
|
|
const res = validateUsername(long);
|
|
expect(res.isValid).toBe(false);
|
|
});
|
|
test("rejects invalid chars", () => {
|
|
const res = validateUsername("Invalid!Name");
|
|
expect(res.isValid).toBe(false);
|
|
});
|
|
test("accepts valid ASCII names", () => {
|
|
const res = validateUsername("Good_Name123");
|
|
expect(res.isValid).toBe(true);
|
|
});
|
|
test("accepts allowed Unicode like ü", () => {
|
|
const res = validateUsername("Üser");
|
|
expect(res.isValid).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("sanitizeUsername", () => {
|
|
test.each([
|
|
{ input: "GoodName", expected: "GoodName" },
|
|
{ input: "a!", expected: "axx" },
|
|
{ input: "a$%b", expected: "abx" },
|
|
{
|
|
input: "abc".repeat(10),
|
|
expected: "abc"
|
|
.repeat(Math.floor(MAX_USERNAME_LENGTH / 3))
|
|
.slice(0, MAX_USERNAME_LENGTH),
|
|
},
|
|
{ input: "", expected: "xxx" },
|
|
{ input: "Ünicode Test!", expected: "Ünicode Test" },
|
|
])('sanitizeUsername("%s") → "%s"', ({ input, expected }) => {
|
|
const out = sanitizeUsername(input);
|
|
expect(out).toBe(expected);
|
|
expect(out.length).toBeGreaterThanOrEqual(MIN_USERNAME_LENGTH);
|
|
expect(out.length).toBeLessThanOrEqual(MAX_USERNAME_LENGTH);
|
|
});
|
|
});
|
|
});
|