review-line-comment.spec.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { expect, test, type Page } from "@playwright/test"
  2. import { base64Encode } from "@opencode-ai/core/util/encode"
  3. import { mockOpenCodeServer } from "../utils/mock-server"
  4. import { expectAppVisible, expectSessionTitle } from "../utils/waits"
  5. const directory = "C:/OpenCode/ReviewLineCommentRegression"
  6. const sessionID = "ses_review_line_comment_regression"
  7. const title = "Review line comment regression"
  8. test.beforeEach(async ({ page }) => {
  9. await openReview(page)
  10. })
  11. test("opens the comment editor when code is clicked", async ({ page }) => {
  12. const review = page.locator('[data-component="session-review"]')
  13. const line = review.getByText("export const value = 'after'", { exact: true })
  14. await expectAppVisible(line)
  15. await line.click()
  16. await expect(review.getByRole("textbox")).toBeVisible()
  17. await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 2")
  18. })
  19. test("opens the comment editor when a line number is clicked", async ({ page }) => {
  20. const review = page.locator('[data-component="session-review"]')
  21. const lineNumber = review.locator('[data-column-number="1"]').last()
  22. await expectAppVisible(lineNumber)
  23. await lineNumber.click()
  24. await expect(review.getByRole("textbox")).toBeVisible()
  25. await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 1")
  26. })
  27. test("opens the comment editor for a line number range", async ({ page }) => {
  28. const review = page.locator('[data-component="session-review"]')
  29. const start = review.locator('[data-column-number="1"]').last()
  30. const end = review.locator('[data-column-number="3"]').last()
  31. await expectAppVisible(start)
  32. await expectAppVisible(end)
  33. await start.dragTo(end)
  34. await expect(review.getByRole("textbox")).toBeVisible()
  35. await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on lines 1-3")
  36. })
  37. test("shows a comment button when a line number is hovered", async ({ page }) => {
  38. const review = page.locator('[data-component="session-review"]')
  39. const lineNumber = review.locator('[data-column-number="1"]').last()
  40. await expectAppVisible(lineNumber)
  41. const comment = review.getByRole("button", { name: "Comment", exact: true })
  42. await expect(async () => {
  43. await lineNumber.hover()
  44. await expect(lineNumber).toHaveAttribute("data-hovered", "")
  45. await expect(comment).toHaveCount(1)
  46. await expect(comment).toHaveCSS("pointer-events", "auto")
  47. await comment.focus()
  48. await expect(comment).toBeFocused()
  49. }).toPass({ timeout: 10_000 })
  50. await comment.press("Enter")
  51. await expect(review.getByRole("textbox")).toBeVisible()
  52. await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 1")
  53. })
  54. test("stages a submitted line comment in the prompt context", async ({ page }) => {
  55. page.on("request", (request) => {
  56. expect.soft(request.method(), `unexpected ${request.method()} ${new URL(request.url()).pathname}`).toBe("GET")
  57. })
  58. const review = page.locator('[data-component="session-review"]')
  59. await review.getByText("export const value = 'after'", { exact: true }).click()
  60. const textbox = review.getByRole("textbox")
  61. await expect(textbox).toBeVisible()
  62. await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 2")
  63. await textbox.fill("Use the existing value instead")
  64. const submit = review.locator('[data-slot="line-comment-action"][data-variant="primary"]')
  65. await expect(submit).toBeEnabled()
  66. await submit.click()
  67. await expect(review.getByText("Use the existing value instead", { exact: true })).toBeVisible()
  68. await page.getByRole("tab", { name: "Session" }).click()
  69. const context = page.getByText("Use the existing value instead", { exact: true }).last()
  70. await expect(context).toBeVisible()
  71. await expect(context.locator("..")).toContainText("review.ts:2")
  72. })
  73. async function openReview(page: Page) {
  74. await page.setViewportSize({ width: 700, height: 900 })
  75. await mockOpenCodeServer(page, {
  76. protocol: "v2",
  77. directory,
  78. project: {
  79. id: "proj_review_line_comment_regression",
  80. worktree: directory,
  81. vcs: "git",
  82. name: "review-line-comment-regression",
  83. time: { created: 1700000000000, updated: 1700000000000 },
  84. sandboxes: [],
  85. },
  86. provider: { all: [], connected: [], default: {} },
  87. sessions: [
  88. {
  89. id: sessionID,
  90. slug: "review-line-comment-regression",
  91. projectID: "proj_review_line_comment_regression",
  92. directory,
  93. title,
  94. version: "dev",
  95. time: { created: 1700000000000, updated: 1700000000000 },
  96. },
  97. ],
  98. vcsDiff: [
  99. {
  100. file: "src/review.ts",
  101. additions: 1,
  102. deletions: 1,
  103. status: "modified",
  104. patch:
  105. "diff --git a/src/review.ts b/src/review.ts\n--- a/src/review.ts\n+++ b/src/review.ts\n@@ -1,3 +1,3 @@\n export const first = 1\n-export const value = 'before'\n+export const value = 'after'\n export const last = 3\n",
  106. },
  107. ],
  108. pageMessages: () => ({
  109. items: [
  110. {
  111. info: {
  112. id: "msg_review_line_comment_regression",
  113. sessionID,
  114. role: "user",
  115. time: { created: 1700000000000 },
  116. summary: { diffs: [] },
  117. agent: "build",
  118. model: { providerID: "opencode", modelID: "test" },
  119. },
  120. parts: [
  121. {
  122. id: "prt_review_line_comment_regression",
  123. sessionID,
  124. messageID: "msg_review_line_comment_regression",
  125. type: "text",
  126. text: "Review this change.",
  127. },
  128. ],
  129. },
  130. ],
  131. }),
  132. })
  133. await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
  134. await expectSessionTitle(page, title)
  135. const changes = page.getByRole("tab", { name: "Changes" })
  136. const diffResponse = page.waitForResponse(
  137. (response) =>
  138. response.request().method() === "GET" && response.ok() && new URL(response.url()).pathname === "/api/vcs/diff",
  139. )
  140. await changes.click()
  141. expect((await (await diffResponse).json()).data).toHaveLength(1)
  142. await expect(page.getByRole("tab", { selected: true })).toHaveAccessibleName(/Files Changed/)
  143. const review = page.locator('[data-component="session-review"]')
  144. await expectAppVisible(review)
  145. const file = review.locator('[data-file="src/review.ts"]')
  146. await expectAppVisible(file)
  147. const trigger = file.getByRole("button", { expanded: false })
  148. await expect(trigger).toHaveCount(1)
  149. await trigger.click()
  150. await expect(file.getByRole("button", { expanded: true })).toBeVisible()
  151. await expect(file.getByText("export const value = 'after'", { exact: true })).toBeVisible()
  152. }