Skip to content

Commit ecf601c

Browse files
committed
chore: refactor packages/app files
1 parent fa97475 commit ecf601c

93 files changed

Lines changed: 5279 additions & 4358 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/app/e2e/files/file-open.spec.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
import { test, expect } from "../fixtures"
2-
import { openPalette, clickListItem } from "../actions"
2+
import { promptSelector } from "../selectors"
33

44
test("can open a file tab from the search palette", async ({ page, gotoSession }) => {
55
await gotoSession()
66

7-
const dialog = await openPalette(page)
7+
await page.locator(promptSelector).click()
8+
await page.keyboard.type("/open")
9+
10+
const command = page.locator('[data-slash-id="file.open"]').first()
11+
await expect(command).toBeVisible()
12+
await page.keyboard.press("Enter")
13+
14+
const dialog = page
15+
.getByRole("dialog")
16+
.filter({ has: page.getByPlaceholder(/search files/i) })
17+
.first()
18+
await expect(dialog).toBeVisible()
819

920
const input = dialog.getByRole("textbox").first()
1021
await input.fill("package.json")
1122

12-
await clickListItem(dialog, { keyStartsWith: "file:" })
23+
const item = dialog.locator('[data-slot="list-item"][data-key^="file:"]').first()
24+
await expect(item).toBeVisible({ timeout: 30_000 })
25+
await item.click()
1326

1427
await expect(dialog).toHaveCount(0)
1528

packages/app/e2e/files/file-viewer.spec.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
import { test, expect } from "../fixtures"
2-
import { openPalette, clickListItem } from "../actions"
2+
import { promptSelector } from "../selectors"
33

44
test("smoke file viewer renders real file content", async ({ page, gotoSession }) => {
55
await gotoSession()
66

7-
const sep = process.platform === "win32" ? "\\" : "/"
8-
const file = ["packages", "app", "package.json"].join(sep)
7+
await page.locator(promptSelector).click()
8+
await page.keyboard.type("/open")
99

10-
const dialog = await openPalette(page)
10+
const command = page.locator('[data-slash-id="file.open"]').first()
11+
await expect(command).toBeVisible()
12+
await page.keyboard.press("Enter")
13+
14+
const dialog = page
15+
.getByRole("dialog")
16+
.filter({ has: page.getByPlaceholder(/search files/i) })
17+
.first()
18+
await expect(dialog).toBeVisible()
19+
20+
const file = "packages/app/package.json"
1121

1222
const input = dialog.getByRole("textbox").first()
1323
await input.fill(file)
1424

15-
await clickListItem(dialog, { text: /packages.*app.*package.json/ })
25+
const item = dialog
26+
.locator('[data-slot="list-item"]')
27+
.filter({ hasText: /packages[\\/].*app[\\/].*package.json/ })
28+
.first()
29+
await expect(item).toBeVisible({ timeout: 30_000 })
30+
await item.click()
1631

1732
await expect(dialog).toHaveCount(0)
1833

packages/app/e2e/projects/workspace-new-session.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,19 @@ async function createSessionFromWorkspace(page: Page, slug: string, text: string
6969

7070
const prompt = page.locator(promptSelector)
7171
await expect(prompt).toBeVisible()
72+
await expect(prompt).toBeEditable()
7273
await prompt.click()
73-
await page.keyboard.type(text)
74-
await page.keyboard.press("Enter")
74+
await expect(prompt).toBeFocused()
75+
await prompt.fill(text)
76+
await expect.poll(async () => ((await prompt.textContent()) ?? "").trim()).toContain(text)
77+
await prompt.press("Enter")
7578

7679
await expect.poll(() => slugFromUrl(page.url())).toBe(slug)
77-
await expect(page).toHaveURL(new RegExp(`/${slug}/session/[^/?#]+`), { timeout: 30_000 })
80+
await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 30_000 }).not.toBe("")
7881

7982
const sessionID = sessionIDFromUrl(page.url())
8083
if (!sessionID) throw new Error(`Failed to parse session id from url: ${page.url()}`)
84+
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${sessionID}(?:[/?#]|$)`))
8185
return sessionID
8286
}
8387

packages/app/e2e/projects/workspaces.spec.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
projectWorkspacesToggleSelector,
2323
workspaceItemSelector,
2424
} from "../selectors"
25-
import { dirSlug } from "../utils"
25+
import { createSdk, dirSlug } from "../utils"
2626

2727
function slugFromUrl(url: string) {
2828
return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
@@ -256,14 +256,45 @@ test("can delete a workspace", async ({ page, withProject }) => {
256256
await page.setViewportSize({ width: 1400, height: 800 })
257257

258258
await withProject(async (project) => {
259-
const { rootSlug, slug } = await setupWorkspaceTest(page, project)
259+
const sdk = createSdk(project.directory)
260+
const { rootSlug, slug, directory } = await setupWorkspaceTest(page, project)
261+
262+
await expect
263+
.poll(
264+
async () => {
265+
const worktrees = await sdk.worktree
266+
.list()
267+
.then((r) => r.data ?? [])
268+
.catch(() => [] as string[])
269+
return worktrees.includes(directory)
270+
},
271+
{ timeout: 30_000 },
272+
)
273+
.toBe(true)
260274

261275
const menu = await openWorkspaceMenu(page, slug)
262276
await clickMenuItem(menu, /^Delete$/i, { force: true })
263277
await confirmDialog(page, /^Delete workspace$/i)
264278

265279
await expect(page).toHaveURL(new RegExp(`/${rootSlug}/session`))
266-
await expect(page.locator(workspaceItemSelector(slug))).toHaveCount(0)
280+
281+
await expect
282+
.poll(
283+
async () => {
284+
const worktrees = await sdk.worktree
285+
.list()
286+
.then((r) => r.data ?? [])
287+
.catch(() => [] as string[])
288+
return worktrees.includes(directory)
289+
},
290+
{ timeout: 60_000 },
291+
)
292+
.toBe(false)
293+
294+
await project.gotoSession()
295+
296+
await openSidebar(page)
297+
await expect(page.locator(workspaceItemSelector(slug))).toHaveCount(0, { timeout: 60_000 })
267298
await expect(page.locator(workspaceItemSelector(rootSlug)).first()).toBeVisible()
268299
})
269300
})
Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,95 @@
11
import { test, expect } from "../fixtures"
2+
import type { Page } from "@playwright/test"
23
import { promptSelector } from "../selectors"
34
import { withSession } from "../actions"
45

6+
function contextButton(page: Page) {
7+
return page
8+
.locator('[data-component="button"]')
9+
.filter({ has: page.locator('[data-component="progress-circle"]').first() })
10+
.first()
11+
}
12+
13+
async function seedContextSession(input: { sessionID: string; sdk: Parameters<typeof withSession>[0] }) {
14+
await input.sdk.session.promptAsync({
15+
sessionID: input.sessionID,
16+
noReply: true,
17+
parts: [
18+
{
19+
type: "text",
20+
text: "seed context",
21+
},
22+
],
23+
})
24+
25+
await expect
26+
.poll(async () => {
27+
const messages = await input.sdk.session
28+
.messages({ sessionID: input.sessionID, limit: 1 })
29+
.then((r) => r.data ?? [])
30+
return messages.length
31+
})
32+
.toBeGreaterThan(0)
33+
}
34+
535
test("context panel can be opened from the prompt", async ({ page, sdk, gotoSession }) => {
636
const title = `e2e smoke context ${Date.now()}`
737

838
await withSession(sdk, title, async (session) => {
9-
await sdk.session.promptAsync({
10-
sessionID: session.id,
11-
noReply: true,
12-
parts: [
13-
{
14-
type: "text",
15-
text: "seed context",
16-
},
17-
],
18-
})
39+
await seedContextSession({ sessionID: session.id, sdk })
1940

20-
await expect
21-
.poll(async () => {
22-
const messages = await sdk.session.messages({ sessionID: session.id, limit: 1 }).then((r) => r.data ?? [])
23-
return messages.length
24-
})
25-
.toBeGreaterThan(0)
41+
await gotoSession(session.id)
42+
43+
const trigger = contextButton(page)
44+
await expect(trigger).toBeVisible()
45+
await trigger.click()
46+
47+
const tabs = page.locator('[data-component="tabs"][data-variant="normal"]')
48+
await expect(tabs.getByRole("tab", { name: "Context" })).toBeVisible()
49+
})
50+
})
2651

52+
test("context panel can be closed from the context tab close action", async ({ page, sdk, gotoSession }) => {
53+
await withSession(sdk, `e2e context toggle ${Date.now()}`, async (session) => {
54+
await seedContextSession({ sessionID: session.id, sdk })
2755
await gotoSession(session.id)
2856

29-
const contextButton = page
30-
.locator('[data-component="button"]')
31-
.filter({ has: page.locator('[data-component="progress-circle"]').first() })
32-
.first()
57+
await page.locator(promptSelector).click()
3358

34-
await expect(contextButton).toBeVisible()
35-
await contextButton.click()
59+
const trigger = contextButton(page)
60+
await expect(trigger).toBeVisible()
61+
await trigger.click()
3662

3763
const tabs = page.locator('[data-component="tabs"][data-variant="normal"]')
38-
await expect(tabs.getByRole("tab", { name: "Context" })).toBeVisible()
64+
const context = tabs.getByRole("tab", { name: "Context" })
65+
await expect(context).toBeVisible()
66+
67+
await page.getByRole("button", { name: "Close tab" }).first().click()
68+
await expect(context).toHaveCount(0)
69+
})
70+
})
71+
72+
test("context panel can open file picker from context actions", async ({ page, sdk, gotoSession }) => {
73+
await withSession(sdk, `e2e context tabs ${Date.now()}`, async (session) => {
74+
await seedContextSession({ sessionID: session.id, sdk })
75+
await gotoSession(session.id)
76+
77+
await page.locator(promptSelector).click()
78+
79+
const trigger = contextButton(page)
80+
await expect(trigger).toBeVisible()
81+
await trigger.click()
82+
83+
await expect(page.getByRole("tab", { name: "Context" })).toBeVisible()
84+
await page.getByRole("button", { name: "Open file" }).first().click()
85+
86+
const dialog = page
87+
.getByRole("dialog")
88+
.filter({ has: page.getByPlaceholder(/search files/i) })
89+
.first()
90+
await expect(dialog).toBeVisible()
91+
92+
await page.keyboard.press("Escape")
93+
await expect(dialog).toHaveCount(0)
3994
})
4095
})

packages/app/e2e/prompt/prompt.spec.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ test("can send a prompt and receive a reply", async ({ page, sdk, gotoSession })
4444
)
4545

4646
.toContain(token)
47-
48-
const reply = page.locator('[data-slot="session-turn-summary-section"]').filter({ hasText: token }).first()
49-
await expect(reply).toBeVisible({ timeout: 90_000 })
5047
} finally {
5148
page.off("pageerror", onPageError)
5249
await sdk.session.delete({ sessionID }).catch(() => undefined)

packages/app/e2e/session/session-undo-redo.spec.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,26 @@ async function seedConversation(input: {
1010
sessionID: string
1111
token: string
1212
}) {
13+
const messages = async () =>
14+
await input.sdk.session.messages({ sessionID: input.sessionID, limit: 100 }).then((r) => r.data ?? [])
15+
const seeded = await messages()
16+
const userIDs = new Set(seeded.filter((m) => m.info.role === "user").map((m) => m.info.id))
17+
1318
const prompt = input.page.locator(promptSelector)
1419
await expect(prompt).toBeVisible()
15-
await prompt.click()
16-
await input.page.keyboard.type(`Reply with exactly: ${input.token}`)
17-
await input.page.keyboard.press("Enter")
20+
await input.sdk.session.promptAsync({
21+
sessionID: input.sessionID,
22+
noReply: true,
23+
parts: [{ type: "text", text: input.token }],
24+
})
1825

1926
let userMessageID: string | undefined
2027
await expect
2128
.poll(
2229
async () => {
23-
const messages = await input.sdk.session
24-
.messages({ sessionID: input.sessionID, limit: 50 })
25-
.then((r) => r.data ?? [])
26-
const users = messages.filter(
30+
const users = (await messages()).filter(
2731
(m) =>
32+
!userIDs.has(m.info.id) &&
2833
m.info.role === "user" &&
2934
m.parts.filter((p) => p.type === "text").some((p) => p.text.includes(input.token)),
3035
)
@@ -33,21 +38,14 @@ async function seedConversation(input: {
3338
const user = users[users.length - 1]
3439
if (!user) return false
3540
userMessageID = user.info.id
36-
37-
const assistantText = messages
38-
.filter((m) => m.info.role === "assistant")
39-
.flatMap((m) => m.parts)
40-
.filter((p) => p.type === "text")
41-
.map((p) => p.text)
42-
.join("\n")
43-
44-
return assistantText.includes(input.token)
41+
return true
4542
},
46-
{ timeout: 90_000 },
43+
{ timeout: 90_000, intervals: [250, 500, 1_000] },
4744
)
4845
.toBe(true)
4946

5047
if (!userMessageID) throw new Error("Expected a user message id")
48+
await expect(input.page.locator(`[data-message-id="${userMessageID}"]`).first()).toBeVisible({ timeout: 30_000 })
5149
return { prompt, userMessageID }
5250
}
5351

0 commit comments

Comments
 (0)