comments.test.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { beforeAll, describe, expect, mock, test } from "bun:test"
  2. import { createRoot } from "solid-js"
  3. import type { LineComment } from "./comments"
  4. let createCommentSessionForTest: typeof import("./comments").createCommentSessionForTest
  5. beforeAll(async () => {
  6. mock.module("@solidjs/router", () => ({
  7. useNavigate: () => () => undefined,
  8. useParams: () => ({}),
  9. }))
  10. mock.module("@opencode-ai/ui/context", () => ({
  11. createSimpleContext: () => ({
  12. use: () => undefined,
  13. provider: () => undefined,
  14. }),
  15. }))
  16. const mod = await import("./comments")
  17. createCommentSessionForTest = mod.createCommentSessionForTest
  18. })
  19. function line(file: string, id: string, time: number): LineComment {
  20. return {
  21. id,
  22. file,
  23. comment: id,
  24. time,
  25. selection: { start: 1, end: 1 },
  26. }
  27. }
  28. describe("comments session indexing", () => {
  29. test("keeps file list behavior and aggregate chronological order", () => {
  30. createRoot((dispose) => {
  31. const now = Date.now()
  32. const comments = createCommentSessionForTest({
  33. "a.ts": [line("a.ts", "a-late", now + 20_000), line("a.ts", "a-early", now + 1_000)],
  34. "b.ts": [line("b.ts", "b-mid", now + 10_000)],
  35. })
  36. expect(comments.list("a.ts").map((item) => item.id)).toEqual(["a-late", "a-early"])
  37. expect(comments.all().map((item) => item.id)).toEqual(["a-early", "b-mid", "a-late"])
  38. const next = comments.add({
  39. file: "b.ts",
  40. comment: "next",
  41. selection: { start: 2, end: 2 },
  42. })
  43. expect(comments.list("b.ts").at(-1)?.id).toBe(next.id)
  44. expect(comments.all().map((item) => item.time)).toEqual(
  45. comments
  46. .all()
  47. .map((item) => item.time)
  48. .slice()
  49. .sort((a, b) => a - b),
  50. )
  51. dispose()
  52. })
  53. })
  54. test("remove updates file and aggregate indexes consistently", () => {
  55. createRoot((dispose) => {
  56. const comments = createCommentSessionForTest({
  57. "a.ts": [line("a.ts", "a1", 10), line("a.ts", "shared", 20)],
  58. "b.ts": [line("b.ts", "shared", 30)],
  59. })
  60. comments.setFocus({ file: "a.ts", id: "shared" })
  61. comments.setActive({ file: "a.ts", id: "shared" })
  62. comments.remove("a.ts", "shared")
  63. expect(comments.list("a.ts").map((item) => item.id)).toEqual(["a1"])
  64. expect(
  65. comments
  66. .all()
  67. .filter((item) => item.id === "shared")
  68. .map((item) => item.file),
  69. ).toEqual(["b.ts"])
  70. expect(comments.focus()).toBeNull()
  71. expect(comments.active()).toEqual({ file: "a.ts", id: "shared" })
  72. dispose()
  73. })
  74. })
  75. test("clear resets file and aggregate indexes plus focus state", () => {
  76. createRoot((dispose) => {
  77. const comments = createCommentSessionForTest({
  78. "a.ts": [line("a.ts", "a1", 10)],
  79. })
  80. const next = comments.add({
  81. file: "b.ts",
  82. comment: "next",
  83. selection: { start: 2, end: 2 },
  84. })
  85. comments.setActive({ file: "b.ts", id: next.id })
  86. comments.clear()
  87. expect(comments.list("a.ts")).toEqual([])
  88. expect(comments.list("b.ts")).toEqual([])
  89. expect(comments.all()).toEqual([])
  90. expect(comments.focus()).toBeNull()
  91. expect(comments.active()).toBeNull()
  92. dispose()
  93. })
  94. })
  95. test("remove keeps focus when same comment id exists in another file", () => {
  96. createRoot((dispose) => {
  97. const comments = createCommentSessionForTest({
  98. "a.ts": [line("a.ts", "shared", 10)],
  99. "b.ts": [line("b.ts", "shared", 20)],
  100. })
  101. comments.setFocus({ file: "b.ts", id: "shared" })
  102. comments.remove("a.ts", "shared")
  103. expect(comments.focus()).toEqual({ file: "b.ts", id: "shared" })
  104. expect(comments.list("a.ts")).toEqual([])
  105. expect(comments.list("b.ts").map((item) => item.id)).toEqual(["shared"])
  106. dispose()
  107. })
  108. })
  109. test("setFocus and setActive updater callbacks receive current state", () => {
  110. createRoot((dispose) => {
  111. const comments = createCommentSessionForTest()
  112. comments.setFocus({ file: "a.ts", id: "a1" })
  113. comments.setFocus((current) => {
  114. expect(current).toEqual({ file: "a.ts", id: "a1" })
  115. return { file: "b.ts", id: "b1" }
  116. })
  117. comments.setActive({ file: "c.ts", id: "c1" })
  118. comments.setActive((current) => {
  119. expect(current).toEqual({ file: "c.ts", id: "c1" })
  120. return null
  121. })
  122. expect(comments.focus()).toEqual({ file: "b.ts", id: "b1" })
  123. expect(comments.active()).toBeNull()
  124. dispose()
  125. })
  126. })
  127. test("update changes only the targeted comment body", () => {
  128. createRoot((dispose) => {
  129. const comments = createCommentSessionForTest({
  130. "a.ts": [line("a.ts", "a1", 10), line("a.ts", "a2", 20)],
  131. })
  132. comments.update("a.ts", "a2", "edited")
  133. expect(comments.list("a.ts").map((item) => item.comment)).toEqual(["a1", "edited"])
  134. dispose()
  135. })
  136. })
  137. test("replace swaps comment state and clears focus state", () => {
  138. createRoot((dispose) => {
  139. const comments = createCommentSessionForTest({
  140. "a.ts": [line("a.ts", "a1", 10)],
  141. })
  142. comments.setFocus({ file: "a.ts", id: "a1" })
  143. comments.setActive({ file: "a.ts", id: "a1" })
  144. comments.replace([line("b.ts", "b1", 30)])
  145. expect(comments.list("a.ts")).toEqual([])
  146. expect(comments.list("b.ts").map((item) => item.id)).toEqual(["b1"])
  147. expect(comments.focus()).toBeNull()
  148. expect(comments.active()).toBeNull()
  149. dispose()
  150. })
  151. })
  152. })