Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"test": "vp test run --passWithNoTests --project unit --project browser"
},
"dependencies": {
"@base-ui/react": "^1.4.1",
"@base-ui/react": "^1.9.0",
"@betteroffice/docx": "0.4.3",
"@betteroffice/fonts": "0.4.0",
"@betteroffice/pptx": "0.2.0",
Expand Down
13 changes: 5 additions & 8 deletions apps/web/src/components/BranchToolbarBranchSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
} from "@t3tools/contracts";
import { GitBranchIcon } from "lucide-react";
import {
useDeferredValue,
useEffect,
useImperativeHandle,
useOptimistic,
Expand Down Expand Up @@ -216,7 +215,6 @@ export function BranchToolbarBranchSelector({

const [isBranchMenuOpen, setIsBranchMenuOpen] = useState(false);
const [branchQuery, setBranchQuery] = useState("");
const deferredBranchQuery = useDeferredValue(branchQuery);

const branchStatusQuery = useEnvironmentQuery(
branchCwd === null
Expand All @@ -226,18 +224,17 @@ export function BranchToolbarBranchSelector({
input: { cwd: branchCwd },
}),
);
const trimmedBranchQuery = branchQuery.trim();
const branchRefState = usePaginatedBranches({
environmentId,
cwd: branchCwd,
// Substring search needs sanitized names to detect new-branch collisions; refs cannot contain ASCII
// spaces.
query: sanitizeNewRefName(deferredBranchQuery.trim()),
query: sanitizeNewRefName(trimmedBranchQuery),
});
const trimmedBranchQuery = branchQuery.trim();
const deferredTrimmedBranchQuery = deferredBranchQuery.trim();
const refs = branchRefState.refs;
const branchByName = new Map(refs.map((refName) => [refName.name, refName] as const));
const normalizedDeferredBranchQuery = deferredTrimmedBranchQuery.toLowerCase();
const normalizedBranchQuery = trimmedBranchQuery.toLowerCase();
const prReference = parsePullRequestReference(trimmedBranchQuery);
const isSelectingWorktreeBase =
effectiveEnvMode === "worktree" && !envLocked && !activeWorktreePath;
Expand All @@ -260,12 +257,12 @@ export function BranchToolbarBranchSelector({
...(pickerCreateBranchItemValue ? [pickerCreateBranchItemValue] : []),
];
const filteredBranchPickerItems =
normalizedDeferredBranchQuery.length === 0
normalizedBranchQuery.length === 0
? branchPickerItems
: branchPickerItems.filter((itemValue) =>
shouldIncludeBranchPickerItem({
itemValue,
normalizedQuery: normalizedDeferredBranchQuery,
normalizedQuery: normalizedBranchQuery,
createBranchItemValue: pickerCreateBranchItemValue,
checkoutPullRequestItemValue,
}),
Expand Down
77 changes: 77 additions & 0 deletions apps/web/src/components/WorktreeBaseBranchPicker.browser.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { EnvironmentId, type VcsRef } from "@t3tools/contracts";
import { describe, expect, it, vi } from "vite-plus/test";
import { page, userEvent } from "vite-plus/test/browser";

const mocks = vi.hoisted(() => ({ resolvedQueries: new Set<string>() }));

const REFS: VcsRef[] = ["develop", "main", "origin/main", "origin/feature/main-nav"].map(
(name) => ({ name, current: false, isDefault: false, worktreePath: null }),
);

// Like the real hook, a query has no refs until the server answers it.
vi.mock(import("../state/queries"), async (original) => ({
...(await original()),
usePaginatedBranches: (target) => {
const query = target.query ?? "";
const resolved = mocks.resolvedQueries.has(query);
const refs = resolved ? REFS.filter((ref) => ref.name.includes(query)) : [];
return {
data: resolved
? { refs, isRepo: true, hasPrimaryRemote: true, nextCursor: null, totalCount: refs.length }
: null,
refs,
error: null,
isPending: !resolved,
isFetchingNextPage: false,
refresh: () => {},
loadNext: () => {},
};
},
}));

import "~/index.css";
import { render } from "../../test/render";
import { WorktreeBaseBranchPicker } from "./WorktreeBaseBranchPicker";

function setup() {
mocks.resolvedQueries = new Set([""]);
}

describe("WorktreeBaseBranchPicker", () => {
it.each([
{ keys: "{Enter}", selected: "main" },
{ keys: "{ArrowDown}{Enter}", selected: "origin/main" },
])(
"$keys selects $selected when each keystroke's results arrive before the next",
async ({ keys, selected }) => {
setup();
const onValueChange = vi.fn();
const picker = () => (
<WorktreeBaseBranchPicker
environmentId={EnvironmentId.make("primary")}
cwd="/repo"
value=""
onValueChange={onValueChange}
startFromOrigin={false}
onStartFromOriginChange={() => {}}
/>
);
await using screen = await render(picker());

await userEvent.click(screen.getByRole("combobox"));
let query = "";
for (const char of "main") {
await userEvent.type(page.getByPlaceholder("Search refs..."), char);
query += char;
mocks.resolvedQueries.add(query);
await screen.rerender(picker());
}

await expect
.element(page.getByRole("option", { name: "main", exact: true }))
.toHaveAttribute("data-highlighted");
await userEvent.keyboard(keys);
expect(onValueChange).toHaveBeenCalledExactlyOnceWith(selected);
},
);
});
5 changes: 2 additions & 3 deletions apps/web/src/components/WorktreeBaseBranchPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { EnvironmentId } from "@t3tools/contracts";
import { ChevronDownIcon, GitBranchIcon } from "lucide-react";
import { useDeferredValue, useState } from "react";
import { useState } from "react";

import { usePaginatedBranches } from "../state/queries";
import { useEnvironmentQuery } from "../state/query";
Expand Down Expand Up @@ -32,11 +32,10 @@ export function WorktreeBaseBranchPicker({
}) {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query.trim());
const branches = usePaginatedBranches({
environmentId,
cwd,
query: sanitizeNewRefName(deferredQuery),
query: sanitizeNewRefName(query.trim()),
});
const selectedRefQuery = useEnvironmentQuery(
cwd && value
Expand Down
88 changes: 60 additions & 28 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading