Fix settings host picker navigation
This commit is contained in:
@@ -1,8 +1,18 @@
|
|||||||
import { expect, type Page } from "@playwright/test";
|
import { expect, type Page } from "@playwright/test";
|
||||||
import { TEST_HOST_LABEL } from "./daemon-registry";
|
import { buildCreateAgentPreferences, buildSeededHost, TEST_HOST_LABEL } from "./daemon-registry";
|
||||||
import { escapeRegex } from "./regex";
|
import { escapeRegex } from "./regex";
|
||||||
import { getServerId } from "./server-id";
|
import { getServerId } from "./server-id";
|
||||||
|
|
||||||
|
const DISABLE_DEFAULT_SEED_ONCE_KEY = "@paseo:e2e-disable-default-seed-once";
|
||||||
|
const SEED_NONCE_KEY = "@paseo:e2e-seed-nonce";
|
||||||
|
const REGISTRY_KEY = "@paseo:daemon-registry";
|
||||||
|
|
||||||
|
interface SavedSettingsHostInput {
|
||||||
|
serverId: string;
|
||||||
|
label: string;
|
||||||
|
endpoint: string;
|
||||||
|
}
|
||||||
|
|
||||||
const SECTION_LABELS = {
|
const SECTION_LABELS = {
|
||||||
general: "General",
|
general: "General",
|
||||||
appearance: "Appearance",
|
appearance: "Appearance",
|
||||||
@@ -84,6 +94,60 @@ export async function openCompactSettings(page: Page): Promise<void> {
|
|||||||
await expect(page.getByTestId("settings-sidebar")).toBeVisible();
|
await expect(page.getByTestId("settings-sidebar")).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function seedSavedSettingsHosts(
|
||||||
|
page: Page,
|
||||||
|
hosts: SavedSettingsHostInput[],
|
||||||
|
): Promise<void> {
|
||||||
|
await page.goto("/");
|
||||||
|
const nowIso = new Date().toISOString();
|
||||||
|
const registry = hosts.map((host) =>
|
||||||
|
buildSeededHost({
|
||||||
|
serverId: host.serverId,
|
||||||
|
label: host.label,
|
||||||
|
endpoint: host.endpoint,
|
||||||
|
nowIso,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const firstHost = registry[0];
|
||||||
|
if (!firstHost) {
|
||||||
|
throw new Error("Expected at least one settings host fixture.");
|
||||||
|
}
|
||||||
|
const preferences = buildCreateAgentPreferences(firstHost.serverId);
|
||||||
|
|
||||||
|
await page.evaluate(
|
||||||
|
({ keys, storedRegistry, storedPreferences }) => {
|
||||||
|
const nonce = localStorage.getItem(keys.seedNonce);
|
||||||
|
if (!nonce) {
|
||||||
|
throw new Error("Expected e2e seed nonce before overriding settings host registry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(keys.registry, JSON.stringify(storedRegistry));
|
||||||
|
localStorage.setItem("@paseo:create-agent-preferences", JSON.stringify(storedPreferences));
|
||||||
|
localStorage.setItem(keys.disableDefaultSeedOnce, nonce);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keys: {
|
||||||
|
disableDefaultSeedOnce: DISABLE_DEFAULT_SEED_ONCE_KEY,
|
||||||
|
registry: REGISTRY_KEY,
|
||||||
|
seedNonce: SEED_NONCE_KEY,
|
||||||
|
},
|
||||||
|
storedRegistry: registry,
|
||||||
|
storedPreferences: preferences,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function selectSettingsHost(page: Page, serverId: string): Promise<void> {
|
||||||
|
await page.getByTestId("settings-host-picker").click();
|
||||||
|
await page.getByTestId(`settings-host-picker-item-${serverId}`).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function expectSettingsHostPickerLabel(page: Page, label: string): Promise<void> {
|
||||||
|
await expect(
|
||||||
|
page.getByTestId("settings-host-picker").getByText(label, { exact: true }),
|
||||||
|
).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
export async function expectCompactSettingsList(page: Page): Promise<void> {
|
export async function expectCompactSettingsList(page: Page): Promise<void> {
|
||||||
await expect(page).toHaveURL(/\/settings$/);
|
await expect(page).toHaveURL(/\/settings$/);
|
||||||
await expect(page.getByTestId("settings-sidebar")).toBeVisible();
|
await expect(page.getByTestId("settings-sidebar")).toBeVisible();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { test, expect } from "./fixtures";
|
import { test, expect } from "./fixtures";
|
||||||
import { gotoAppShell, openSettings } from "./helpers/app";
|
import { gotoAppShell, openSettings } from "./helpers/app";
|
||||||
|
import { getE2EDaemonPort } from "./helpers/daemon-port";
|
||||||
import {
|
import {
|
||||||
openSettingsSection,
|
openSettingsSection,
|
||||||
expectSettingsHeader,
|
expectSettingsHeader,
|
||||||
@@ -26,7 +27,12 @@ import {
|
|||||||
expectAboutContent,
|
expectAboutContent,
|
||||||
expectGeneralContent,
|
expectGeneralContent,
|
||||||
expectAppearanceContent,
|
expectAppearanceContent,
|
||||||
|
seedSavedSettingsHosts,
|
||||||
|
selectSettingsHost,
|
||||||
|
expectSettingsHostPickerLabel,
|
||||||
|
openSettingsHostSection,
|
||||||
} from "./helpers/settings";
|
} from "./helpers/settings";
|
||||||
|
import { getServerId } from "./helpers/server-id";
|
||||||
|
|
||||||
test.describe("Settings sidebar navigation", () => {
|
test.describe("Settings sidebar navigation", () => {
|
||||||
test("clicking a sidebar section updates the URL and renders the section", async ({ page }) => {
|
test("clicking a sidebar section updates the URL and renders the section", async ({ page }) => {
|
||||||
@@ -157,4 +163,28 @@ test.describe("Settings — compact master-detail", () => {
|
|||||||
await expect(page).toHaveURL(/\/settings$/);
|
await expect(page).toHaveURL(/\/settings$/);
|
||||||
await expectSettingsSidebarVisible(page);
|
await expectSettingsSidebarVisible(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("switching the host picker on the settings list scopes host rows without navigating", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const primaryServerId = getServerId();
|
||||||
|
const secondaryServerId = "srv_e2e_settings_secondary";
|
||||||
|
const secondaryHostLabel = "Stable horse";
|
||||||
|
const endpoint = `127.0.0.1:${getE2EDaemonPort()}`;
|
||||||
|
|
||||||
|
await seedSavedSettingsHosts(page, [
|
||||||
|
{ serverId: primaryServerId, label: "First horse", endpoint },
|
||||||
|
{ serverId: secondaryServerId, label: secondaryHostLabel, endpoint },
|
||||||
|
]);
|
||||||
|
await gotoAppShell(page);
|
||||||
|
await openCompactSettings(page);
|
||||||
|
|
||||||
|
await selectSettingsHost(page, secondaryServerId);
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/settings$/);
|
||||||
|
await expectSettingsSidebarVisible(page);
|
||||||
|
await expectSettingsHostPickerLabel(page, secondaryHostLabel);
|
||||||
|
|
||||||
|
await openSettingsHostSection(page, secondaryServerId, "connections");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1097,13 +1097,30 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
|||||||
const sortedHosts = useSortedHosts(hosts, localServerId);
|
const sortedHosts = useSortedHosts(hosts, localServerId);
|
||||||
const hostServerIds = useMemo(() => hosts.map((host) => host.serverId), [hosts]);
|
const hostServerIds = useMemo(() => hosts.map((host) => host.serverId), [hosts]);
|
||||||
const anyOnlineServerId = useAnyOnlineHostServerId(hostServerIds);
|
const anyOnlineServerId = useAnyOnlineHostServerId(hostServerIds);
|
||||||
|
const [selectedSettingsHostServerId, setSelectedSettingsHostServerId] = useState<string | null>(
|
||||||
|
view.kind === "host" ? view.serverId : null,
|
||||||
|
);
|
||||||
|
const knownSelectedSettingsHostServerId = useMemo(() => {
|
||||||
|
if (!selectedSettingsHostServerId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return hosts.some((host) => host.serverId === selectedSettingsHostServerId)
|
||||||
|
? selectedSettingsHostServerId
|
||||||
|
: null;
|
||||||
|
}, [hosts, selectedSettingsHostServerId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (view.kind === "host") {
|
||||||
|
setSelectedSettingsHostServerId(view.serverId);
|
||||||
|
}
|
||||||
|
}, [view]);
|
||||||
|
|
||||||
// The host the four sections scope to: the host on the active view, otherwise
|
// The host the four sections scope to: the host on the active view, otherwise
|
||||||
// the local daemon, otherwise the first available host.
|
// the picker choice, otherwise the local daemon, otherwise the first host.
|
||||||
const activeHostServerId = useMemo(() => {
|
const activeHostServerId = useMemo(() => {
|
||||||
if (view.kind === "host") return view.serverId;
|
if (view.kind === "host") return view.serverId;
|
||||||
return localServerId ?? sortedHosts[0]?.serverId ?? null;
|
return knownSelectedSettingsHostServerId ?? localServerId ?? sortedHosts[0]?.serverId ?? null;
|
||||||
}, [view, localServerId, sortedHosts]);
|
}, [view, knownSelectedSettingsHostServerId, localServerId, sortedHosts]);
|
||||||
|
|
||||||
const handleSendBehaviorChange = useCallback(
|
const handleSendBehaviorChange = useCallback(
|
||||||
(behavior: SendBehavior) => {
|
(behavior: SendBehavior) => {
|
||||||
@@ -1205,10 +1222,15 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
|||||||
[isCompactLayout, router],
|
[isCompactLayout, router],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Picker: swap the host but keep the section the user is already looking at.
|
// Picker: choose the host for host-section rows. If the user is already on a
|
||||||
|
// host detail route, keep that detail section and swap only the host segment.
|
||||||
const handleSelectHost = useCallback(
|
const handleSelectHost = useCallback(
|
||||||
(serverId: string) => {
|
(serverId: string) => {
|
||||||
const section: HostSectionSlug = view.kind === "host" ? view.section : "connections";
|
setSelectedSettingsHostServerId(serverId);
|
||||||
|
if (view.kind !== "host") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const section: HostSectionSlug = view.section;
|
||||||
const target = buildSettingsHostSectionRoute(serverId, section);
|
const target = buildSettingsHostSectionRoute(serverId, section);
|
||||||
if (isCompactLayout) {
|
if (isCompactLayout) {
|
||||||
router.push(target);
|
router.push(target);
|
||||||
|
|||||||
Reference in New Issue
Block a user