Skip to content
Source
Sign inSign up
Open History to see the last commit

ThemeSearchSection.test.ts

152 lines5.3 KB

1import { describe, expect, it, vi } from "vite-plus/test";
2​
3import type { OpenVsxThemeExtension, OpenVsxThemeSort } from "../../openVsxThemes";
4import { createThemeSearch, type ThemeSearchView } from "./ThemeSearchSection";
5​
6interface Request {
7 readonly query: string;
8 readonly sortBy: OpenVsxThemeSort | undefined;
9 readonly signal: AbortSignal | undefined;
10 resolve(results: OpenVsxThemeExtension[]): void;
11 reject(cause: unknown): void;
13​
14function setup() {
15 vi.useFakeTimers();
16 const requests: Request[] = [];
17 const search = (
18 query: string,
19 options: { signal?: AbortSignal; sortBy?: OpenVsxThemeSort } = {},
20 ) =>
21 new Promise<OpenVsxThemeExtension[]>((resolve, reject) => {
22 requests.push({ query, sortBy: options.sortBy, signal: options.signal, resolve, reject });
23 });
24 const view = {
25 results: null as ReadonlyArray<OpenVsxThemeExtension> | null,
26 error: null as string | null,
27 isSearching: false,
28 };
29 const sink: ThemeSearchView = {
30 setResults: (results) => {
31 view.results = results;
32 },
33 setError: (error) => {
34 view.error = error;
35 },
36 setIsSearching: (isSearching) => {
37 view.isSearching = isSearching;
38 },
39 };
40 const session = new AbortController();
41 const themeSearch = createThemeSearch(sink, session.signal, search);
42 return {
43 requests,
44 view,
45 session,
46 themeSearch,
47 [Symbol.dispose]: () => vi.useRealTimers(),
48 };
50​
51const extension = (name: string) => ({ name }) as OpenVsxThemeExtension;
52const flush = () => vi.advanceTimersByTimeAsync(0);
53​
54describe("createThemeSearch", () => {
55 it("searches once the typed query settles", async () => {
56 using env = setup();
57 env.themeSearch.setQuery("dra");
58 env.themeSearch.setQuery("dracula ");
59 await vi.advanceTimersByTimeAsync(349);
60 expect(env.requests).toHaveLength(0);
61 await vi.advanceTimersByTimeAsync(1);
62 expect(env.requests.map((request) => request.query)).toEqual(["dracula"]);
63 expect(env.view.isSearching).toBe(true);
64​
65 env.requests[0]!.resolve([extension("Dracula")]);
66 await flush();
67 expect(env.view.results).toEqual([extension("Dracula")]);
68 expect(env.view.isSearching).toBe(false);
69 });
70​
71 it("keeps the shown results when typing returns to their query", async () => {
72 using env = setup();
73 env.themeSearch.setQuery("nord");
74 await vi.advanceTimersByTimeAsync(350);
75 env.requests[0]!.resolve([extension("Nord")]);
76 await flush();
77​
78 env.themeSearch.setQuery("nordic");
79 await vi.advanceTimersByTimeAsync(350);
80 env.themeSearch.setQuery("nord");
81 await vi.advanceTimersByTimeAsync(350);
82​
83 expect(env.requests).toHaveLength(2);
84 expect(env.requests[1]!.signal?.aborted).toBe(true);
85 expect(env.view.results).toEqual([extension("Nord")]);
86 expect(env.view.isSearching).toBe(false);
87 });
88​
89 it("searches again right away when the sort changes", async () => {
90 using env = setup();
91 env.themeSearch.setQuery("nord");
92 await vi.advanceTimersByTimeAsync(350);
93 env.themeSearch.setSortBy("rating");
94 expect(env.requests[0]!.signal?.aborted).toBe(true);
95 expect(env.requests[1]).toMatchObject({ query: "nord", sortBy: "rating" });
96 });
97​
98 it("clearing the query drops results and the request in flight", async () => {
99 using env = setup();
100 env.themeSearch.setQuery("nord");
101 await vi.advanceTimersByTimeAsync(350);
102 env.themeSearch.setQuery(" ");
103 expect(env.requests[0]!.signal?.aborted).toBe(true);
104 expect(env.view).toEqual({ results: null, error: null, isSearching: false });
105 });
106​
107 it("holds searches during an install and keeps its error on the shown query", async () => {
108 using env = setup();
109 env.themeSearch.setQuery("nord");
110 await vi.advanceTimersByTimeAsync(350);
111 env.requests[0]!.resolve([extension("Nord")]);
112 await flush();
113​
114 const install = env.themeSearch.beginInstall();
115 env.view.error = "That theme could not be added.";
116 env.themeSearch.searchNow();
117 expect(env.requests).toHaveLength(1);
118 expect(env.themeSearch.endInstall(install)).toBe(true);
119 expect(env.requests).toHaveLength(1);
120 expect(env.view.error).toBe("That theme could not be added.");
121​
122 const next = env.themeSearch.beginInstall();
123 env.themeSearch.setQuery("dracula");
124 await vi.advanceTimersByTimeAsync(350);
125 expect(env.requests).toHaveLength(1);
126 env.themeSearch.endInstall(next);
127 expect(env.requests.map((request) => request.query)).toEqual(["nord", "dracula"]);
128 expect(env.view.error).toBeNull();
129 });
130​
131 it("Enter during the debounce searches once", async () => {
132 using env = setup();
133 env.themeSearch.setQuery("nord");
134 env.themeSearch.searchNow();
135 await vi.advanceTimersByTimeAsync(350);
136 expect(env.requests.map((request) => request.query)).toEqual(["nord"]);
137 expect(env.requests[0]!.signal?.aborted).toBe(false);
138 });
139​
140 it("closing the session aborts the search, the debounce, and the install", async () => {
141 using env = setup();
142 env.themeSearch.setQuery("nord");
143 await vi.advanceTimersByTimeAsync(350);
144 const install = env.themeSearch.beginInstall();
145 env.themeSearch.setQuery("dracula");
146 env.session.abort();
147 await vi.advanceTimersByTimeAsync(350);
148 expect(env.requests[0]!.signal?.aborted).toBe(true);
149 expect(install.aborted).toBe(true);
150 expect(env.requests).toHaveLength(1);
151 });
152});