review-state-persistence.spec.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { base64Encode } from "@opencode-ai/core/util/encode"
  2. import { expect, test, type Page } from "@playwright/test"
  3. import { mockOpenCodeServer } from "../utils/mock-server"
  4. import { expectSessionTitle } from "../utils/waits"
  5. const directory = "C:/OpenCode/ReviewStatePersistence"
  6. const projectID = "proj_review_state_persistence"
  7. const sessionA = "ses_review_state_a"
  8. const sessionB = "ses_review_state_b"
  9. const titleA = "Alpha review state"
  10. const titleB = "Beta review state"
  11. const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
  12. test.use({ viewport: { width: 1440, height: 900 } })
  13. test("restores review mode and selected file per session", async ({ page }) => {
  14. await setup(page)
  15. await page.goto(sessionHref(sessionA))
  16. await expectSessionTitle(page, titleA)
  17. await page.getByRole("button", { name: "Toggle review" }).click()
  18. await selectMode(page, "Git changes", "Branch changes")
  19. await selectFile(page, "beta.ts")
  20. await switchSession(page, titleB)
  21. await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible()
  22. await selectFile(page, "gamma.ts")
  23. await switchSession(page, titleA)
  24. await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible()
  25. await expectSelectedFile(page, "beta.ts")
  26. await selectMode(page, "Branch changes", "Git changes")
  27. await expectSelectedFile(page, "alpha.ts")
  28. await selectMode(page, "Git changes", "Branch changes")
  29. await expectSelectedFile(page, "beta.ts")
  30. await page.reload()
  31. await expectSessionTitle(page, titleA)
  32. await expect(page.getByRole("button", { name: "Branch changes" })).toBeVisible()
  33. await expectSelectedFile(page, "beta.ts")
  34. await switchSession(page, titleB)
  35. await expect(page.getByRole("button", { name: "Git changes" })).toBeVisible()
  36. await expectSelectedFile(page, "gamma.ts")
  37. })
  38. async function selectMode(page: Page, current: string, next: string) {
  39. await page.getByRole("button", { name: current }).click()
  40. await page.getByRole("option", { name: next }).dispatchEvent("click")
  41. }
  42. async function selectFile(page: Page, file: string) {
  43. await page.getByRole("button", { name: file }).click()
  44. await expectSelectedFile(page, file)
  45. }
  46. async function expectSelectedFile(page: Page, file: string) {
  47. await expect(page.locator('[data-slot="session-review-v2-file-name"]')).toHaveText(file)
  48. }
  49. async function switchSession(page: Page, title: string) {
  50. await page.locator("[data-titlebar-tab-slot]", { hasText: title }).click()
  51. await expectSessionTitle(page, title)
  52. }
  53. async function setup(page: Page) {
  54. await mockOpenCodeServer(page, {
  55. protocol: "v1",
  56. directory,
  57. project: {
  58. id: projectID,
  59. worktree: directory,
  60. vcs: "git",
  61. name: "review-state-persistence",
  62. time: { created: 1700000000000, updated: 1700000000000 },
  63. sandboxes: [],
  64. },
  65. provider: {
  66. all: [
  67. {
  68. id: "opencode",
  69. name: "OpenCode",
  70. models: { test: { id: "test", name: "Test", limit: { context: 200_000 } } },
  71. },
  72. ],
  73. connected: ["opencode"],
  74. default: { providerID: "opencode", modelID: "test" },
  75. },
  76. sessions: [session(sessionA, titleA, 1700000000000), session(sessionB, titleB, 1700000001000)],
  77. pageMessages: () => ({ items: [] }),
  78. })
  79. await page.route(/\/vcs(?:\?.*)?$/, (route) =>
  80. route.fulfill({
  81. status: 200,
  82. contentType: "application/json",
  83. body: JSON.stringify({ branch: "feature", default_branch: "dev" }),
  84. }),
  85. )
  86. await page.route("**/vcs/diff**", (route) =>
  87. route.fulfill({
  88. status: 200,
  89. contentType: "application/json",
  90. body: JSON.stringify(
  91. new URL(route.request().url()).searchParams.get("mode") === "branch"
  92. ? [diff("src/alpha.ts"), diff("src/beta.ts")]
  93. : [diff("src/alpha.ts"), diff("src/gamma.ts")],
  94. ),
  95. }),
  96. )
  97. await page.addInitScript(
  98. ({ directory, server, sessions }) => {
  99. localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
  100. localStorage.setItem(
  101. "opencode.global.dat:server",
  102. JSON.stringify({
  103. projects: { local: [{ worktree: directory, expanded: true }] },
  104. lastProject: { local: directory },
  105. }),
  106. )
  107. localStorage.setItem(
  108. "opencode.window.browser.dat:tabs",
  109. JSON.stringify(sessions.map((sessionId: string) => ({ type: "session", server, sessionId }))),
  110. )
  111. },
  112. { directory, server, sessions: [sessionA, sessionB] },
  113. )
  114. }
  115. function session(id: string, title: string, created: number) {
  116. return {
  117. id,
  118. slug: id,
  119. projectID,
  120. directory,
  121. title,
  122. version: "dev",
  123. time: { created, updated: created },
  124. }
  125. }
  126. function diff(file: string) {
  127. return {
  128. file,
  129. additions: 1,
  130. deletions: 1,
  131. status: "modified",
  132. patch: `diff --git a/${file} b/${file}\n--- a/${file}\n+++ b/${file}\n@@ -1 +1 @@\n-export const value = 'before'\n+export const value = 'after'\n`,
  133. }
  134. }
  135. function sessionHref(sessionID: string) {
  136. return `/server/${base64Encode(server)}/session/${sessionID}`
  137. }