Admin: validate raw emoji input instead of trimming it
The backend rejects whitespace in emoji keys, so an entry like " 🐶 " must fail the picker's validation rather than be saved as emoji:🐶. Only an exactly empty field clears the icon; whitespace-only text now shows the invalid-emoji error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1282,10 +1282,9 @@ describe("DefinitionsTab Font Awesome and emoji icons", () => {
|
||||
|
||||
it("builds and validates emoji keys like the registry", () => {
|
||||
expect(emojiIconKey("🐶")).toBe("emoji:🐶");
|
||||
expect(emojiIconKey(" 🐶 ")).toBe("emoji:🐶");
|
||||
expect(emojiIconKey("👨👩👧👦")).toBe("emoji:👨👩👧👦");
|
||||
expect(emojiIconKey("⭐".repeat(8))).toBe(`emoji:${"⭐".repeat(8)}`);
|
||||
for (const bad of ["", " ", "⭐".repeat(9), "a b", "<b>", "a&b", "🐶\u0007"]) {
|
||||
for (const bad of ["", " ", " 🐶 ", "🐶 ", "\t🐶", "⭐".repeat(9), "a b", "<b>", "a&b", "🐶\u0007"]) {
|
||||
expect(emojiIconKey(bad), JSON.stringify(bad)).toBeNull();
|
||||
}
|
||||
expect(faIconName("fa:dog")).toBe("dog");
|
||||
@@ -1335,7 +1334,7 @@ describe("DefinitionsTab Font Awesome and emoji icons", () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: "New" }));
|
||||
const s = sheet();
|
||||
fillNewTask(s);
|
||||
fireEvent.change(emojiInput(s), { target: { value: " 🐶 " } });
|
||||
fireEvent.change(emojiInput(s), { target: { value: "🐶" } });
|
||||
expect(radio(s, "Icon", "No icon").getAttribute("aria-checked")).toBe("false");
|
||||
|
||||
await save(s);
|
||||
@@ -1358,7 +1357,7 @@ describe("DefinitionsTab Font Awesome and emoji icons", () => {
|
||||
expect(rowGlyph(await screen.findByTestId("definition-99"))).toBe("lucide:dog");
|
||||
});
|
||||
|
||||
for (const bad of ["a b", "<b>", "a&b", "⭐".repeat(9)]) {
|
||||
for (const bad of [" 🐶 ", "🐶 ", " ", "a b", "<b>", "a&b", "⭐".repeat(9)]) {
|
||||
it(`an invalid emoji entry ${JSON.stringify(bad)} is not saved`, async () => {
|
||||
await renderReady([{ ...BRUSH, id: 50, icon: "lucide:dog" }]);
|
||||
fireEvent.click(screen.getByTestId("definition-50"));
|
||||
|
||||
@@ -249,7 +249,7 @@ function buildBody(draft: Draft, keepsAssignees = false): DefinitionCreateBody |
|
||||
if (!title) return "Give the task a title.";
|
||||
if (draft.assigneeIds.length === 0 && !keepsAssignees) return "Assign at least one child.";
|
||||
if (draft.windows.length === 0) return "Pick at least one window.";
|
||||
if (draft.emoji.trim() && draft.icon === null) {
|
||||
if (draft.emoji && draft.icon === null) {
|
||||
return "Enter a single emoji (no spaces or < > &), or clear the emoji field.";
|
||||
}
|
||||
const rule = buildRule(draft);
|
||||
@@ -590,10 +590,10 @@ function EditSheet({
|
||||
<button
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={draft.icon === null && !draft.emoji.trim()}
|
||||
aria-checked={draft.icon === null && !draft.emoji}
|
||||
aria-label="No icon"
|
||||
className={
|
||||
draft.icon === null && !draft.emoji.trim() ? "defs-icon defs-icon--on" : "defs-icon"
|
||||
draft.icon === null && !draft.emoji ? "defs-icon defs-icon--on" : "defs-icon"
|
||||
}
|
||||
onClick={() => update({ icon: null, emoji: "" })}
|
||||
>
|
||||
|
||||
@@ -32,12 +32,13 @@ export function faIconKey(name: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* The stored key for typed emoji text, e.g. `emoji:🐶`. Edge whitespace is
|
||||
* trimmed; the rest must pass the registry's emoji check (1-8 code points,
|
||||
* no whitespace, control characters or `<`, `>`, `&`), else null.
|
||||
* The stored key for typed emoji text, e.g. `emoji:🐶`. The raw text — not
|
||||
* trimmed, as the backend rejects any whitespace — must pass the registry's
|
||||
* emoji check (1-8 code points, no whitespace, control characters or `<`,
|
||||
* `>`, `&`), else null. Only an empty field means "no icon".
|
||||
*/
|
||||
export function emojiIconKey(text: string): string | null {
|
||||
const key = `${EMOJI_PREFIX}${text.trim()}`;
|
||||
const key = `${EMOJI_PREFIX}${text}`;
|
||||
return resolveIcon(key).kind === "emoji" ? key : null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user