Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions apps/web/src/components/CommandPalette.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { describe, expect, it, vi } from "vite-plus/test";
import { EnvironmentId, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts";
import type { Thread } from "../types";
import {
buildBrowseGroups,
buildThreadActionItems,
canPreloadBrowsePath,
createBrowseNavigationCoordinator,
enumerateCommandPaletteItems,
filterCommandPaletteGroups,
type CommandPaletteGroup,
Expand Down Expand Up @@ -193,3 +196,103 @@ describe("buildThreadActionItems", () => {
expect(items.map((item) => item.value)).toEqual(["thread:thread-active"]);
});
});

describe("buildBrowseGroups", () => {
it("waits for asynchronous browse navigation actions", async () => {
let finishNavigation: (() => void) | undefined;
const browseTo = vi.fn(
() =>
new Promise<void>((resolve) => {
finishNavigation = resolve;
}),
);
const groups = buildBrowseGroups({
browseEntries: [{ name: "Downloads", fullPath: "/Users/test/Downloads" }],
browseQuery: "~/",
canBrowseUp: false,
upIcon: null,
directoryIcon: null,
browseUp: vi.fn(),
browseTo,
});
const item = groups[0]?.items[0];
if (!item || item.kind !== "action") {
throw new Error("Expected a browse action");
}

let actionSettled = false;
const action = item.run().then(() => {
actionSettled = true;
});
await Promise.resolve();

expect(browseTo).toHaveBeenCalledWith("Downloads");
expect(actionSettled).toBe(false);

finishNavigation?.();
await action;
expect(actionSettled).toBe(true);
});
});

describe("createBrowseNavigationCoordinator", () => {
it("only commits the latest overlapping navigation", async () => {
const coordinator = createBrowseNavigationCoordinator();
let finishFirst: (() => void) | undefined;
let finishSecond: (() => void) | undefined;
const commits: string[] = [];

const first = coordinator.run({
load: () =>
new Promise<void>((resolve) => {
finishFirst = resolve;
}),
commit: () => {
commits.push("first");
},
});
const second = coordinator.run({
load: () =>
new Promise<void>((resolve) => {
finishSecond = resolve;
}),
commit: () => {
commits.push("second");
},
});

finishSecond?.();
await expect(second).resolves.toBe(true);
finishFirst?.();
await expect(first).resolves.toBe(false);
expect(commits).toEqual(["second"]);
});

it("does not commit after newer user input invalidates the navigation", async () => {
const coordinator = createBrowseNavigationCoordinator();
let finishNavigation: (() => void) | undefined;
const commit = vi.fn();
const navigation = coordinator.run({
load: () =>
new Promise<void>((resolve) => {
finishNavigation = resolve;
}),
commit,
});

coordinator.invalidate();
finishNavigation?.();

await expect(navigation).resolves.toBe(false);
expect(commit).not.toHaveBeenCalled();
});
});

describe("canPreloadBrowsePath", () => {
it("only preloads paths for connected environments", () => {
expect(canPreloadBrowsePath("connected")).toBe(true);
expect(canPreloadBrowsePath("offline")).toBe(false);
expect(canPreloadBrowsePath("reconnecting")).toBe(false);
expect(canPreloadBrowsePath(null)).toBe(false);
});
});
52 changes: 39 additions & 13 deletions apps/web/src/components/CommandPalette.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type FilesystemBrowseEntry,
THREAD_JUMP_KEYBINDING_COMMANDS,
} from "@t3tools/contracts";
import type { EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection";
import type { SidebarThreadSortOrder } from "@t3tools/contracts/settings";
import * as Arr from "effect/Array";
import * as Result from "effect/Result";
Expand All @@ -15,6 +16,39 @@ export const RECENT_THREAD_LIMIT = 12;
export const ITEM_ICON_CLASS = "size-4 text-muted-foreground/80";
export const ADDON_ICON_CLASS = "size-4";

export interface BrowseNavigationCoordinator {
readonly invalidate: () => void;
readonly run: (input: {
readonly load: () => Promise<void>;
readonly commit: () => void;
}) => Promise<boolean>;
}

export function createBrowseNavigationCoordinator(): BrowseNavigationCoordinator {
let generation = 0;

return {
invalidate: () => {
generation += 1;
},
run: async (input) => {
const navigationGeneration = ++generation;
await input.load();
if (navigationGeneration !== generation) {
return false;
}
input.commit();
return true;
},
};
}

export function canPreloadBrowsePath(
connectionPhase: EnvironmentConnectionPhase | null | undefined,
): boolean {
return connectionPhase === "connected";
}

export interface CommandPaletteItem {
readonly kind: "action" | "submenu";
readonly value: string;
Expand Down Expand Up @@ -73,10 +107,8 @@ export type CommandPaletteMode = "root" | "root-browse" | "submenu" | "submenu-b
export function filterBrowseEntries(input: {
browseEntries: ReadonlyArray<FilesystemBrowseEntry>;
browseFilterQuery: string;
highlightedItemValue: string | null;
}): {
filteredEntries: FilesystemBrowseEntry[];
highlightedEntry: FilesystemBrowseEntry | null;
exactEntry: FilesystemBrowseEntry | null;
} {
const lowerFilter = input.browseFilterQuery.toLowerCase();
Expand All @@ -88,18 +120,12 @@ export function filterBrowseEntries(input: {
(showHidden || !entry.name.startsWith(".")),
);

let highlightedEntry: FilesystemBrowseEntry | null = null;
if (input.highlightedItemValue?.startsWith("browse:")) {
const highlightedPath = input.highlightedItemValue.slice("browse:".length);
highlightedEntry = filteredEntries.find((entry) => entry.fullPath === highlightedPath) ?? null;
}

const exactEntry =
input.browseFilterQuery.length > 0
? (filteredEntries.find((entry) => entry.name === input.browseFilterQuery) ?? null)
: null;

return { filteredEntries, highlightedEntry, exactEntry };
return { filteredEntries, exactEntry };
}

export function normalizeSearchText(value: string): string {
Expand Down Expand Up @@ -302,8 +328,8 @@ export function buildBrowseGroups(input: {
canBrowseUp: boolean;
upIcon: ReactNode;
directoryIcon: ReactNode;
browseUp: () => void;
browseTo: (name: string) => void;
browseUp: () => void | Promise<void>;
browseTo: (name: string) => void | Promise<void>;
}): CommandPaletteGroup[] {
const items: CommandPaletteActionItem[] = [];

Expand All @@ -316,7 +342,7 @@ export function buildBrowseGroups(input: {
icon: input.upIcon,
keepOpen: true,
run: async () => {
input.browseUp();
await input.browseUp();
},
});
}
Expand All @@ -330,7 +356,7 @@ export function buildBrowseGroups(input: {
icon: input.directoryIcon,
keepOpen: true,
run: async () => {
input.browseTo(entry.name);
await input.browseTo(entry.name);
},
});
}
Expand Down
Loading
Loading