mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-25 17:22:27 +08:00
fix(skills): stop the import dialog from locking while discover refetches
The import mutation's onSettled awaited every affected refresh, including the discover list, which downloads each repository from GitHub (up to 60s per repo). TanStack Query only settles a mutation after onSettled resolves, so isPending stayed true and the import dialog could not be cancelled or closed until discover finished. The discover segment stays mounted once opened, so this hit anyone who had visited it on a slow GitHub connection. Only await the installed and unmanaged refreshes the dialog depends on; repos and discover are still invalidated in the background. Fixes #7994
This commit is contained in:
1 parent
c1cb531fc9
commit
033017b0be
2 files changed
+56
-17
No files matched your search
+10
-7
@@ -288,15 +288,18 @@ export function useImportSkillsFromApps() {
|
||||
},
|
||||
// Import may persist Skills or auto-discovered repositories before a
|
||||
// later item fails, so refresh every affected authoritative collection.
|
||||
onSettled: () =>
|
||||
Promise.all([
|
||||
// 只等导入弹窗关心的两个列表:mutation 要等 onSettled 返回的 Promise
|
||||
// 才结束,「发现」重拉要从 GitHub 下载仓库,等它会让弹窗一直锁着(#7994)
|
||||
onSettled: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["skills", "repos"] });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ["skills", "discoverable"],
|
||||
});
|
||||
return Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["skills", "installed"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["skills", "unmanaged"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["skills", "repos"] }),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["skills", "discoverable"],
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -285,16 +285,52 @@ describe("Skills install and import mutation hooks", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps a rejected import pending until all affected caches refresh", async () => {
|
||||
let releaseInvalidation: (() => void) | undefined;
|
||||
const invalidationPending = new Promise<void>((resolve) => {
|
||||
releaseInvalidation = resolve;
|
||||
// #7994:「发现」重拉要从 GitHub 下载仓库,可能很久都不返回;导入只等
|
||||
// installed / unmanaged,不能因为它一直 pending、把导入弹窗锁住
|
||||
function spyImportInvalidations(queryClient: QueryClient) {
|
||||
let releaseLocal: (() => void) | undefined;
|
||||
const localPending = new Promise<void>((resolve) => {
|
||||
releaseLocal = resolve;
|
||||
});
|
||||
const spy = vi
|
||||
.spyOn(queryClient, "invalidateQueries")
|
||||
.mockImplementation((filters) => {
|
||||
const scope = filters?.queryKey?.[1];
|
||||
return scope === "installed" || scope === "unmanaged"
|
||||
? localPending
|
||||
: new Promise<void>(() => undefined);
|
||||
});
|
||||
return { spy, releaseLocal: () => releaseLocal?.() };
|
||||
}
|
||||
|
||||
it("finishes an import without waiting for repos or discover to refetch", async () => {
|
||||
const queryClient = createQueryClient();
|
||||
const imported = makeSkill({ name: "Imported Skill" });
|
||||
apiMocks.importFromApps.mockResolvedValueOnce([imported]);
|
||||
const { spy, releaseLocal } = spyImportInvalidations(queryClient);
|
||||
const { result } = renderHook(() => useImportSkillsFromApps(), {
|
||||
wrapper: createWrapper(queryClient),
|
||||
});
|
||||
|
||||
let mutation!: Promise<InstalledSkill[]>;
|
||||
act(() => {
|
||||
mutation = result.current.mutateAsync([]);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(spy).toHaveBeenCalledTimes(4));
|
||||
expect(result.current.isPending).toBe(true);
|
||||
|
||||
releaseLocal();
|
||||
await act(async () => {
|
||||
await expect(mutation).resolves.toEqual([imported]);
|
||||
});
|
||||
await waitFor(() => expect(result.current.isPending).toBe(false));
|
||||
});
|
||||
|
||||
it("keeps a rejected import pending only until installed and unmanaged refresh", async () => {
|
||||
const queryClient = createQueryClient();
|
||||
apiMocks.importFromApps.mockRejectedValueOnce(new Error("import failed"));
|
||||
const invalidateSpy = vi
|
||||
.spyOn(queryClient, "invalidateQueries")
|
||||
.mockImplementation(() => invalidationPending);
|
||||
const { spy, releaseLocal } = spyImportInvalidations(queryClient);
|
||||
const { result } = renderHook(() => useImportSkillsFromApps(), {
|
||||
wrapper: createWrapper(queryClient),
|
||||
});
|
||||
@@ -305,7 +341,7 @@ describe("Skills install and import mutation hooks", () => {
|
||||
void mutation.catch(() => undefined);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(invalidateSpy).toHaveBeenCalledTimes(4));
|
||||
await waitFor(() => expect(spy).toHaveBeenCalledTimes(4));
|
||||
expect(result.current.isPending).toBe(true);
|
||||
for (const queryKey of [
|
||||
["skills", "installed"],
|
||||
@@ -313,10 +349,10 @@ describe("Skills install and import mutation hooks", () => {
|
||||
["skills", "repos"],
|
||||
["skills", "discoverable"],
|
||||
]) {
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey });
|
||||
expect(spy).toHaveBeenCalledWith({ queryKey });
|
||||
}
|
||||
|
||||
releaseInvalidation?.();
|
||||
releaseLocal();
|
||||
await act(async () => {
|
||||
await expect(mutation).rejects.toThrow("import failed");
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user