mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
Refine agent sharing dialog (#3699)
## Summary - Refine the agent share dialog around recipient sharing, link copying, catalog sharing, and export. - Show memory settings only when a linked agent has memories to include. - Use a catalog toggle for custom agents and keep built-in agents out of the catalog flow. ## Validation - `pnpm typecheck` - Focused Playwright share and catalog flows --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -37,10 +37,13 @@ import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/shared/ui/dialog";
|
||||
import { Separator } from "@/shared/ui/separator";
|
||||
import { Spinner } from "@/shared/ui/spinner";
|
||||
import { Switch } from "@/shared/ui/switch";
|
||||
|
||||
import {
|
||||
formatShareRecipientName,
|
||||
@@ -63,7 +66,7 @@ type PersonaShareDialogProps = {
|
||||
};
|
||||
|
||||
type SnapshotShareDialogProps = {
|
||||
afterLink?: React.ReactNode;
|
||||
beforeExport?: React.ReactNode;
|
||||
displayName: string;
|
||||
encodeSnapshot: (
|
||||
memoryLevel: SnapshotMemoryLevel,
|
||||
@@ -198,7 +201,6 @@ function MemoryShareConfirmation({
|
||||
function ShareLevelControl({
|
||||
ariaLabel,
|
||||
disabled,
|
||||
hasMemoryOptions,
|
||||
testId,
|
||||
value,
|
||||
options,
|
||||
@@ -206,27 +208,11 @@ function ShareLevelControl({
|
||||
}: {
|
||||
ariaLabel: string;
|
||||
disabled: boolean;
|
||||
hasMemoryOptions: boolean;
|
||||
testId: string;
|
||||
value: SnapshotMemoryLevel;
|
||||
options: { value: SnapshotMemoryLevel; label: string }[];
|
||||
onChange: (level: SnapshotMemoryLevel) => void;
|
||||
}) {
|
||||
if (!hasMemoryOptions) {
|
||||
// Nothing to choose from, so there is no dropdown to open. State the
|
||||
// outcome rather than naming the sole option: the memory-level labels
|
||||
// ("Agent only", "+ core memory", …) are comparative and only make sense
|
||||
// when the alternatives are actually offered.
|
||||
return (
|
||||
<span
|
||||
className="inline-flex h-8 w-auto shrink-0 items-center justify-end px-2 text-sm text-muted-foreground"
|
||||
data-testid={testId}
|
||||
>
|
||||
No memories included
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SnapshotOptionMenu
|
||||
ariaLabel={ariaLabel}
|
||||
@@ -240,7 +226,7 @@ function ShareLevelControl({
|
||||
}
|
||||
|
||||
export function SnapshotShareDialog({
|
||||
afterLink,
|
||||
beforeExport,
|
||||
displayName,
|
||||
encodeSnapshot,
|
||||
hasMemoryOptions,
|
||||
@@ -452,7 +438,6 @@ export function SnapshotShareDialog({
|
||||
return (
|
||||
<Dialog onOpenChange={handleDialogOpenChange} open={open}>
|
||||
<DialogContent
|
||||
aria-describedby={undefined}
|
||||
className="max-w-xl gap-3 bg-transparent p-0 shadow-none"
|
||||
data-testid={`${testIdPrefix}-dialog`}
|
||||
showCloseButton={false}
|
||||
@@ -461,10 +446,16 @@ export function SnapshotShareDialog({
|
||||
className="relative rounded-2xl bg-background p-6 pb-4 shadow-2xl"
|
||||
data-testid={`${testIdPrefix}-main-card`}
|
||||
>
|
||||
<DialogHeader className="space-y-0">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="min-w-0 truncate pr-10">
|
||||
Share {displayName}
|
||||
</DialogTitle>
|
||||
<DialogDescription
|
||||
data-testid={`${testIdPrefix}-share-description`}
|
||||
>
|
||||
Anyone you share this {itemLabel} with will receive a copy they
|
||||
can add and use. Changes you make later won’t sync.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogClose
|
||||
className="absolute right-4 top-4 flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors duration-150 ease-out hover:bg-accent hover:text-accent-foreground focus:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-100"
|
||||
@@ -521,34 +512,44 @@ export function SnapshotShareDialog({
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<p
|
||||
className="text-xs text-secondary-foreground/75"
|
||||
data-testid={`${testIdPrefix}-send-description`}
|
||||
>
|
||||
They’ll receive a copy they can add and use. Changes you make
|
||||
later won’t sync.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{hasMemoryOptions ? (
|
||||
<section
|
||||
className="space-y-2"
|
||||
data-testid={`${testIdPrefix}-link-settings`}
|
||||
>
|
||||
<h3 className="text-xs font-medium text-secondary-foreground/75">
|
||||
Share settings
|
||||
</h3>
|
||||
<div
|
||||
className="flex items-center gap-3"
|
||||
data-testid={`${testIdPrefix}-share-level-row`}
|
||||
>
|
||||
<h4 className="min-w-0 flex-1 text-sm font-medium">
|
||||
What’s included
|
||||
</h4>
|
||||
<ShareLevelControl
|
||||
ariaLabel="What to include"
|
||||
disabled={isInterfacePending}
|
||||
onChange={setShareLevel}
|
||||
options={shareLevels}
|
||||
testId={`${testIdPrefix}-share-level`}
|
||||
value={shareLevel}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<Separator
|
||||
className="bg-input/40"
|
||||
data-testid={`${testIdPrefix}-link-divider`}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="flex items-center gap-3 pt-2"
|
||||
className="flex justify-end"
|
||||
data-testid={`${testIdPrefix}-link-row`}
|
||||
>
|
||||
<span
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground"
|
||||
data-testid={`${testIdPrefix}-link-icon`}
|
||||
>
|
||||
<Link2 className="h-4 w-4" />
|
||||
</span>
|
||||
<div
|
||||
className="min-w-0 flex-1"
|
||||
data-testid={`${testIdPrefix}-link-copy`}
|
||||
>
|
||||
<h3 className="text-sm font-medium">Share with a link</h3>
|
||||
<p className="text-xs text-secondary-foreground/75">
|
||||
Anyone with the link can add and use a copy.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
className="shrink-0 border-border shadow-none disabled:opacity-100"
|
||||
@@ -622,24 +623,6 @@ export function SnapshotShareDialog({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex items-center gap-3"
|
||||
data-testid={`${testIdPrefix}-share-level-row`}
|
||||
>
|
||||
<h3 className="min-w-0 flex-1 text-sm font-medium">
|
||||
What’s included
|
||||
</h3>
|
||||
<ShareLevelControl
|
||||
ariaLabel="What to include"
|
||||
disabled={isInterfacePending}
|
||||
hasMemoryOptions={hasMemoryOptions}
|
||||
onChange={setShareLevel}
|
||||
options={shareLevels}
|
||||
testId={`${testIdPrefix}-share-level`}
|
||||
value={shareLevel}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AnimatePresence initial={false}>
|
||||
{showMemoryWarning ? (
|
||||
<motion.div
|
||||
@@ -664,10 +647,9 @@ export function SnapshotShareDialog({
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
|
||||
{afterLink}
|
||||
</div>
|
||||
</div>
|
||||
{beforeExport}
|
||||
<button
|
||||
className="relative flex min-h-14 w-full items-center gap-3 rounded-2xl bg-background px-5 py-4 text-left text-sm font-medium shadow-2xl outline-hidden transition-colors hover:bg-muted focus-visible:bg-muted disabled:cursor-default disabled:opacity-100"
|
||||
data-testid={`${testIdPrefix}-export`}
|
||||
@@ -703,13 +685,6 @@ export function PersonaShareDialog({
|
||||
persona,
|
||||
}: PersonaShareDialogProps) {
|
||||
const encodeSnapshotMutation = useEncodeAgentSnapshotForSendMutation();
|
||||
const catalogShareLevels = React.useMemo(
|
||||
() => [
|
||||
{ value: "not-shared", label: "Not shared" },
|
||||
{ value: "none", label: "Shared" },
|
||||
],
|
||||
[],
|
||||
);
|
||||
const encodeSnapshot = React.useCallback(
|
||||
async (memoryLevel: SnapshotMemoryLevel) =>
|
||||
encodeSnapshotMutation.mutateAsync({
|
||||
@@ -729,15 +704,13 @@ export function PersonaShareDialog({
|
||||
|
||||
return (
|
||||
<SnapshotShareDialog
|
||||
afterLink={
|
||||
beforeExport={
|
||||
persona.isBuiltIn ? null : (
|
||||
<section
|
||||
className="flex min-h-16 w-full items-center gap-3"
|
||||
className="relative flex min-h-16 w-full items-center gap-3 rounded-2xl bg-background px-5 py-4 shadow-2xl"
|
||||
data-testid="persona-share-catalog"
|
||||
>
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
||||
<BookUser className="h-4 w-4" />
|
||||
</span>
|
||||
<BookUser className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-sm font-medium">Share to catalog</h3>
|
||||
<p className="text-xs text-secondary-foreground/75">
|
||||
@@ -746,20 +719,16 @@ export function PersonaShareDialog({
|
||||
included.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center">
|
||||
<SnapshotOptionMenu
|
||||
ariaLabel="What to share in the catalog"
|
||||
disabled={isPending}
|
||||
onValueChange={(nextValue) =>
|
||||
onCatalogShareLevelChange(
|
||||
nextValue as CatalogPersonaShareLevel,
|
||||
)
|
||||
}
|
||||
options={catalogShareLevels}
|
||||
testId="persona-share-catalog-access"
|
||||
value={catalogShareLevel}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
aria-label="Share to catalog"
|
||||
checked={catalogShareLevel !== "not-shared"}
|
||||
data-testid="persona-share-catalog-access"
|
||||
disabled={isPending}
|
||||
onCheckedChange={(checked) =>
|
||||
onCatalogShareLevelChange(checked ? "none" : "not-shared")
|
||||
}
|
||||
style={{ cursor: "default" }}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -93,10 +93,10 @@ async function sharePersonaToCatalog(
|
||||
) {
|
||||
await page.getByLabel(`Open actions for ${displayName}`).click();
|
||||
await page.getByRole("menuitem", { name: "Share" }).click();
|
||||
await page.getByTestId("persona-share-catalog-access").click();
|
||||
await page
|
||||
.getByRole("menuitemradio", { name: "Shared", exact: true })
|
||||
.click();
|
||||
const catalogToggle = page.getByTestId("persona-share-catalog-access");
|
||||
await expect(catalogToggle).not.toBeChecked();
|
||||
await catalogToggle.click();
|
||||
await expect(catalogToggle).toBeChecked();
|
||||
await page
|
||||
.getByTestId("persona-share-dialog")
|
||||
.getByRole("button", { name: "Close" })
|
||||
@@ -979,21 +979,27 @@ test("custom personas share with people and keep export separate", async ({
|
||||
page.getByRole("heading", { name: "Share Animation Auditor" }),
|
||||
).toBeVisible();
|
||||
await expect(shareDialog.getByText("Added by You")).toHaveCount(0);
|
||||
const sendDescription = shareDialog.getByTestId(
|
||||
"persona-share-send-description",
|
||||
const shareDescription = shareDialog.getByTestId(
|
||||
"persona-share-share-description",
|
||||
);
|
||||
await expect(sendDescription).toHaveText(
|
||||
"They’ll receive a copy they can add and use. Changes you make later won’t sync.",
|
||||
await expect(shareDescription).toHaveText(
|
||||
"Anyone you share this agent with will receive a copy they can add and use. Changes you make later won’t sync.",
|
||||
);
|
||||
await expect(sendDescription).toHaveClass(
|
||||
/text-xs.*text-secondary-foreground\/75/,
|
||||
await expect(shareDescription).toHaveClass(/text-sm.*text-muted-foreground/);
|
||||
const shareDescriptionId = await shareDescription.getAttribute("id");
|
||||
expect(shareDescriptionId).toBeTruthy();
|
||||
await expect(shareDialog).toHaveAttribute(
|
||||
"aria-describedby",
|
||||
shareDescriptionId ?? "",
|
||||
);
|
||||
const sendDescriptionMetrics = await sendDescription.evaluate((element) => ({
|
||||
height: element.getBoundingClientRect().height,
|
||||
lineHeight: Number.parseFloat(getComputedStyle(element).lineHeight),
|
||||
}));
|
||||
expect(sendDescriptionMetrics.height).toBeLessThanOrEqual(
|
||||
sendDescriptionMetrics.lineHeight + 1,
|
||||
const shareDescriptionMetrics = await shareDescription.evaluate(
|
||||
(element) => ({
|
||||
height: element.getBoundingClientRect().height,
|
||||
lineHeight: Number.parseFloat(getComputedStyle(element).lineHeight),
|
||||
}),
|
||||
);
|
||||
expect(shareDescriptionMetrics.height).toBeLessThanOrEqual(
|
||||
shareDescriptionMetrics.lineHeight * 2 + 1,
|
||||
);
|
||||
await expect(
|
||||
shareDialog.getByRole("heading", { name: "Who has access" }),
|
||||
@@ -1001,57 +1007,49 @@ test("custom personas share with people and keep export separate", async ({
|
||||
await expect(shareDialog.getByText("Owner", { exact: true })).toHaveCount(0);
|
||||
await expect(shareDialog.getByText("(You)", { exact: true })).toHaveCount(0);
|
||||
const linkRow = page.getByTestId("persona-share-link-row");
|
||||
await expect(page.getByTestId("persona-share-copy-link")).toBeVisible();
|
||||
await expect(
|
||||
linkRow.getByRole("heading", { name: "Share with a link" }),
|
||||
).toBeVisible();
|
||||
shareDialog.getByRole("heading", { name: "Share with a link" }),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
linkRow.getByText("Anyone with the link can add and use a copy."),
|
||||
).toHaveClass(/text-xs.*text-secondary-foreground\/75/);
|
||||
shareDialog.getByText("Anyone with the link can add and use a copy."),
|
||||
).toHaveCount(0);
|
||||
await expect(page.getByTestId("persona-share-send")).toHaveCount(0);
|
||||
const copyLinkButton = page.getByTestId("persona-share-copy-link");
|
||||
const linkIcon = page.getByTestId("persona-share-link-icon");
|
||||
const linkCopy = page.getByTestId("persona-share-link-copy");
|
||||
const catalogSection = page.getByTestId("persona-share-catalog");
|
||||
const staticShareLevel = page.getByTestId("persona-share-share-level");
|
||||
const shareLevelRow = page.getByTestId("persona-share-share-level-row");
|
||||
const shareMainCard = page.getByTestId("persona-share-main-card");
|
||||
const exportAgentRow = page.getByTestId("persona-share-export");
|
||||
await waitForAnimations(page);
|
||||
const [
|
||||
linkRowBox,
|
||||
initialCopyLinkButtonBox,
|
||||
linkIconBox,
|
||||
linkCopyBox,
|
||||
catalogSectionBox,
|
||||
staticShareLevelBox,
|
||||
shareLevelRowBox,
|
||||
shareMainCardBox,
|
||||
exportAgentRowBox,
|
||||
] = await Promise.all([
|
||||
linkRow.boundingBox(),
|
||||
copyLinkButton.boundingBox(),
|
||||
linkIcon.boundingBox(),
|
||||
linkCopy.boundingBox(),
|
||||
catalogSection.boundingBox(),
|
||||
staticShareLevel.boundingBox(),
|
||||
shareLevelRow.boundingBox(),
|
||||
shareMainCard.boundingBox(),
|
||||
exportAgentRow.boundingBox(),
|
||||
]);
|
||||
const sendDescriptionBox = await sendDescription.boundingBox();
|
||||
const shareDescriptionBox = await shareDescription.boundingBox();
|
||||
const recipientFieldBox = await page
|
||||
.getByTestId("persona-share-recipient-field")
|
||||
.boundingBox();
|
||||
// Reading order: who → how it goes out → what's included → catalog.
|
||||
expect(sendDescriptionBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(recipientFieldBox?.y ?? 0) + (recipientFieldBox?.height ?? 0),
|
||||
// Without memories, the recipient field flows directly into the link action.
|
||||
expect(recipientFieldBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(shareDescriptionBox?.y ?? 0) + (shareDescriptionBox?.height ?? 0),
|
||||
);
|
||||
await expect(page.getByTestId("persona-share-link-settings")).toHaveCount(0);
|
||||
await expect(page.getByTestId("persona-share-share-level-row")).toHaveCount(
|
||||
0,
|
||||
);
|
||||
expect(linkRowBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(sendDescriptionBox?.y ?? 0) + (sendDescriptionBox?.height ?? 0),
|
||||
(recipientFieldBox?.y ?? 0) + (recipientFieldBox?.height ?? 0),
|
||||
);
|
||||
expect(shareLevelRowBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(linkRowBox?.y ?? 0) + (linkRowBox?.height ?? 0),
|
||||
);
|
||||
expect(catalogSectionBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(shareLevelRowBox?.y ?? 0) + (shareLevelRowBox?.height ?? 0),
|
||||
);
|
||||
// Copy link is the link row's own action, not a stranded footer button, so
|
||||
// it rides on that row, vertically centred with the link icon and flush to
|
||||
// the row's right edge.
|
||||
// Copy link stays inside the main card, after the shared settings divider, while catalog and
|
||||
// export are separate rows below it.
|
||||
expect(initialCopyLinkButtonBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
linkRowBox?.y ?? 0,
|
||||
);
|
||||
@@ -1059,40 +1057,15 @@ test("custom personas share with people and keep export separate", async ({
|
||||
(initialCopyLinkButtonBox?.y ?? 0) +
|
||||
(initialCopyLinkButtonBox?.height ?? 0),
|
||||
).toBeLessThanOrEqual((linkRowBox?.y ?? 0) + (linkRowBox?.height ?? 0) + 1);
|
||||
expect(
|
||||
Math.abs(
|
||||
(initialCopyLinkButtonBox?.y ?? 0) +
|
||||
(initialCopyLinkButtonBox?.height ?? 0) / 2 -
|
||||
((linkIconBox?.y ?? 0) + (linkIconBox?.height ?? 0) / 2),
|
||||
),
|
||||
).toBeLessThanOrEqual(1);
|
||||
expect(
|
||||
Math.abs(
|
||||
(linkRowBox?.x ?? 0) +
|
||||
(linkRowBox?.width ?? 0) -
|
||||
((initialCopyLinkButtonBox?.x ?? 0) +
|
||||
(initialCopyLinkButtonBox?.width ?? 0)),
|
||||
),
|
||||
).toBeLessThanOrEqual(1);
|
||||
expect(
|
||||
Math.abs(
|
||||
(linkCopyBox?.y ?? 0) +
|
||||
(linkCopyBox?.height ?? 0) / 2 -
|
||||
((linkIconBox?.y ?? 0) + (linkIconBox?.height ?? 0) / 2),
|
||||
),
|
||||
).toBeLessThanOrEqual(1);
|
||||
await expect(page.getByTestId("persona-share-link-divider")).toHaveCount(0);
|
||||
await expect(page.getByTestId("persona-share-copy-link-footer")).toHaveCount(
|
||||
0,
|
||||
await expect(page.getByTestId("persona-share-link-divider")).toHaveClass(
|
||||
/bg-input\/40/,
|
||||
);
|
||||
expect(catalogSectionBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0) + 12,
|
||||
);
|
||||
expect(exportAgentRowBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(catalogSectionBox?.y ?? 0) + (catalogSectionBox?.height ?? 0) + 12,
|
||||
);
|
||||
expect(
|
||||
Math.abs(
|
||||
(shareLevelRowBox?.y ?? 0) +
|
||||
(shareLevelRowBox?.height ?? 0) / 2 -
|
||||
((staticShareLevelBox?.y ?? 0) +
|
||||
(staticShareLevelBox?.height ?? 0) / 2),
|
||||
),
|
||||
).toBeLessThanOrEqual(1);
|
||||
await expect(copyLinkButton).toHaveClass(
|
||||
/border.*bg-background.*border-border/,
|
||||
);
|
||||
@@ -1109,12 +1082,7 @@ test("custom personas share with people and keep export separate", async ({
|
||||
await expect.poll(copyLinkHasVisibleShadow).toBe(false);
|
||||
await copyLinkButton.hover();
|
||||
await expect.poll(copyLinkHasVisibleShadow).toBe(false);
|
||||
await expect(page.getByTestId("persona-share-share-level")).toHaveText(
|
||||
"No memories included",
|
||||
);
|
||||
await expect(
|
||||
shareDialog.getByText("No memories included", { exact: true }),
|
||||
).toHaveCount(1);
|
||||
await expect(page.getByTestId("persona-share-share-level")).toHaveCount(0);
|
||||
await expect(page.getByTestId("persona-share-recipient-access")).toHaveCount(
|
||||
0,
|
||||
);
|
||||
@@ -1131,20 +1099,10 @@ test("custom personas share with people and keep export separate", async ({
|
||||
await expect(
|
||||
shareDialog.getByText("File format", { exact: true }),
|
||||
).toHaveCount(0);
|
||||
const shareMainCard = page.getByTestId("persona-share-main-card");
|
||||
const exportAgentRow = page.getByTestId("persona-share-export");
|
||||
await expect(exportAgentRow).toHaveText("Export agent");
|
||||
await expect(shareMainCard.getByTestId("persona-share-export")).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await waitForAnimations(page);
|
||||
const shareMainCardBox = await shareMainCard.boundingBox();
|
||||
const exportAgentRowBox = await exportAgentRow.boundingBox();
|
||||
const shareCardGap =
|
||||
(exportAgentRowBox?.y ?? 0) -
|
||||
((shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0));
|
||||
expect(shareCardGap).toBeGreaterThanOrEqual(12);
|
||||
expect(shareCardGap).toBeLessThan(16);
|
||||
const [shareMainCardStyles, exportAgentRowStyles] = await Promise.all([
|
||||
shareMainCard.evaluate((element) => ({
|
||||
borderRadius: getComputedStyle(element).borderRadius,
|
||||
@@ -1528,9 +1486,9 @@ This deliberately long fenced-code example must not establish the minimum width
|
||||
const shareMainCard = shareDialog.getByTestId("persona-share-main-card");
|
||||
const copyLinkButton = shareDialog.getByTestId("persona-share-copy-link");
|
||||
const catalogSection = shareDialog.getByTestId("persona-share-catalog");
|
||||
await expect(
|
||||
shareMainCard.getByTestId("persona-share-catalog"),
|
||||
).toBeVisible();
|
||||
await expect(shareMainCard.getByTestId("persona-share-catalog")).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await expect(catalogSection).toContainText("Share to catalog");
|
||||
await expect(catalogSection).toContainText(
|
||||
"Anyone in this community can find and use a copy.",
|
||||
@@ -1544,26 +1502,18 @@ This deliberately long fenced-code example must not establish the minimum width
|
||||
catalogSection.boundingBox(),
|
||||
shareMainCard.boundingBox(),
|
||||
]);
|
||||
// Copy link belongs to the link row above, so the catalog is the section
|
||||
// that closes the card rather than trailing an orphaned button.
|
||||
// Catalog is its own card below the sharing controls.
|
||||
expect(
|
||||
(copyLinkButtonBox?.y ?? 0) + (copyLinkButtonBox?.height ?? 0),
|
||||
).toBeLessThanOrEqual(catalogSectionBox?.y ?? 0);
|
||||
expect(
|
||||
(catalogSectionBox?.y ?? 0) + (catalogSectionBox?.height ?? 0),
|
||||
).toBeLessThanOrEqual(
|
||||
(shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0),
|
||||
);
|
||||
await expect(catalogAccess).toHaveText("Not shared");
|
||||
expect(catalogSectionBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0),
|
||||
);
|
||||
await expect(catalogAccess).not.toBeChecked();
|
||||
await catalogAccess.click();
|
||||
await expect(page.getByRole("menuitemradio")).toHaveText([
|
||||
"Not shared",
|
||||
"Shared",
|
||||
]);
|
||||
await page
|
||||
.getByRole("menuitemradio", { name: "Shared", exact: true })
|
||||
.click();
|
||||
await expect(catalogAccess).toHaveText("Shared");
|
||||
await expect(catalogAccess).toBeChecked();
|
||||
const storedPersonas = await invokeTauri<
|
||||
Array<{ id: string; shared: boolean }>
|
||||
>(page, "list_personas");
|
||||
@@ -1651,11 +1601,9 @@ This deliberately long fenced-code example must not establish the minimum width
|
||||
|
||||
await page.getByLabel("Open actions for Catalog Analyst").click();
|
||||
await page.getByRole("menuitem", { name: "Share" }).click();
|
||||
await expect(catalogAccess).toHaveText("Shared");
|
||||
await expect(catalogAccess).toBeChecked();
|
||||
await catalogAccess.click();
|
||||
await page
|
||||
.getByRole("menuitemradio", { name: "Not shared", exact: true })
|
||||
.click();
|
||||
await expect(catalogAccess).not.toBeChecked();
|
||||
await page
|
||||
.getByTestId("persona-share-dialog")
|
||||
.getByRole("button", { name: "Close" })
|
||||
@@ -1687,9 +1635,6 @@ test("a queued catalog share is not presented as relay-published", async ({
|
||||
await page.getByLabel("Open actions for Queued Catalog Agent").click();
|
||||
await page.getByRole("menuitem", { name: "Share" }).click();
|
||||
await page.getByTestId("persona-share-catalog-access").click();
|
||||
await page
|
||||
.getByRole("menuitemradio", { name: "Shared", exact: true })
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByText(
|
||||
@@ -1918,7 +1863,7 @@ test("one share level selector drives both the link and send paths", async ({
|
||||
const shareLevel = shareDialog.getByLabel("What to include", {
|
||||
exact: true,
|
||||
});
|
||||
const catalogAccess = shareDialog.getByLabel("What to share in the catalog");
|
||||
const catalogAccess = shareDialog.getByLabel("Share to catalog");
|
||||
const recipientField = page.getByTestId("persona-share-recipient-field");
|
||||
const emptyRecipientFieldBox = await recipientField.boundingBox();
|
||||
await expect(shareDialog.getByTestId("persona-share-send")).toHaveCount(0);
|
||||
@@ -1930,13 +1875,14 @@ test("one share level selector drives both the link and send paths", async ({
|
||||
await expect(shareLevel).toHaveCSS("text-decoration-line", "none");
|
||||
await expect(shareLevel).toHaveCSS("padding-left", "8px");
|
||||
await expect(shareLevel).toHaveCSS("padding-right", "8px");
|
||||
await expect(catalogAccess).toHaveText("Not shared");
|
||||
await catalogAccess.click();
|
||||
await expect(page.getByRole("menuitemradio")).toHaveText([
|
||||
"Not shared",
|
||||
"Shared",
|
||||
]);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(catalogAccess).not.toBeChecked();
|
||||
await expect(catalogAccess).toHaveCSS("cursor", "default");
|
||||
await expect(
|
||||
shareDialog.getByTestId("persona-share-link-settings"),
|
||||
).toContainText("Share settings");
|
||||
await expect(
|
||||
shareDialog.getByText("Share settings", { exact: true }),
|
||||
).toHaveClass(/text-xs.*text-secondary-foreground\/75/);
|
||||
const copyLinkButton = shareDialog.getByTestId("persona-share-copy-link");
|
||||
const recipientFieldBox = await recipientField.boundingBox();
|
||||
const [shareLevelBox, copyLinkButtonBox, catalogAccessBox] =
|
||||
@@ -1945,15 +1891,15 @@ test("one share level selector drives both the link and send paths", async ({
|
||||
copyLinkButton.boundingBox(),
|
||||
catalogAccess.boundingBox(),
|
||||
]);
|
||||
// Reading order: who → how it goes out → what's included → catalog.
|
||||
expect(copyLinkButtonBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
// Reading order: who → what's included → copy link → catalog.
|
||||
expect(shareLevelBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(recipientFieldBox?.y ?? 0) + (recipientFieldBox?.height ?? 0),
|
||||
);
|
||||
expect(shareLevelBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(copyLinkButtonBox?.y ?? 0) + (copyLinkButtonBox?.height ?? 0),
|
||||
expect(copyLinkButtonBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(shareLevelBox?.y ?? 0) + (shareLevelBox?.height ?? 0),
|
||||
);
|
||||
expect(catalogAccessBox?.y ?? 0).toBeGreaterThanOrEqual(
|
||||
(shareLevelBox?.y ?? 0) + (shareLevelBox?.height ?? 0),
|
||||
(copyLinkButtonBox?.y ?? 0) + (copyLinkButtonBox?.height ?? 0),
|
||||
);
|
||||
// The memory choice is stated once, governing both delivery actions —
|
||||
// neither the recipients row nor the link row carries its own copy.
|
||||
|
||||
Reference in New Issue
Block a user