* Extract client SDK package * Polish SDK client identity defaults * Build client before dependent CI jobs * Restore daemon client server export * Extract protocol and client SDK packages * Fix provider override schema validation * Fix app test daemon client imports * Simplify workspace build targets * Fix CLI test server build bootstrap * Run SDK package tests in CI * Fix rebase package split drift * Restore lockfile registry metadata * Update SDK config test for prompt default * Move terminal stream router test to client package * Fix rebase drift for protocol imports * Fix SDK agent capability fixture * Restore legacy server client exports * Fix server export compatibility test * Advertise custom mode icon client capability * Remove server daemon-client exports * Format rebased mode control import * Fix rebase drift for protocol imports Files added by upstream PRs (#893, #1147, #1154) referenced the pre-split shared/ paths that this branch moves into @getpaseo/protocol. Redirect those imports to the protocol package so typecheck stays green after the rebase.
150 lines
4.6 KiB
TypeScript
150 lines
4.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { UserComposerAttachment } from "@/attachments/types";
|
|
import type { GitHubSearchItem } from "@getpaseo/protocol/messages";
|
|
import { findCheckoutHintPrAttachment, syncPickerPrAttachment } from "./new-workspace-picker-state";
|
|
|
|
function makePrItem(number: number, title: string, headRefName = "feature/x"): GitHubSearchItem {
|
|
return {
|
|
kind: "pr",
|
|
number,
|
|
title,
|
|
url: `https://example.com/pull/${number}`,
|
|
state: "open",
|
|
body: null,
|
|
labels: [],
|
|
baseRefName: "main",
|
|
headRefName,
|
|
};
|
|
}
|
|
|
|
function prAttachment(
|
|
item: GitHubSearchItem,
|
|
): Extract<UserComposerAttachment, { kind: "github_pr" }> {
|
|
return { kind: "github_pr", item };
|
|
}
|
|
|
|
function issueAttachment(number: number): UserComposerAttachment {
|
|
return {
|
|
kind: "github_issue",
|
|
item: {
|
|
kind: "issue",
|
|
number,
|
|
title: `Issue ${number}`,
|
|
url: `https://example.com/issues/${number}`,
|
|
state: "open",
|
|
body: null,
|
|
labels: [],
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("syncPickerPrAttachment", () => {
|
|
it("selects a PR when no previous picker PR is set", () => {
|
|
const pr = makePrItem(202, "Refactor picker");
|
|
const result = syncPickerPrAttachment({
|
|
attachments: [],
|
|
previousPickerPrNumber: null,
|
|
item: { kind: "github-pr", item: pr },
|
|
});
|
|
expect(result.attachedPrNumber).toBe(202);
|
|
expect(result.attachments).toEqual([prAttachment(pr)]);
|
|
});
|
|
|
|
it("selects a branch without modifying attachments when no previous picker PR", () => {
|
|
const issue = issueAttachment(44);
|
|
const result = syncPickerPrAttachment({
|
|
attachments: [issue],
|
|
previousPickerPrNumber: null,
|
|
item: { kind: "branch", name: "dev" },
|
|
});
|
|
expect(result.attachedPrNumber).toBeNull();
|
|
expect(result.attachments).toEqual([issue]);
|
|
});
|
|
|
|
it("replaces the previous picker PR when a different PR is selected", () => {
|
|
const prA = makePrItem(202, "Refactor picker", "feature/picker");
|
|
const prB = makePrItem(303, "Polish chip", "feature/chip");
|
|
const result = syncPickerPrAttachment({
|
|
attachments: [prAttachment(prA)],
|
|
previousPickerPrNumber: 202,
|
|
item: { kind: "github-pr", item: prB },
|
|
});
|
|
expect(result.attachedPrNumber).toBe(303);
|
|
expect(result.attachments).toEqual([prAttachment(prB)]);
|
|
});
|
|
|
|
it("removes the previous picker PR and adds no new attachment when a branch is selected", () => {
|
|
const pr = makePrItem(202, "Refactor picker");
|
|
const issue = issueAttachment(44);
|
|
const result = syncPickerPrAttachment({
|
|
attachments: [issue, prAttachment(pr)],
|
|
previousPickerPrNumber: 202,
|
|
item: { kind: "branch", name: "dev" },
|
|
});
|
|
expect(result.attachedPrNumber).toBeNull();
|
|
expect(result.attachments).toEqual([issue]);
|
|
});
|
|
|
|
it("does not duplicate a PR that was already manually attached by the user", () => {
|
|
const pr = makePrItem(202, "Refactor picker");
|
|
const result = syncPickerPrAttachment({
|
|
attachments: [prAttachment(pr)],
|
|
previousPickerPrNumber: null,
|
|
item: { kind: "github-pr", item: pr },
|
|
});
|
|
expect(result.attachedPrNumber).toBeNull();
|
|
expect(result.attachments).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("findCheckoutHintPrAttachment", () => {
|
|
it("returns the first attached PR that is not selected or dismissed", () => {
|
|
const first = prAttachment(makePrItem(101, "A"));
|
|
const second = prAttachment(makePrItem(202, "B"));
|
|
|
|
expect(
|
|
findCheckoutHintPrAttachment({
|
|
attachments: [issueAttachment(44), first, second],
|
|
selectedItem: null,
|
|
dismissedPrNumbers: new Set(),
|
|
}),
|
|
).toBe(first);
|
|
});
|
|
|
|
it("skips the selected PR and offers the next attached PR", () => {
|
|
const selected = prAttachment(makePrItem(101, "A"));
|
|
const next = prAttachment(makePrItem(202, "B"));
|
|
|
|
expect(
|
|
findCheckoutHintPrAttachment({
|
|
attachments: [selected, next],
|
|
selectedItem: { kind: "github-pr", item: selected.item },
|
|
dismissedPrNumbers: new Set(),
|
|
}),
|
|
).toBe(next);
|
|
});
|
|
|
|
it("skips dismissed PRs and ignores issues", () => {
|
|
const dismissed = prAttachment(makePrItem(101, "A"));
|
|
const next = prAttachment(makePrItem(202, "B"));
|
|
|
|
expect(
|
|
findCheckoutHintPrAttachment({
|
|
attachments: [issueAttachment(44), dismissed, next],
|
|
selectedItem: null,
|
|
dismissedPrNumbers: new Set([101]),
|
|
}),
|
|
).toBe(next);
|
|
});
|
|
|
|
it("returns null when only issues qualify", () => {
|
|
expect(
|
|
findCheckoutHintPrAttachment({
|
|
attachments: [issueAttachment(44)],
|
|
selectedItem: null,
|
|
dismissedPrNumbers: new Set(),
|
|
}),
|
|
).toBeNull();
|
|
});
|
|
});
|