reigncode-app 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +30 -0
- package/Dockerfile +21 -0
- package/README.md +51 -0
- package/bunfig.toml +3 -0
- package/create-effect-simplification-spec.md +515 -0
- package/e2e/AGENTS.md +226 -0
- package/e2e/actions.ts +1018 -0
- package/e2e/app/home.spec.ts +24 -0
- package/e2e/app/navigation.spec.ts +10 -0
- package/e2e/app/palette.spec.ts +20 -0
- package/e2e/app/server-default.spec.ts +58 -0
- package/e2e/app/session.spec.ts +16 -0
- package/e2e/app/titlebar-history.spec.ts +120 -0
- package/e2e/commands/input-focus.spec.ts +15 -0
- package/e2e/commands/panels.spec.ts +33 -0
- package/e2e/commands/tab-close.spec.ts +32 -0
- package/e2e/files/file-open.spec.ts +31 -0
- package/e2e/files/file-tree.spec.ts +56 -0
- package/e2e/files/file-viewer.spec.ts +156 -0
- package/e2e/fixtures.ts +154 -0
- package/e2e/models/model-picker.spec.ts +48 -0
- package/e2e/models/models-visibility.spec.ts +61 -0
- package/e2e/projects/project-edit.spec.ts +43 -0
- package/e2e/projects/projects-close.spec.ts +54 -0
- package/e2e/projects/projects-switch.spec.ts +116 -0
- package/e2e/projects/workspace-new-session.spec.ts +94 -0
- package/e2e/projects/workspaces.spec.ts +375 -0
- package/e2e/prompt/context.spec.ts +95 -0
- package/e2e/prompt/prompt-async.spec.ts +76 -0
- package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
- package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
- package/e2e/prompt/prompt-history.spec.ts +184 -0
- package/e2e/prompt/prompt-mention.spec.ts +26 -0
- package/e2e/prompt/prompt-multiline.spec.ts +24 -0
- package/e2e/prompt/prompt-shell.spec.ts +62 -0
- package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
- package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
- package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
- package/e2e/prompt/prompt.spec.ts +55 -0
- package/e2e/selectors.ts +75 -0
- package/e2e/session/session-child-navigation.spec.ts +37 -0
- package/e2e/session/session-composer-dock.spec.ts +530 -0
- package/e2e/session/session-model-persistence.spec.ts +359 -0
- package/e2e/session/session-review.spec.ts +426 -0
- package/e2e/session/session-undo-redo.spec.ts +233 -0
- package/e2e/session/session.spec.ts +174 -0
- package/e2e/settings/settings-keybinds.spec.ts +389 -0
- package/e2e/settings/settings-models.spec.ts +122 -0
- package/e2e/settings/settings-providers.spec.ts +136 -0
- package/e2e/settings/settings.spec.ts +519 -0
- package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
- package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
- package/e2e/sidebar/sidebar.spec.ts +40 -0
- package/e2e/status/status-popover.spec.ts +94 -0
- package/e2e/terminal/terminal-init.spec.ts +28 -0
- package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
- package/e2e/terminal/terminal-tabs.spec.ts +168 -0
- package/e2e/terminal/terminal.spec.ts +18 -0
- package/e2e/thinking-level.spec.ts +25 -0
- package/e2e/tsconfig.json +9 -0
- package/e2e/utils.ts +63 -0
- package/happydom.ts +75 -0
- package/index.html +23 -0
- package/package.json +77 -0
- package/playwright.config.ts +45 -0
- package/public/_headers +17 -0
- package/public/oc-theme-preload.js +35 -0
- package/script/e2e-local.ts +180 -0
- package/src/addons/serialize.test.ts +319 -0
- package/src/addons/serialize.ts +634 -0
- package/src/app.tsx +308 -0
- package/src/components/debug-bar.tsx +443 -0
- package/src/components/dialog-connect-provider.tsx +617 -0
- package/src/components/dialog-custom-provider-form.ts +158 -0
- package/src/components/dialog-custom-provider.test.ts +80 -0
- package/src/components/dialog-custom-provider.tsx +329 -0
- package/src/components/dialog-edit-project.tsx +255 -0
- package/src/components/dialog-fork.tsx +108 -0
- package/src/components/dialog-manage-models.tsx +101 -0
- package/src/components/dialog-release-notes.tsx +144 -0
- package/src/components/dialog-select-directory.tsx +392 -0
- package/src/components/dialog-select-file.tsx +466 -0
- package/src/components/dialog-select-mcp.tsx +107 -0
- package/src/components/dialog-select-model-unpaid.tsx +137 -0
- package/src/components/dialog-select-model.tsx +220 -0
- package/src/components/dialog-select-provider.tsx +86 -0
- package/src/components/dialog-select-server.tsx +649 -0
- package/src/components/dialog-settings.tsx +73 -0
- package/src/components/file-tree.test.ts +78 -0
- package/src/components/file-tree.tsx +507 -0
- package/src/components/link.tsx +26 -0
- package/src/components/model-tooltip.tsx +91 -0
- package/src/components/prompt-input/attachments.test.ts +44 -0
- package/src/components/prompt-input/attachments.ts +201 -0
- package/src/components/prompt-input/build-request-parts.test.ts +312 -0
- package/src/components/prompt-input/build-request-parts.ts +175 -0
- package/src/components/prompt-input/context-items.tsx +88 -0
- package/src/components/prompt-input/drag-overlay.tsx +25 -0
- package/src/components/prompt-input/editor-dom.test.ts +99 -0
- package/src/components/prompt-input/editor-dom.ts +148 -0
- package/src/components/prompt-input/files.ts +66 -0
- package/src/components/prompt-input/history.test.ts +153 -0
- package/src/components/prompt-input/history.ts +256 -0
- package/src/components/prompt-input/image-attachments.tsx +58 -0
- package/src/components/prompt-input/paste.ts +24 -0
- package/src/components/prompt-input/placeholder.test.ts +48 -0
- package/src/components/prompt-input/placeholder.ts +15 -0
- package/src/components/prompt-input/slash-popover.tsx +141 -0
- package/src/components/prompt-input/submit.test.ts +346 -0
- package/src/components/prompt-input/submit.ts +579 -0
- package/src/components/prompt-input.tsx +1595 -0
- package/src/components/server/server-row.tsx +130 -0
- package/src/components/session/index.ts +5 -0
- package/src/components/session/session-context-breakdown.test.ts +61 -0
- package/src/components/session/session-context-breakdown.ts +132 -0
- package/src/components/session/session-context-format.ts +20 -0
- package/src/components/session/session-context-metrics.test.ts +101 -0
- package/src/components/session/session-context-metrics.ts +82 -0
- package/src/components/session/session-context-tab.tsx +339 -0
- package/src/components/session/session-header.tsx +486 -0
- package/src/components/session/session-new-view.tsx +91 -0
- package/src/components/session/session-sortable-tab.tsx +70 -0
- package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
- package/src/components/session-context-usage.tsx +122 -0
- package/src/components/settings-general.tsx +585 -0
- package/src/components/settings-keybinds.tsx +453 -0
- package/src/components/settings-list.tsx +5 -0
- package/src/components/settings-models.tsx +137 -0
- package/src/components/settings-providers.tsx +251 -0
- package/src/components/status-popover.tsx +419 -0
- package/src/components/terminal.tsx +653 -0
- package/src/components/titlebar-history.test.ts +63 -0
- package/src/components/titlebar-history.ts +57 -0
- package/src/components/titlebar.tsx +312 -0
- package/src/constants/file-picker.ts +89 -0
- package/src/context/command-keybind.test.ts +69 -0
- package/src/context/command.test.ts +25 -0
- package/src/context/command.tsx +437 -0
- package/src/context/comments.test.ts +186 -0
- package/src/context/comments.tsx +243 -0
- package/src/context/file/content-cache.ts +88 -0
- package/src/context/file/path.test.ts +360 -0
- package/src/context/file/path.ts +151 -0
- package/src/context/file/tree-store.ts +170 -0
- package/src/context/file/types.ts +41 -0
- package/src/context/file/view-cache.ts +146 -0
- package/src/context/file/watcher.test.ts +149 -0
- package/src/context/file/watcher.ts +53 -0
- package/src/context/file-content-eviction-accounting.test.ts +65 -0
- package/src/context/file.tsx +280 -0
- package/src/context/global-sdk.tsx +232 -0
- package/src/context/global-sync/bootstrap.ts +206 -0
- package/src/context/global-sync/child-store.test.ts +38 -0
- package/src/context/global-sync/child-store.ts +281 -0
- package/src/context/global-sync/event-reducer.test.ts +552 -0
- package/src/context/global-sync/event-reducer.ts +359 -0
- package/src/context/global-sync/eviction.ts +28 -0
- package/src/context/global-sync/queue.ts +83 -0
- package/src/context/global-sync/session-cache.test.ts +102 -0
- package/src/context/global-sync/session-cache.ts +62 -0
- package/src/context/global-sync/session-load.ts +25 -0
- package/src/context/global-sync/session-prefetch.test.ts +96 -0
- package/src/context/global-sync/session-prefetch.ts +100 -0
- package/src/context/global-sync/session-trim.test.ts +59 -0
- package/src/context/global-sync/session-trim.ts +56 -0
- package/src/context/global-sync/types.ts +133 -0
- package/src/context/global-sync/utils.ts +25 -0
- package/src/context/global-sync.test.ts +122 -0
- package/src/context/global-sync.tsx +408 -0
- package/src/context/highlights.tsx +233 -0
- package/src/context/language.tsx +248 -0
- package/src/context/layout-scroll.test.ts +64 -0
- package/src/context/layout-scroll.ts +126 -0
- package/src/context/layout.test.ts +69 -0
- package/src/context/layout.tsx +937 -0
- package/src/context/local.tsx +422 -0
- package/src/context/model-variant.test.ts +86 -0
- package/src/context/model-variant.ts +52 -0
- package/src/context/models.tsx +163 -0
- package/src/context/notification.tsx +373 -0
- package/src/context/permission-auto-respond.test.ts +102 -0
- package/src/context/permission-auto-respond.ts +51 -0
- package/src/context/permission.tsx +277 -0
- package/src/context/platform.tsx +99 -0
- package/src/context/prompt.tsx +297 -0
- package/src/context/sdk.tsx +49 -0
- package/src/context/server.tsx +295 -0
- package/src/context/settings.tsx +241 -0
- package/src/context/sync-optimistic.test.ts +123 -0
- package/src/context/sync.tsx +618 -0
- package/src/context/terminal-title.ts +51 -0
- package/src/context/terminal.test.ts +82 -0
- package/src/context/terminal.tsx +437 -0
- package/src/entry.tsx +144 -0
- package/src/env.d.ts +18 -0
- package/src/hooks/use-providers.ts +44 -0
- package/src/i18n/ar.ts +855 -0
- package/src/i18n/br.ts +867 -0
- package/src/i18n/bs.ts +943 -0
- package/src/i18n/da.ts +937 -0
- package/src/i18n/de.ts +879 -0
- package/src/i18n/en.ts +948 -0
- package/src/i18n/es.ts +950 -0
- package/src/i18n/fr.ts +878 -0
- package/src/i18n/ja.ts +861 -0
- package/src/i18n/ko.ts +860 -0
- package/src/i18n/no.ts +944 -0
- package/src/i18n/parity.test.ts +32 -0
- package/src/i18n/pl.ts +865 -0
- package/src/i18n/ru.ts +946 -0
- package/src/i18n/th.ts +933 -0
- package/src/i18n/tr.ts +952 -0
- package/src/i18n/zh.ts +930 -0
- package/src/i18n/zht.ts +925 -0
- package/src/index.css +29 -0
- package/src/index.ts +6 -0
- package/src/pages/directory-layout.tsx +88 -0
- package/src/pages/error.tsx +327 -0
- package/src/pages/home.tsx +131 -0
- package/src/pages/layout/deep-links.ts +50 -0
- package/src/pages/layout/helpers.test.ts +211 -0
- package/src/pages/layout/helpers.ts +98 -0
- package/src/pages/layout/inline-editor.tsx +126 -0
- package/src/pages/layout/sidebar-items.tsx +437 -0
- package/src/pages/layout/sidebar-project.tsx +384 -0
- package/src/pages/layout/sidebar-shell.tsx +125 -0
- package/src/pages/layout/sidebar-workspace.tsx +504 -0
- package/src/pages/layout.tsx +2509 -0
- package/src/pages/session/composer/index.ts +2 -0
- package/src/pages/session/composer/session-composer-region.tsx +255 -0
- package/src/pages/session/composer/session-composer-state.test.ts +128 -0
- package/src/pages/session/composer/session-composer-state.ts +249 -0
- package/src/pages/session/composer/session-followup-dock.tsx +109 -0
- package/src/pages/session/composer/session-permission-dock.tsx +74 -0
- package/src/pages/session/composer/session-question-dock.tsx +449 -0
- package/src/pages/session/composer/session-request-tree.ts +52 -0
- package/src/pages/session/composer/session-revert-dock.tsx +99 -0
- package/src/pages/session/composer/session-todo-dock.tsx +330 -0
- package/src/pages/session/file-tab-scroll.test.ts +40 -0
- package/src/pages/session/file-tab-scroll.ts +67 -0
- package/src/pages/session/file-tabs.tsx +456 -0
- package/src/pages/session/handoff.ts +36 -0
- package/src/pages/session/helpers.test.ts +181 -0
- package/src/pages/session/helpers.ts +198 -0
- package/src/pages/session/message-gesture.test.ts +62 -0
- package/src/pages/session/message-gesture.ts +21 -0
- package/src/pages/session/message-id-from-hash.ts +6 -0
- package/src/pages/session/message-timeline.tsx +1013 -0
- package/src/pages/session/review-tab.tsx +170 -0
- package/src/pages/session/session-layout.ts +20 -0
- package/src/pages/session/session-model-helpers.test.ts +51 -0
- package/src/pages/session/session-model-helpers.ts +16 -0
- package/src/pages/session/session-side-panel.tsx +453 -0
- package/src/pages/session/terminal-label.ts +16 -0
- package/src/pages/session/terminal-panel.test.ts +25 -0
- package/src/pages/session/terminal-panel.tsx +326 -0
- package/src/pages/session/use-session-commands.tsx +495 -0
- package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
- package/src/pages/session/use-session-hash-scroll.ts +197 -0
- package/src/pages/session.tsx +1841 -0
- package/src/sst-env.d.ts +12 -0
- package/src/testing/model-selection.ts +80 -0
- package/src/testing/prompt.ts +56 -0
- package/src/testing/session-composer.ts +84 -0
- package/src/testing/terminal.ts +118 -0
- package/src/theme-preload.test.ts +46 -0
- package/src/utils/agent.ts +23 -0
- package/src/utils/aim.ts +138 -0
- package/src/utils/base64.ts +10 -0
- package/src/utils/comment-note.ts +88 -0
- package/src/utils/id.ts +99 -0
- package/src/utils/notification-click.test.ts +27 -0
- package/src/utils/notification-click.ts +13 -0
- package/src/utils/persist.test.ts +115 -0
- package/src/utils/persist.ts +476 -0
- package/src/utils/prompt.test.ts +44 -0
- package/src/utils/prompt.ts +203 -0
- package/src/utils/runtime-adapters.test.ts +62 -0
- package/src/utils/runtime-adapters.ts +39 -0
- package/src/utils/same.ts +6 -0
- package/src/utils/scoped-cache.test.ts +69 -0
- package/src/utils/scoped-cache.ts +104 -0
- package/src/utils/server-errors.test.ts +131 -0
- package/src/utils/server-errors.ts +80 -0
- package/src/utils/server-health.test.ts +123 -0
- package/src/utils/server-health.ts +91 -0
- package/src/utils/server.ts +22 -0
- package/src/utils/solid-dnd.tsx +49 -0
- package/src/utils/sound.ts +117 -0
- package/src/utils/terminal-writer.test.ts +64 -0
- package/src/utils/terminal-writer.ts +65 -0
- package/src/utils/time.ts +22 -0
- package/src/utils/uuid.test.ts +78 -0
- package/src/utils/uuid.ts +12 -0
- package/src/utils/worktree.test.ts +46 -0
- package/src/utils/worktree.ts +73 -0
- package/sst-env.d.ts +10 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +15 -0
- package/vite.js +26 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { waitSessionIdle, withSession } from "../actions"
|
|
2
|
+
import { test, expect } from "../fixtures"
|
|
3
|
+
import { createSdk } from "../utils"
|
|
4
|
+
|
|
5
|
+
const count = 14
|
|
6
|
+
|
|
7
|
+
function body(mark: string) {
|
|
8
|
+
return [
|
|
9
|
+
`title ${mark}`,
|
|
10
|
+
`mark ${mark}`,
|
|
11
|
+
...Array.from({ length: 32 }, (_, i) => `line ${String(i + 1).padStart(2, "0")} ${mark}`),
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function files(tag: string) {
|
|
16
|
+
return Array.from({ length: count }, (_, i) => {
|
|
17
|
+
const id = String(i).padStart(2, "0")
|
|
18
|
+
return {
|
|
19
|
+
file: `review-scroll-${id}.txt`,
|
|
20
|
+
mark: `${tag}-${id}`,
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function seed(list: ReturnType<typeof files>) {
|
|
26
|
+
const out = ["*** Begin Patch"]
|
|
27
|
+
|
|
28
|
+
for (const item of list) {
|
|
29
|
+
out.push(`*** Add File: ${item.file}`)
|
|
30
|
+
for (const line of body(item.mark)) out.push(`+${line}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
out.push("*** End Patch")
|
|
34
|
+
return out.join("\n")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function edit(file: string, prev: string, next: string) {
|
|
38
|
+
return ["*** Begin Patch", `*** Update File: ${file}`, "@@", `-mark ${prev}`, `+mark ${next}`, "*** End Patch"].join(
|
|
39
|
+
"\n",
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function patch(sdk: ReturnType<typeof createSdk>, sessionID: string, patchText: string) {
|
|
44
|
+
await sdk.session.promptAsync({
|
|
45
|
+
sessionID,
|
|
46
|
+
agent: "build",
|
|
47
|
+
system: [
|
|
48
|
+
"You are seeding deterministic e2e UI state.",
|
|
49
|
+
"Your only valid response is one apply_patch tool call.",
|
|
50
|
+
`Use this JSON input: ${JSON.stringify({ patchText })}`,
|
|
51
|
+
"Do not call any other tools.",
|
|
52
|
+
"Do not output plain text.",
|
|
53
|
+
].join("\n"),
|
|
54
|
+
parts: [{ type: "text", text: "Apply the provided patch exactly once." }],
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
await waitSessionIdle(sdk, sessionID, 120_000)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function show(page: Parameters<typeof test>[0]["page"]) {
|
|
61
|
+
const btn = page.getByRole("button", { name: "Toggle review" }).first()
|
|
62
|
+
await expect(btn).toBeVisible()
|
|
63
|
+
if ((await btn.getAttribute("aria-expanded")) !== "true") await btn.click()
|
|
64
|
+
await expect(btn).toHaveAttribute("aria-expanded", "true")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function expand(page: Parameters<typeof test>[0]["page"]) {
|
|
68
|
+
const close = page.getByRole("button", { name: /^Collapse all$/i }).first()
|
|
69
|
+
const open = await close
|
|
70
|
+
.isVisible()
|
|
71
|
+
.then((value) => value)
|
|
72
|
+
.catch(() => false)
|
|
73
|
+
|
|
74
|
+
const btn = page.getByRole("button", { name: /^Expand all$/i }).first()
|
|
75
|
+
if (open) {
|
|
76
|
+
await close.click()
|
|
77
|
+
await expect(btn).toBeVisible()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await expect(btn).toBeVisible()
|
|
81
|
+
await btn.click()
|
|
82
|
+
await expect(close).toBeVisible()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function waitMark(page: Parameters<typeof test>[0]["page"], file: string, mark: string) {
|
|
86
|
+
await page.waitForFunction(
|
|
87
|
+
({ file, mark }) => {
|
|
88
|
+
const view = document.querySelector('[data-slot="session-review-scroll"] .scroll-view__viewport')
|
|
89
|
+
if (!(view instanceof HTMLElement)) return false
|
|
90
|
+
|
|
91
|
+
const head = Array.from(view.querySelectorAll("h3")).find(
|
|
92
|
+
(node) => node instanceof HTMLElement && node.textContent?.includes(file),
|
|
93
|
+
)
|
|
94
|
+
if (!(head instanceof HTMLElement)) return false
|
|
95
|
+
|
|
96
|
+
return Array.from(head.parentElement?.querySelectorAll("diffs-container") ?? []).some((host) => {
|
|
97
|
+
if (!(host instanceof HTMLElement)) return false
|
|
98
|
+
const root = host.shadowRoot
|
|
99
|
+
return root?.textContent?.includes(`mark ${mark}`) ?? false
|
|
100
|
+
})
|
|
101
|
+
},
|
|
102
|
+
{ file, mark },
|
|
103
|
+
{ timeout: 60_000 },
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function spot(page: Parameters<typeof test>[0]["page"], file: string) {
|
|
108
|
+
return page.evaluate((file) => {
|
|
109
|
+
const view = document.querySelector('[data-slot="session-review-scroll"] .scroll-view__viewport')
|
|
110
|
+
if (!(view instanceof HTMLElement)) return null
|
|
111
|
+
|
|
112
|
+
const row = Array.from(view.querySelectorAll("h3")).find(
|
|
113
|
+
(node) => node instanceof HTMLElement && node.textContent?.includes(file),
|
|
114
|
+
)
|
|
115
|
+
if (!(row instanceof HTMLElement)) return null
|
|
116
|
+
|
|
117
|
+
const a = row.getBoundingClientRect()
|
|
118
|
+
const b = view.getBoundingClientRect()
|
|
119
|
+
return {
|
|
120
|
+
top: a.top - b.top,
|
|
121
|
+
y: view.scrollTop,
|
|
122
|
+
}
|
|
123
|
+
}, file)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function comment(page: Parameters<typeof test>[0]["page"], file: string, note: string) {
|
|
127
|
+
const row = page.locator(`[data-file="${file}"]`).first()
|
|
128
|
+
await expect(row).toBeVisible()
|
|
129
|
+
|
|
130
|
+
const line = row.locator('diffs-container [data-line="2"]').first()
|
|
131
|
+
await expect(line).toBeVisible()
|
|
132
|
+
await line.hover()
|
|
133
|
+
|
|
134
|
+
const add = row.getByRole("button", { name: /^Comment$/ }).first()
|
|
135
|
+
await expect(add).toBeVisible()
|
|
136
|
+
await add.click()
|
|
137
|
+
|
|
138
|
+
const area = row.locator('[data-slot="line-comment-textarea"]').first()
|
|
139
|
+
await expect(area).toBeVisible()
|
|
140
|
+
await area.fill(note)
|
|
141
|
+
|
|
142
|
+
const submit = row.locator('[data-slot="line-comment-action"][data-variant="primary"]').first()
|
|
143
|
+
await expect(submit).toBeEnabled()
|
|
144
|
+
await submit.click()
|
|
145
|
+
|
|
146
|
+
await expect(row.locator('[data-slot="line-comment-content"]').filter({ hasText: note }).first()).toBeVisible()
|
|
147
|
+
await expect(row.locator('[data-slot="line-comment-tools"]').first()).toBeVisible()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function overflow(page: Parameters<typeof test>[0]["page"], file: string) {
|
|
151
|
+
const row = page.locator(`[data-file="${file}"]`).first()
|
|
152
|
+
const view = page.locator('[data-slot="session-review-scroll"] .scroll-view__viewport').first()
|
|
153
|
+
const pop = row.locator('[data-slot="line-comment-popover"][data-inline-body]').first()
|
|
154
|
+
const tools = row.locator('[data-slot="line-comment-tools"]').first()
|
|
155
|
+
|
|
156
|
+
const [width, viewBox, popBox, toolsBox] = await Promise.all([
|
|
157
|
+
view.evaluate((el) => el.scrollWidth - el.clientWidth),
|
|
158
|
+
view.boundingBox(),
|
|
159
|
+
pop.boundingBox(),
|
|
160
|
+
tools.boundingBox(),
|
|
161
|
+
])
|
|
162
|
+
|
|
163
|
+
if (!viewBox || !popBox || !toolsBox) return null
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
width,
|
|
167
|
+
pop: popBox.x + popBox.width - (viewBox.x + viewBox.width),
|
|
168
|
+
tools: toolsBox.x + toolsBox.width - (viewBox.x + viewBox.width),
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function openReviewFile(page: Parameters<typeof test>[0]["page"], file: string) {
|
|
173
|
+
const row = page.locator(`[data-file="${file}"]`).first()
|
|
174
|
+
await expect(row).toBeVisible()
|
|
175
|
+
await row.hover()
|
|
176
|
+
|
|
177
|
+
const open = row.getByRole("button", { name: /^Open file$/i }).first()
|
|
178
|
+
await expect(open).toBeVisible()
|
|
179
|
+
await open.click()
|
|
180
|
+
|
|
181
|
+
const tab = page.getByRole("tab", { name: file }).first()
|
|
182
|
+
await expect(tab).toBeVisible()
|
|
183
|
+
await tab.click()
|
|
184
|
+
|
|
185
|
+
const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
|
|
186
|
+
await expect(viewer).toBeVisible()
|
|
187
|
+
return viewer
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function fileComment(page: Parameters<typeof test>[0]["page"], note: string) {
|
|
191
|
+
const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
|
|
192
|
+
await expect(viewer).toBeVisible()
|
|
193
|
+
|
|
194
|
+
const line = viewer.locator('diffs-container [data-line="2"]').first()
|
|
195
|
+
await expect(line).toBeVisible()
|
|
196
|
+
await line.hover()
|
|
197
|
+
|
|
198
|
+
const add = viewer.getByRole("button", { name: /^Comment$/ }).first()
|
|
199
|
+
await expect(add).toBeVisible()
|
|
200
|
+
await add.click()
|
|
201
|
+
|
|
202
|
+
const area = viewer.locator('[data-slot="line-comment-textarea"]').first()
|
|
203
|
+
await expect(area).toBeVisible()
|
|
204
|
+
await area.fill(note)
|
|
205
|
+
|
|
206
|
+
const submit = viewer.locator('[data-slot="line-comment-action"][data-variant="primary"]').first()
|
|
207
|
+
await expect(submit).toBeEnabled()
|
|
208
|
+
await submit.click()
|
|
209
|
+
|
|
210
|
+
await expect(viewer.locator('[data-slot="line-comment-content"]').filter({ hasText: note }).first()).toBeVisible()
|
|
211
|
+
await expect(viewer.locator('[data-slot="line-comment-tools"]').first()).toBeVisible()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function fileOverflow(page: Parameters<typeof test>[0]["page"]) {
|
|
215
|
+
const viewer = page.locator('[data-component="file"][data-mode="text"]').first()
|
|
216
|
+
const view = page.locator('[role="tabpanel"] .scroll-view__viewport').first()
|
|
217
|
+
const pop = viewer.locator('[data-slot="line-comment-popover"][data-inline-body]').first()
|
|
218
|
+
const tools = viewer.locator('[data-slot="line-comment-tools"]').first()
|
|
219
|
+
|
|
220
|
+
const [width, viewBox, popBox, toolsBox] = await Promise.all([
|
|
221
|
+
view.evaluate((el) => el.scrollWidth - el.clientWidth),
|
|
222
|
+
view.boundingBox(),
|
|
223
|
+
pop.boundingBox(),
|
|
224
|
+
tools.boundingBox(),
|
|
225
|
+
])
|
|
226
|
+
|
|
227
|
+
if (!viewBox || !popBox || !toolsBox) return null
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
width,
|
|
231
|
+
pop: popBox.x + popBox.width - (viewBox.x + viewBox.width),
|
|
232
|
+
tools: toolsBox.x + toolsBox.width - (viewBox.x + viewBox.width),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
test("review applies inline comment clicks without horizontal overflow", async ({ page, withProject }) => {
|
|
237
|
+
test.setTimeout(180_000)
|
|
238
|
+
|
|
239
|
+
const tag = `review-comment-${Date.now()}`
|
|
240
|
+
const file = `review-comment-${tag}.txt`
|
|
241
|
+
const note = `comment ${tag}`
|
|
242
|
+
|
|
243
|
+
await page.setViewportSize({ width: 1280, height: 900 })
|
|
244
|
+
|
|
245
|
+
await withProject(async (project) => {
|
|
246
|
+
const sdk = createSdk(project.directory)
|
|
247
|
+
|
|
248
|
+
await withSession(sdk, `e2e review comment ${tag}`, async (session) => {
|
|
249
|
+
await patch(sdk, session.id, seed([{ file, mark: tag }]))
|
|
250
|
+
|
|
251
|
+
await expect
|
|
252
|
+
.poll(
|
|
253
|
+
async () => {
|
|
254
|
+
const diff = await sdk.session.diff({ sessionID: session.id }).then((res) => res.data ?? [])
|
|
255
|
+
return diff.length
|
|
256
|
+
},
|
|
257
|
+
{ timeout: 60_000 },
|
|
258
|
+
)
|
|
259
|
+
.toBe(1)
|
|
260
|
+
|
|
261
|
+
await project.gotoSession(session.id)
|
|
262
|
+
await show(page)
|
|
263
|
+
|
|
264
|
+
const tab = page.getByRole("tab", { name: /Review/i }).first()
|
|
265
|
+
await expect(tab).toBeVisible()
|
|
266
|
+
await tab.click()
|
|
267
|
+
|
|
268
|
+
await expand(page)
|
|
269
|
+
await waitMark(page, file, tag)
|
|
270
|
+
await comment(page, file, note)
|
|
271
|
+
|
|
272
|
+
await expect
|
|
273
|
+
.poll(async () => (await overflow(page, file))?.width ?? Number.POSITIVE_INFINITY, { timeout: 10_000 })
|
|
274
|
+
.toBeLessThanOrEqual(1)
|
|
275
|
+
await expect
|
|
276
|
+
.poll(async () => (await overflow(page, file))?.pop ?? Number.POSITIVE_INFINITY, { timeout: 10_000 })
|
|
277
|
+
.toBeLessThanOrEqual(1)
|
|
278
|
+
await expect
|
|
279
|
+
.poll(async () => (await overflow(page, file))?.tools ?? Number.POSITIVE_INFINITY, { timeout: 10_000 })
|
|
280
|
+
.toBeLessThanOrEqual(1)
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test("review file comments submit on click without clipping actions", async ({ page, withProject }) => {
|
|
286
|
+
test.setTimeout(180_000)
|
|
287
|
+
|
|
288
|
+
const tag = `review-file-comment-${Date.now()}`
|
|
289
|
+
const file = `review-file-comment-${tag}.txt`
|
|
290
|
+
const note = `comment ${tag}`
|
|
291
|
+
|
|
292
|
+
await page.setViewportSize({ width: 1280, height: 900 })
|
|
293
|
+
|
|
294
|
+
await withProject(async (project) => {
|
|
295
|
+
const sdk = createSdk(project.directory)
|
|
296
|
+
|
|
297
|
+
await withSession(sdk, `e2e review file comment ${tag}`, async (session) => {
|
|
298
|
+
await patch(sdk, session.id, seed([{ file, mark: tag }]))
|
|
299
|
+
|
|
300
|
+
await expect
|
|
301
|
+
.poll(
|
|
302
|
+
async () => {
|
|
303
|
+
const diff = await sdk.session.diff({ sessionID: session.id }).then((res) => res.data ?? [])
|
|
304
|
+
return diff.length
|
|
305
|
+
},
|
|
306
|
+
{ timeout: 60_000 },
|
|
307
|
+
)
|
|
308
|
+
.toBe(1)
|
|
309
|
+
|
|
310
|
+
await project.gotoSession(session.id)
|
|
311
|
+
await show(page)
|
|
312
|
+
|
|
313
|
+
const tab = page.getByRole("tab", { name: /Review/i }).first()
|
|
314
|
+
await expect(tab).toBeVisible()
|
|
315
|
+
await tab.click()
|
|
316
|
+
|
|
317
|
+
await expand(page)
|
|
318
|
+
await waitMark(page, file, tag)
|
|
319
|
+
await openReviewFile(page, file)
|
|
320
|
+
await fileComment(page, note)
|
|
321
|
+
|
|
322
|
+
await expect
|
|
323
|
+
.poll(async () => (await fileOverflow(page))?.width ?? Number.POSITIVE_INFINITY, { timeout: 10_000 })
|
|
324
|
+
.toBeLessThanOrEqual(1)
|
|
325
|
+
await expect
|
|
326
|
+
.poll(async () => (await fileOverflow(page))?.pop ?? Number.POSITIVE_INFINITY, { timeout: 10_000 })
|
|
327
|
+
.toBeLessThanOrEqual(1)
|
|
328
|
+
await expect
|
|
329
|
+
.poll(async () => (await fileOverflow(page))?.tools ?? Number.POSITIVE_INFINITY, { timeout: 10_000 })
|
|
330
|
+
.toBeLessThanOrEqual(1)
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test("review keeps scroll position after a live diff update", async ({ page, withProject }) => {
|
|
336
|
+
test.skip(Boolean(process.env.CI), "Flaky in CI for now.")
|
|
337
|
+
test.setTimeout(180_000)
|
|
338
|
+
|
|
339
|
+
const tag = `review-${Date.now()}`
|
|
340
|
+
const list = files(tag)
|
|
341
|
+
const hit = list[list.length - 4]!
|
|
342
|
+
const next = `${tag}-live`
|
|
343
|
+
|
|
344
|
+
await page.setViewportSize({ width: 1600, height: 1000 })
|
|
345
|
+
|
|
346
|
+
await withProject(async (project) => {
|
|
347
|
+
const sdk = createSdk(project.directory)
|
|
348
|
+
|
|
349
|
+
await withSession(sdk, `e2e review ${tag}`, async (session) => {
|
|
350
|
+
await patch(sdk, session.id, seed(list))
|
|
351
|
+
|
|
352
|
+
await expect
|
|
353
|
+
.poll(
|
|
354
|
+
async () => {
|
|
355
|
+
const info = await sdk.session.get({ sessionID: session.id }).then((res) => res.data)
|
|
356
|
+
return info?.summary?.files ?? 0
|
|
357
|
+
},
|
|
358
|
+
{ timeout: 60_000 },
|
|
359
|
+
)
|
|
360
|
+
.toBe(list.length)
|
|
361
|
+
|
|
362
|
+
await expect
|
|
363
|
+
.poll(
|
|
364
|
+
async () => {
|
|
365
|
+
const diff = await sdk.session.diff({ sessionID: session.id }).then((res) => res.data ?? [])
|
|
366
|
+
return diff.length
|
|
367
|
+
},
|
|
368
|
+
{ timeout: 60_000 },
|
|
369
|
+
)
|
|
370
|
+
.toBe(list.length)
|
|
371
|
+
|
|
372
|
+
await project.gotoSession(session.id)
|
|
373
|
+
await show(page)
|
|
374
|
+
|
|
375
|
+
const tab = page.getByRole("tab", { name: /Review/i }).first()
|
|
376
|
+
await expect(tab).toBeVisible()
|
|
377
|
+
await tab.click()
|
|
378
|
+
|
|
379
|
+
const view = page.locator('[data-slot="session-review-scroll"] .scroll-view__viewport').first()
|
|
380
|
+
await expect(view).toBeVisible()
|
|
381
|
+
const heads = page.getByRole("heading", { level: 3 }).filter({ hasText: /^review-scroll-/ })
|
|
382
|
+
await expect(heads).toHaveCount(list.length, {
|
|
383
|
+
timeout: 60_000,
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
await expand(page)
|
|
387
|
+
await waitMark(page, hit.file, hit.mark)
|
|
388
|
+
|
|
389
|
+
const row = page
|
|
390
|
+
.getByRole("heading", { level: 3, name: new RegExp(hit.file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) })
|
|
391
|
+
.first()
|
|
392
|
+
await expect(row).toBeVisible()
|
|
393
|
+
await row.evaluate((el) => el.scrollIntoView({ block: "center" }))
|
|
394
|
+
|
|
395
|
+
await expect.poll(async () => (await spot(page, hit.file))?.y ?? 0).toBeGreaterThan(200)
|
|
396
|
+
const prev = await spot(page, hit.file)
|
|
397
|
+
if (!prev) throw new Error(`missing review row for ${hit.file}`)
|
|
398
|
+
|
|
399
|
+
await patch(sdk, session.id, edit(hit.file, hit.mark, next))
|
|
400
|
+
|
|
401
|
+
await expect
|
|
402
|
+
.poll(
|
|
403
|
+
async () => {
|
|
404
|
+
const diff = await sdk.session.diff({ sessionID: session.id }).then((res) => res.data ?? [])
|
|
405
|
+
const item = diff.find((item) => item.file === hit.file)
|
|
406
|
+
return typeof item?.after === "string" ? item.after : ""
|
|
407
|
+
},
|
|
408
|
+
{ timeout: 60_000 },
|
|
409
|
+
)
|
|
410
|
+
.toContain(`mark ${next}`)
|
|
411
|
+
|
|
412
|
+
await waitMark(page, hit.file, next)
|
|
413
|
+
|
|
414
|
+
await expect
|
|
415
|
+
.poll(
|
|
416
|
+
async () => {
|
|
417
|
+
const next = await spot(page, hit.file)
|
|
418
|
+
if (!next) return Number.POSITIVE_INFINITY
|
|
419
|
+
return Math.max(Math.abs(next.top - prev.top), Math.abs(next.y - prev.y))
|
|
420
|
+
},
|
|
421
|
+
{ timeout: 60_000 },
|
|
422
|
+
)
|
|
423
|
+
.toBeLessThanOrEqual(32)
|
|
424
|
+
})
|
|
425
|
+
})
|
|
426
|
+
})
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { Page } from "@playwright/test"
|
|
2
|
+
import { test, expect } from "../fixtures"
|
|
3
|
+
import { withSession } from "../actions"
|
|
4
|
+
import { createSdk, modKey } from "../utils"
|
|
5
|
+
import { promptSelector } from "../selectors"
|
|
6
|
+
|
|
7
|
+
async function seedConversation(input: {
|
|
8
|
+
page: Page
|
|
9
|
+
sdk: ReturnType<typeof createSdk>
|
|
10
|
+
sessionID: string
|
|
11
|
+
token: string
|
|
12
|
+
}) {
|
|
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
|
+
|
|
18
|
+
const prompt = input.page.locator(promptSelector)
|
|
19
|
+
await expect(prompt).toBeVisible()
|
|
20
|
+
await input.sdk.session.promptAsync({
|
|
21
|
+
sessionID: input.sessionID,
|
|
22
|
+
noReply: true,
|
|
23
|
+
parts: [{ type: "text", text: input.token }],
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
let userMessageID: string | undefined
|
|
27
|
+
await expect
|
|
28
|
+
.poll(
|
|
29
|
+
async () => {
|
|
30
|
+
const users = (await messages()).filter(
|
|
31
|
+
(m) =>
|
|
32
|
+
!userIDs.has(m.info.id) &&
|
|
33
|
+
m.info.role === "user" &&
|
|
34
|
+
m.parts.filter((p) => p.type === "text").some((p) => p.text.includes(input.token)),
|
|
35
|
+
)
|
|
36
|
+
if (users.length === 0) return false
|
|
37
|
+
|
|
38
|
+
const user = users[users.length - 1]
|
|
39
|
+
if (!user) return false
|
|
40
|
+
userMessageID = user.info.id
|
|
41
|
+
return true
|
|
42
|
+
},
|
|
43
|
+
{ timeout: 90_000, intervals: [250, 500, 1_000] },
|
|
44
|
+
)
|
|
45
|
+
.toBe(true)
|
|
46
|
+
|
|
47
|
+
if (!userMessageID) throw new Error("Expected a user message id")
|
|
48
|
+
await expect(input.page.locator(`[data-message-id="${userMessageID}"]`)).toHaveCount(1, { timeout: 30_000 })
|
|
49
|
+
return { prompt, userMessageID }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test("slash undo sets revert and restores prior prompt", async ({ page, withProject }) => {
|
|
53
|
+
test.setTimeout(120_000)
|
|
54
|
+
|
|
55
|
+
const token = `undo_${Date.now()}`
|
|
56
|
+
|
|
57
|
+
await withProject(async (project) => {
|
|
58
|
+
const sdk = createSdk(project.directory)
|
|
59
|
+
|
|
60
|
+
await withSession(sdk, `e2e undo ${Date.now()}`, async (session) => {
|
|
61
|
+
await project.gotoSession(session.id)
|
|
62
|
+
|
|
63
|
+
const seeded = await seedConversation({ page, sdk, sessionID: session.id, token })
|
|
64
|
+
|
|
65
|
+
await seeded.prompt.click()
|
|
66
|
+
await page.keyboard.type("/undo")
|
|
67
|
+
|
|
68
|
+
const undo = page.locator('[data-slash-id="session.undo"]').first()
|
|
69
|
+
await expect(undo).toBeVisible()
|
|
70
|
+
await page.keyboard.press("Enter")
|
|
71
|
+
|
|
72
|
+
await expect
|
|
73
|
+
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
|
74
|
+
timeout: 30_000,
|
|
75
|
+
})
|
|
76
|
+
.toBe(seeded.userMessageID)
|
|
77
|
+
|
|
78
|
+
await expect(seeded.prompt).toContainText(token)
|
|
79
|
+
await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`)).toHaveCount(0)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("slash redo clears revert and restores latest state", async ({ page, withProject }) => {
|
|
85
|
+
test.setTimeout(120_000)
|
|
86
|
+
|
|
87
|
+
const token = `redo_${Date.now()}`
|
|
88
|
+
|
|
89
|
+
await withProject(async (project) => {
|
|
90
|
+
const sdk = createSdk(project.directory)
|
|
91
|
+
|
|
92
|
+
await withSession(sdk, `e2e redo ${Date.now()}`, async (session) => {
|
|
93
|
+
await project.gotoSession(session.id)
|
|
94
|
+
|
|
95
|
+
const seeded = await seedConversation({ page, sdk, sessionID: session.id, token })
|
|
96
|
+
|
|
97
|
+
await seeded.prompt.click()
|
|
98
|
+
await page.keyboard.type("/undo")
|
|
99
|
+
|
|
100
|
+
const undo = page.locator('[data-slash-id="session.undo"]').first()
|
|
101
|
+
await expect(undo).toBeVisible()
|
|
102
|
+
await page.keyboard.press("Enter")
|
|
103
|
+
|
|
104
|
+
await expect
|
|
105
|
+
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
|
106
|
+
timeout: 30_000,
|
|
107
|
+
})
|
|
108
|
+
.toBe(seeded.userMessageID)
|
|
109
|
+
|
|
110
|
+
await seeded.prompt.click()
|
|
111
|
+
await page.keyboard.press(`${modKey}+A`)
|
|
112
|
+
await page.keyboard.press("Backspace")
|
|
113
|
+
await page.keyboard.type("/redo")
|
|
114
|
+
|
|
115
|
+
const redo = page.locator('[data-slash-id="session.redo"]').first()
|
|
116
|
+
await expect(redo).toBeVisible()
|
|
117
|
+
await page.keyboard.press("Enter")
|
|
118
|
+
|
|
119
|
+
await expect
|
|
120
|
+
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
|
121
|
+
timeout: 30_000,
|
|
122
|
+
})
|
|
123
|
+
.toBeUndefined()
|
|
124
|
+
|
|
125
|
+
await expect(seeded.prompt).not.toContainText(token)
|
|
126
|
+
await expect(page.locator(`[data-message-id="${seeded.userMessageID}"]`)).toHaveCount(1)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test("slash undo/redo traverses multi-step revert stack", async ({ page, withProject }) => {
|
|
132
|
+
test.setTimeout(120_000)
|
|
133
|
+
|
|
134
|
+
const firstToken = `undo_redo_first_${Date.now()}`
|
|
135
|
+
const secondToken = `undo_redo_second_${Date.now()}`
|
|
136
|
+
|
|
137
|
+
await withProject(async (project) => {
|
|
138
|
+
const sdk = createSdk(project.directory)
|
|
139
|
+
|
|
140
|
+
await withSession(sdk, `e2e undo redo stack ${Date.now()}`, async (session) => {
|
|
141
|
+
await project.gotoSession(session.id)
|
|
142
|
+
|
|
143
|
+
const first = await seedConversation({
|
|
144
|
+
page,
|
|
145
|
+
sdk,
|
|
146
|
+
sessionID: session.id,
|
|
147
|
+
token: firstToken,
|
|
148
|
+
})
|
|
149
|
+
const second = await seedConversation({
|
|
150
|
+
page,
|
|
151
|
+
sdk,
|
|
152
|
+
sessionID: session.id,
|
|
153
|
+
token: secondToken,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
expect(first.userMessageID).not.toBe(second.userMessageID)
|
|
157
|
+
|
|
158
|
+
const firstMessage = page.locator(`[data-message-id="${first.userMessageID}"]`)
|
|
159
|
+
const secondMessage = page.locator(`[data-message-id="${second.userMessageID}"]`)
|
|
160
|
+
|
|
161
|
+
await expect(firstMessage).toHaveCount(1)
|
|
162
|
+
await expect(secondMessage).toHaveCount(1)
|
|
163
|
+
|
|
164
|
+
await second.prompt.click()
|
|
165
|
+
await page.keyboard.press(`${modKey}+A`)
|
|
166
|
+
await page.keyboard.press("Backspace")
|
|
167
|
+
await page.keyboard.type("/undo")
|
|
168
|
+
|
|
169
|
+
const undo = page.locator('[data-slash-id="session.undo"]').first()
|
|
170
|
+
await expect(undo).toBeVisible()
|
|
171
|
+
await page.keyboard.press("Enter")
|
|
172
|
+
|
|
173
|
+
await expect
|
|
174
|
+
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
|
175
|
+
timeout: 30_000,
|
|
176
|
+
})
|
|
177
|
+
.toBe(second.userMessageID)
|
|
178
|
+
|
|
179
|
+
await expect(firstMessage).toHaveCount(1)
|
|
180
|
+
await expect(secondMessage).toHaveCount(0)
|
|
181
|
+
|
|
182
|
+
await second.prompt.click()
|
|
183
|
+
await page.keyboard.press(`${modKey}+A`)
|
|
184
|
+
await page.keyboard.press("Backspace")
|
|
185
|
+
await page.keyboard.type("/undo")
|
|
186
|
+
await expect(undo).toBeVisible()
|
|
187
|
+
await page.keyboard.press("Enter")
|
|
188
|
+
|
|
189
|
+
await expect
|
|
190
|
+
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
|
191
|
+
timeout: 30_000,
|
|
192
|
+
})
|
|
193
|
+
.toBe(first.userMessageID)
|
|
194
|
+
|
|
195
|
+
await expect(firstMessage).toHaveCount(0)
|
|
196
|
+
await expect(secondMessage).toHaveCount(0)
|
|
197
|
+
|
|
198
|
+
await second.prompt.click()
|
|
199
|
+
await page.keyboard.press(`${modKey}+A`)
|
|
200
|
+
await page.keyboard.press("Backspace")
|
|
201
|
+
await page.keyboard.type("/redo")
|
|
202
|
+
|
|
203
|
+
const redo = page.locator('[data-slash-id="session.redo"]').first()
|
|
204
|
+
await expect(redo).toBeVisible()
|
|
205
|
+
await page.keyboard.press("Enter")
|
|
206
|
+
|
|
207
|
+
await expect
|
|
208
|
+
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
|
209
|
+
timeout: 30_000,
|
|
210
|
+
})
|
|
211
|
+
.toBe(second.userMessageID)
|
|
212
|
+
|
|
213
|
+
await expect(firstMessage).toHaveCount(1)
|
|
214
|
+
await expect(secondMessage).toHaveCount(0)
|
|
215
|
+
|
|
216
|
+
await second.prompt.click()
|
|
217
|
+
await page.keyboard.press(`${modKey}+A`)
|
|
218
|
+
await page.keyboard.press("Backspace")
|
|
219
|
+
await page.keyboard.type("/redo")
|
|
220
|
+
await expect(redo).toBeVisible()
|
|
221
|
+
await page.keyboard.press("Enter")
|
|
222
|
+
|
|
223
|
+
await expect
|
|
224
|
+
.poll(async () => await sdk.session.get({ sessionID: session.id }).then((r) => r.data?.revert?.messageID), {
|
|
225
|
+
timeout: 30_000,
|
|
226
|
+
})
|
|
227
|
+
.toBeUndefined()
|
|
228
|
+
|
|
229
|
+
await expect(firstMessage).toHaveCount(1)
|
|
230
|
+
await expect(secondMessage).toHaveCount(1)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
})
|