mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-02 22:52:58 +00:00
fix(plugins): a Combobox shows a failed refresh instead of the old results
The query keeps its cached data when a reopened list fails to refresh, so the old items stayed selectable and the error never showed. A failed search now clears the list so its error shows. The migration guide maps addNewWorkspaceItem, which its registration-coverage test requires, and the superseded-search test waits on a later search instead of sleeping.
This commit is contained in:
@@ -21,6 +21,7 @@ interface Issue extends ComboboxItem {
|
||||
|
||||
const ONE: Issue = { id: "1", label: "Slow sidebar", branch: "one" };
|
||||
const TWO: Issue = { id: "2", label: "Crash on resume", branch: "two" };
|
||||
const THREE: Issue = { id: "3", label: "Crash on launch", branch: "three" };
|
||||
|
||||
interface Search {
|
||||
query: string;
|
||||
@@ -40,7 +41,8 @@ function controlledSearch() {
|
||||
if (!found) throw new Error(`No search for "${query}" yet`);
|
||||
return found;
|
||||
};
|
||||
return { search, forQuery, queries: () => searches.map((candidate) => candidate.query) };
|
||||
const reject = (index: number, error: Error) => searches[index]?.reject(error);
|
||||
return { search, forQuery, reject, queries: () => searches.map((candidate) => candidate.query) };
|
||||
}
|
||||
|
||||
const toast = { error: vi.fn() } as unknown as ToastApi;
|
||||
@@ -124,8 +126,28 @@ describe("plugin Combobox", () => {
|
||||
await waitFor(() => expect(screen.queryByText("Slow sidebar")).toBeNull());
|
||||
controlled.forQuery("c").resolve([ONE]);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
expect(screen.getByText("Crash on resume")).toBeTruthy();
|
||||
// Searches settle in order, so once a later one has landed the stale one has been handled.
|
||||
fireEvent.change(input, { target: { value: "cra" } });
|
||||
await waitFor(() => expect(controlled.queries()).toEqual(["", "c", "cr", "cra"]));
|
||||
controlled.forQuery("cra").resolve([THREE]);
|
||||
await screen.findByText("Crash on launch");
|
||||
expect(screen.queryByText("Slow sidebar")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows the error and not the previous results when a reopened list fails to refresh", async () => {
|
||||
const controlled = controlledSearch();
|
||||
renderCombobox({ search: controlled.search });
|
||||
await waitFor(() => expect(controlled.queries()).toEqual([""]));
|
||||
controlled.forQuery("").resolve([ONE]);
|
||||
await screen.findByText("Slow sidebar");
|
||||
|
||||
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" });
|
||||
await waitFor(() => expect(screen.queryByRole("textbox")).toBeNull());
|
||||
fireEvent.click(screen.getByRole("button", { name: "Issue" }));
|
||||
await waitFor(() => expect(controlled.queries()).toEqual(["", ""]));
|
||||
controlled.reject(1, new Error("tracker is down"));
|
||||
|
||||
expect(await screen.findByText("Search failed: tracker is down")).toBeTruthy();
|
||||
expect(screen.queryByText("Slow sidebar")).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -148,7 +148,8 @@ export function Combobox<Item extends ComboboxItem>({
|
||||
gcTime: 0,
|
||||
retry: false,
|
||||
});
|
||||
const items: readonly Item[] = results.data ?? NO_RESULTS;
|
||||
// A failed search shows its error, not the results of an earlier one.
|
||||
const items: readonly Item[] = results.isError ? NO_RESULTS : (results.data ?? NO_RESULTS);
|
||||
const item = useMemo<ComposerControlItem>(() => {
|
||||
const choices = items.map(({ id, label, description }) => ({ value: id, label, description }));
|
||||
return {
|
||||
|
||||
@@ -81,6 +81,7 @@ Use this table as the complete registration checklist.
|
||||
| `plugin.addClientSide(fn)` in the old root entry | Delete the wrapper and move the body of `fn` into the default client entry function |
|
||||
| `client.addComposerPill(pill)` inside the old client callback | `client.addComposerPill(pill)` inside `index.client.tsx` or an imported `client/` function |
|
||||
| New composer item contribution | `client.addComposerItem(item)` in `index.client.tsx`; see [composer items](reference#composer-items) |
|
||||
| New New workspace item contribution | `client.addNewWorkspaceItem(item)` in `index.client.tsx`; see [New workspace items](reference#new-workspace-items) |
|
||||
| New header contribution | `client.addHeaderButton({ id, workspaceId, button })` |
|
||||
| `plugin.addAttachmentSource(source)` in the old root entry | `client.addAttachmentSource(source)` in `index.client.tsx` |
|
||||
| New settings screen contribution | `client.addSettingsScreen(screen)` in `index.client.tsx`; see [settings screens](reference#settings-screens) |
|
||||
|
||||
@@ -74,6 +74,7 @@ my-plugin/
|
||||
| 旧根入口中的 `plugin.addClientSide(fn)` | 删除这层包装,把 `fn` 的函数体移入默认的客户端入口函数 |
|
||||
| 旧客户端回调中的 `client.addComposerPill(pill)` | `index.client.tsx` 或被导入的 `client/` 函数中的 `client.addComposerPill(pill)` |
|
||||
| 新的编辑器项贡献 | `index.client.tsx` 中的 `client.addComposerItem(item)`;见[编辑器项](reference#composer-items) |
|
||||
| 新的“新建工作区”项贡献 | `index.client.tsx` 中的 `client.addNewWorkspaceItem(item)`;见[新建工作区项](reference#new-workspace-items) |
|
||||
| 新的标题栏贡献 | `client.addHeaderButton({ id, workspaceId, button })` |
|
||||
| 旧根入口中的 `plugin.addAttachmentSource(source)` | `index.client.tsx` 中的 `client.addAttachmentSource(source)` |
|
||||
| 新的设置界面贡献 | `index.client.tsx` 中的 `client.addSettingsScreen(screen)`;见[设置界面](reference#settings-screens) |
|
||||
|
||||
Reference in New Issue
Block a user