18e574a765
A pane's tab shows the other people who have it open (pane-presence.js, in memory, fed by the /ws-pty attach; GET /api/lanes/:id/presence and the pane_presence broadcast to the owner and full collaborators) and says "Billed to <name>" for a pane a collaborator opened (startedBy on the run). The first real keystroke of each attach into a shared workspace's pane by anyone but its owner is audited as `type into <run id>` (mouse, focus and terminal replies to tmux do not count). The pty socket now pings, so a vanished browser is ended at the next re-check and leaves the presence list.
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
/**
|
|
* @file ConsoleTabs.presence.test.tsx
|
|
* @description Workspace sharing phase 4 on the tab bar: a tab shows who ELSE
|
|
* has its panes open (never the viewer), and its tooltip says whose money a
|
|
* pane a collaborator opened is.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { MemoryRouter } from "react-router-dom";
|
|
import "../../../i18n";
|
|
import type { RunHandle } from "../../../lib/api";
|
|
import type { Lane } from "../../../lib/types";
|
|
import { pane, type PaneGroups } from "../../../lib/paneLayout";
|
|
import { ConsoleTabs } from "../ConsoleTabs";
|
|
|
|
vi.mock("../../../lib/api", () => ({
|
|
api: {
|
|
aiTools: {
|
|
list: vi.fn().mockResolvedValue({ default: "claude", tools: [] }),
|
|
models: vi.fn().mockResolvedValue({ flag: true, models: [], defaultModel: null }),
|
|
},
|
|
},
|
|
}));
|
|
vi.mock("../../AuthGate", () => ({
|
|
useAuth: () => ({ state: { mode: "server", user: { id: 1, username: "alice" } } }),
|
|
usePermissions: () => ({ local: false, isAdmin: false }),
|
|
}));
|
|
vi.mock("../../../hooks/usePanePresence", () => ({
|
|
usePanePresence: () => ({
|
|
"agenthub-lane-1": [
|
|
{ id: 1, name: "Alice" },
|
|
{ id: 2, name: "Bob" },
|
|
],
|
|
}),
|
|
}));
|
|
|
|
const LANE = { id: 1, tool: "claude", cwd: "/w", title: "w" } as unknown as Lane;
|
|
const RUN: RunHandle = {
|
|
id: "agenthub-lane-1",
|
|
laneId: 1,
|
|
slot: 0,
|
|
status: "running",
|
|
cwd: "/w",
|
|
model: null,
|
|
permissionMode: null,
|
|
effort: null,
|
|
resumeSessionId: null,
|
|
sessionId: null,
|
|
startedAt: null,
|
|
promptPreview: null,
|
|
startedBy: { id: 2, name: "Bob" },
|
|
tool: "claude",
|
|
};
|
|
const GROUPS: PaneGroups = { groups: [{ root: pane(0), focus: 0 }], active: 0 };
|
|
|
|
describe("ConsoleTabs presence", () => {
|
|
it("shows who else is in a pane, and whose money it is", () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<ConsoleTabs
|
|
lane={LANE}
|
|
tabs={[RUN]}
|
|
groups={GROUPS}
|
|
onGroupsChange={vi.fn()}
|
|
onOpen={vi.fn()}
|
|
onClosed={vi.fn()}
|
|
/>
|
|
</MemoryRouter>
|
|
);
|
|
const people = screen.getAllByTestId(/^console-tab-people-/)[0]!;
|
|
expect(people.textContent).toBe("B");
|
|
expect(people.getAttribute("aria-label")).toBe("Also here: Bob");
|
|
const tab = screen.getAllByRole("tab")[0]!;
|
|
expect(tab.getAttribute("title")).toContain("Billed to Bob");
|
|
expect(tab.getAttribute("title")).not.toContain("Alice");
|
|
});
|
|
});
|