Przeglądaj źródła

chore: fix flaky test

adamelmore 1 miesiąc temu
rodzic
commit
b4d0090e00

+ 11 - 11
packages/app/e2e/projects/projects-switch.spec.ts

@@ -9,7 +9,7 @@ import {
   sessionIDFromUrl,
 } from "../actions"
 import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
-import { createSdk, dirSlug } from "../utils"
+import { createSdk, dirSlug, sessionPath } from "../utils"
 
 function slugFromUrl(url: string) {
   return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
@@ -51,7 +51,6 @@ test("switching back to a project opens the latest workspace session", async ({
 
   const other = await createTestProject()
   const otherSlug = dirSlug(other)
-  const stamp = Date.now()
   let rootDir: string | undefined
   let workspaceDir: string | undefined
   let sessionID: string | undefined
@@ -80,6 +79,7 @@ test("switching back to a project opens the latest workspace session", async ({
 
         const workspaceSlug = slugFromUrl(page.url())
         workspaceDir = base64Decode(workspaceSlug)
+        if (!workspaceDir) throw new Error(`Failed to decode workspace slug: ${workspaceSlug}`)
         await openSidebar(page)
 
         const workspace = page.locator(workspaceItemSelector(workspaceSlug)).first()
@@ -92,15 +92,14 @@ test("switching back to a project opens the latest workspace session", async ({
 
         await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session(?:[/?#]|$)`))
 
-        const prompt = page.locator(promptSelector)
-        await expect(prompt).toBeVisible()
-        await prompt.fill(`project switch remembers workspace ${stamp}`)
-        await prompt.press("Enter")
-
-        await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 30_000 }).not.toBe("")
-        const created = sessionIDFromUrl(page.url())
-        if (!created) throw new Error(`Failed to parse session id from URL: ${page.url()}`)
+        const created = await createSdk(workspaceDir)
+          .session.create()
+          .then((x) => x.data?.id)
+        if (!created) throw new Error(`Failed to create session for workspace: ${workspaceDir}`)
         sessionID = created
+
+        await page.goto(sessionPath(workspaceDir, created))
+        await expect(page.locator(promptSelector)).toBeVisible()
         await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`))
 
         await openSidebar(page)
@@ -114,7 +113,8 @@ test("switching back to a project opens the latest workspace session", async ({
         await expect(rootButton).toBeVisible()
         await rootButton.click()
 
-        await expect(page).toHaveURL(new RegExp(`/${workspaceSlug}/session/${created}(?:[/?#]|$)`))
+        await expect.poll(() => sessionIDFromUrl(page.url()) ?? "").toBe(created)
+        await expect(page).toHaveURL(new RegExp(`/session/${created}(?:[/?#]|$)`))
       },
       { extra: [other] },
     )

+ 40 - 2
packages/app/src/pages/layout.tsx

@@ -61,6 +61,7 @@ import {
   displayName,
   errorMessage,
   getDraggableId,
+  latestRootSession,
   sortedRootSessions,
   syncWorkspaceOrder,
   workspaceKey,
@@ -1093,14 +1094,51 @@ export default function Layout(props: ParentProps) {
     return meta?.worktree ?? directory
   }
 
-  function navigateToProject(directory: string | undefined) {
+  async function navigateToProject(directory: string | undefined) {
     if (!directory) return
     const root = projectRoot(directory)
     server.projects.touch(root)
+    const project = layout.projects.list().find((item) => item.worktree === root)
+    const dirs = Array.from(new Set([root, ...(store.workspaceOrder[root] ?? []), ...(project?.sandboxes ?? [])]))
+    const openSession = async (target: { directory: string; id: string }) => {
+      const resolved = await globalSDK.client.session
+        .get({ sessionID: target.id })
+        .then((x) => x.data)
+        .catch(() => undefined)
+      const next = resolved?.directory ? resolved : target
+      setStore("lastProjectSession", root, { directory: next.directory, id: next.id, at: Date.now() })
+      navigateWithSidebarReset(`/${base64Encode(next.directory)}/session/${next.id}`)
+    }
 
     const projectSession = store.lastProjectSession[root]
     if (projectSession?.id) {
-      navigateWithSidebarReset(`/${base64Encode(projectSession.directory)}/session/${projectSession.id}`)
+      await openSession(projectSession)
+      return
+    }
+
+    const latest = latestRootSession(
+      dirs.map((item) => globalSync.child(item, { bootstrap: false })[0]),
+      Date.now(),
+    )
+    if (latest) {
+      await openSession(latest)
+      return
+    }
+
+    const fetched = latestRootSession(
+      await Promise.all(
+        dirs.map(async (item) => ({
+          path: { directory: item },
+          session: await globalSDK.client.session
+            .list({ directory: item })
+            .then((x) => x.data ?? [])
+            .catch(() => []),
+        })),
+      ),
+      Date.now(),
+    )
+    if (fetched) {
+      await openSession(fetched)
       return
     }
 

+ 75 - 1
packages/app/src/pages/layout/helpers.test.ts

@@ -1,6 +1,25 @@
 import { describe, expect, test } from "bun:test"
+import { type Session } from "@opencode-ai/sdk/v2/client"
 import { collectOpenProjectDeepLinks, drainPendingDeepLinks, parseDeepLink } from "./deep-links"
-import { displayName, errorMessage, getDraggableId, syncWorkspaceOrder, workspaceKey } from "./helpers"
+import {
+  displayName,
+  errorMessage,
+  getDraggableId,
+  latestRootSession,
+  syncWorkspaceOrder,
+  workspaceKey,
+} from "./helpers"
+
+const session = (input: Partial<Session> & Pick<Session, "id" | "directory">) =>
+  ({
+    title: "",
+    version: "v2",
+    parentID: undefined,
+    messageCount: 0,
+    permissions: { session: {}, share: {} },
+    time: { created: 0, updated: 0, archived: undefined },
+    ...input,
+  }) as Session
 
 describe("layout deep links", () => {
   test("parses open-project deep links", () => {
@@ -73,6 +92,61 @@ describe("layout workspace helpers", () => {
     expect(result).toEqual(["/root", "/c", "/b"])
   })
 
+  test("finds the latest root session across workspaces", () => {
+    const result = latestRootSession(
+      [
+        {
+          path: { directory: "/root" },
+          session: [session({ id: "root", directory: "/root", time: { created: 1, updated: 1, archived: undefined } })],
+        },
+        {
+          path: { directory: "/workspace" },
+          session: [
+            session({
+              id: "workspace",
+              directory: "/workspace",
+              time: { created: 2, updated: 2, archived: undefined },
+            }),
+          ],
+        },
+      ],
+      120_000,
+    )
+
+    expect(result?.id).toBe("workspace")
+  })
+
+  test("ignores archived and child sessions when finding latest root session", () => {
+    const result = latestRootSession(
+      [
+        {
+          path: { directory: "/workspace" },
+          session: [
+            session({
+              id: "archived",
+              directory: "/workspace",
+              time: { created: 10, updated: 10, archived: 10 },
+            }),
+            session({
+              id: "child",
+              directory: "/workspace",
+              parentID: "parent",
+              time: { created: 20, updated: 20, archived: undefined },
+            }),
+            session({
+              id: "root",
+              directory: "/workspace",
+              time: { created: 30, updated: 30, archived: undefined },
+            }),
+          ],
+        },
+      ],
+      120_000,
+    )
+
+    expect(result?.id).toBe("root")
+  })
+
   test("extracts draggable id safely", () => {
     expect(getDraggableId({ draggable: { id: "x" } })).toBe("x")
     expect(getDraggableId({ draggable: { id: 42 } })).toBeUndefined()

+ 5 - 0
packages/app/src/pages/layout/helpers.ts

@@ -28,6 +28,11 @@ export const isRootVisibleSession = (session: Session, directory: string) =>
 export const sortedRootSessions = (store: { session: Session[]; path: { directory: string } }, now: number) =>
   store.session.filter((session) => isRootVisibleSession(session, store.path.directory)).sort(sortSessions(now))
 
+export const latestRootSession = (stores: { session: Session[]; path: { directory: string } }[], now: number) =>
+  stores
+    .flatMap((store) => store.session.filter((session) => isRootVisibleSession(session, store.path.directory)))
+    .sort(sortSessions(now))[0]
+
 export const childMapByParent = (sessions: Session[]) => {
   const map = new Map<string, string[]>()
   for (const session of sessions) {