1import { describe, expect, it } from "vite-plus/test";
2
3import { composerEmojiKeyAction, resolveComposerMenuActiveItemId } from "./composerMenuHighlight";
4
5describe("resolveComposerMenuActiveItemId", () => {
6 const items = [{ id: "top" }, { id: "second" }, { id: "third" }] as const;
7
8 it("defaults to the first item when nothing is highlighted", () => {
9 expect(
10 resolveComposerMenuActiveItemId({
11 items,
12 highlightedItemId: null,
13 currentSearchKey: "skill:u",
14 highlightedSearchKey: null,
15 }),
16 ).toBe("top");
17 });
18
19 it("preserves the highlighted item within the same query", () => {
20 expect(
21 resolveComposerMenuActiveItemId({
22 items,
23 highlightedItemId: "second",
24 currentSearchKey: "skill:u",
25 highlightedSearchKey: "skill:u",
26 }),
27 ).toBe("second");
28 });
29
30 it("resets to the top result when the query changes", () => {
31 expect(
32 resolveComposerMenuActiveItemId({
33 items,
34 highlightedItemId: "second",
35 currentSearchKey: "skill:ui",
36 highlightedSearchKey: "skill:u",
37 }),
38 ).toBe("top");
39 });
40
41 it("falls back to the first item when the highlighted item disappears", () => {
42 expect(
43 resolveComposerMenuActiveItemId({
44 items,
45 highlightedItemId: "missing",
46 currentSearchKey: "skill:ui",
47 highlightedSearchKey: "skill:ui",
48 }),
49 ).toBe("top");
50 });
51
52 it("clears the active result while async results are empty and resolves against restored results", () => {
53 const search = {
54 highlightedItemId: "second",
55 currentSearchKey: "path:src",
56 highlightedSearchKey: "path:src",
57 };
58 const cleared = resolveComposerMenuActiveItemId({ ...search, items: [] });
59 expect(cleared).toBeNull();
60 expect(
61 resolveComposerMenuActiveItemId({
62 ...search,
63 highlightedItemId: cleared,
64 items: [{ id: "new-result" }, { id: "second" }],
65 }),
66 ).toBe("new-result");
67 });
68});
69
70describe("emoji autocomplete keys", () => {
71 const key = (key: string, overrides: Partial<KeyboardEvent> = {}) => ({
72 key,
73 shiftKey: false,
74 altKey: false,
75 metaKey: false,
76 ctrlKey: false,
77 isComposing: false,
78 keyCode: 0,
79 ...overrides,
80 });
81
82 it.each([
83 ["Enter", "select"],
84 ["Tab", "select"],
85 ["ArrowDown", "next"],
86 ["ArrowUp", "previous"],
87 ["Escape", "dismiss"],
88 ] as const)("claims %s without allowing submission or moving editor focus", (input, action) =>
89 expect(composerEmojiKeyAction(key(input))).toBe(action),
90 );
91
92 it("leaves newline, focus navigation, send chords, and ordinary typing alone", () => {
93 expect(composerEmojiKeyAction(key("Enter", { shiftKey: true }))).toBeNull();
94 expect(composerEmojiKeyAction(key("Tab", { shiftKey: true }))).toBeNull();
95 expect(composerEmojiKeyAction(key("Enter", { metaKey: true }))).toBeNull();
96 expect(composerEmojiKeyAction(key("Enter", { ctrlKey: true }))).toBeNull();
97 expect(composerEmojiKeyAction(key("z"))).toBeNull();
98 });
99
100 it("never selects or dismisses during IME composition", () => {
101 for (const input of ["Enter", "Tab", "ArrowDown", "ArrowUp", "Escape"]) {
102 expect(composerEmojiKeyAction(key(input, { isComposing: true }))).toBeNull();
103 expect(composerEmojiKeyAction(key(input, { keyCode: 229 }))).toBeNull();
104 }
105 });
106});