test(diff): automate DiffView/CodeBlock horizontal-scroll coverage (closes #21) (#101)

Turns the manual QA ask ("verify DiffView + CodeBlock horizontal-scroll
on-device with a populated diff") into two automated layers:

1. Unit (deterministic, runs in `npm test` now): extracted the shared
   ScrollView props into src/lib/scroll-config.ts (WIDE_CONTENT_SCROLL_CONFIG)
   so DiffView.tsx and CodeBlock.tsx spread the SAME plain object their tests
   assert on — no react-native-renderer needed. Added a source-scan
   regression test (wide-content-scroll.regression.test.ts) that fails if
   either component loses its ScrollView wiring or reintroduces
   numberOfLines truncation.

2. E2E (Maestro): .maestro/flows/diff-scroll.yaml opens a session with a
   pre-seeded wide edit-diff tool call and a wide fenced code block, then
   swipes each horizontal ScrollView left and asserts the off-screen marker
   text becomes visible. mock-opencode-server.ts gained a --seed-diff mode
   that serves this session via GET /session/:id/message (pre-existing
   history), not SSE — issue #90 (a separate SSE-render bug) is being fixed
   independently, and this flow must not depend on it landing first. Wired
   the new flow + port 4100 into run-e2e-flows.sh and activation-e2e.yml.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Den
2026-07-17 10:37:05 -07:00
committed by GitHub
parent 9a24cea61f
commit 0cac46cb36
9 changed files with 334 additions and 10 deletions

View File

@@ -120,12 +120,15 @@ jobs:
# different directories, for all-sessions.yaml). 4099 is a fresh normal-mode # different directories, for all-sessions.yaml). 4099 is a fresh normal-mode
# instance shared by directory-picker.yaml and variant-picker.yaml (its fake # instance shared by directory-picker.yaml and variant-picker.yaml (its fake
# file tree / provider variants / project list don't affect each other). # file tree / provider variants / project list don't affect each other).
# 4100 is normal mode + --seed-diff (one session with a pre-existing wide
# edit-diff tool call + wide code block, for diff-scroll.yaml / issue #21).
# All bind 0.0.0.0 so the emulator can reach them via 10.0.2.2. # All bind 0.0.0.0 so the emulator can reach them via 10.0.2.2.
nohup node tests/fixtures/mock-opencode-server.ts --port 4096 > /tmp/mock-4096.log 2>&1 & nohup node tests/fixtures/mock-opencode-server.ts --port 4096 > /tmp/mock-4096.log 2>&1 &
nohup node tests/fixtures/mock-opencode-server.ts --port 4097 --fail-auth > /tmp/mock-4097.log 2>&1 & nohup node tests/fixtures/mock-opencode-server.ts --port 4097 --fail-auth > /tmp/mock-4097.log 2>&1 &
nohup node tests/fixtures/mock-opencode-server.ts --port 4098 --seed-sessions > /tmp/mock-4098.log 2>&1 & nohup node tests/fixtures/mock-opencode-server.ts --port 4098 --seed-sessions > /tmp/mock-4098.log 2>&1 &
nohup node tests/fixtures/mock-opencode-server.ts --port 4099 > /tmp/mock-4099.log 2>&1 & nohup node tests/fixtures/mock-opencode-server.ts --port 4099 > /tmp/mock-4099.log 2>&1 &
for port in 4096 4097 4098 4099; do nohup node tests/fixtures/mock-opencode-server.ts --port 4100 --seed-diff > /tmp/mock-4100.log 2>&1 &
for port in 4096 4097 4098 4099 4100; do
for i in $(seq 1 30); do for i in $(seq 1 30); do
if curl -sf --connect-timeout 1 -m 3 "http://127.0.0.1:${port}/global/health" > /dev/null 2>&1; then if curl -sf --connect-timeout 1 -m 3 "http://127.0.0.1:${port}/global/health" > /dev/null 2>&1; then
echo "mock server on ${port} responded (may be 401, that's expected on 4097)" echo "mock server on ${port} responded (may be 401, that's expected on 4097)"
@@ -167,6 +170,7 @@ jobs:
echo "--- 4097 (fail-auth) ---"; cat /tmp/mock-4097.log || true echo "--- 4097 (fail-auth) ---"; cat /tmp/mock-4097.log || true
echo "--- 4098 (seed-sessions) ---"; cat /tmp/mock-4098.log || true echo "--- 4098 (seed-sessions) ---"; cat /tmp/mock-4098.log || true
echo "--- 4099 (normal, directory-picker + variant-picker) ---"; cat /tmp/mock-4099.log || true echo "--- 4099 (normal, directory-picker + variant-picker) ---"; cat /tmp/mock-4099.log || true
echo "--- 4100 (seed-diff) ---"; cat /tmp/mock-4100.log || true
- name: Upload screenshots - name: Upload screenshots
if: always() if: always()

View File

@@ -0,0 +1,119 @@
appId: cc.agentlabs.opencode
name: "DiffView + CodeBlock horizontal scroll - wide diff/code line reachable by swipe (closes #21)"
---
# GitHub issue #21 ("QA: verify DiffView + CodeBlock horizontal-scroll
# on-device with a populated diff"). Two rendering fixes shipped earlier were
# code-audited but never verified with real, wide content on a real
# (emulated) device:
# - src/components/chat/DiffView.tsx: diff lines render inside a horizontal
# ScrollView (previously truncated with numberOfLines={1}).
# - src/components/markdown/CodeBlock.tsx: code renders inside a horizontal
# ScrollView (previously long lines wrapped/mangled).
#
# The CI job starts `node tests/fixtures/mock-opencode-server.ts --port 4100
# --seed-diff` on the runner host BEFORE this flow runs. --seed-diff
# pre-populates ONE session (id "seed-diff", directory /mock/project — the
# default connection's directory) whose single assistant message already
# contains:
# - a completed `edit` tool part with a wide "add" diff line (renders via
# ToolCallCard -> EditDetail -> DiffView once the card is expanded)
# - a text part with a wide fenced code block (renders via Markdown ->
# CodeBlock)
# Both wide lines are ~220 filler characters (no spaces, so they can't
# word-wrap) followed by a distinct marker token. This is loaded via GET
# /session/seed-diff/message when the session is opened (selectSession),
# NOT delivered over SSE — issue #90 (a separate SSE-render bug) is being
# fixed independently, and this flow must not depend on it landing first.
#
# Caveat for whoever debugs a flake here: assertVisible/assertNotVisible on
# a substring deep inside ONE very wide, unwrapped Text node is coarser than
# it looks — Maestro (like most accessibility-tree-based mobile test
# frameworks) determines visibility per ELEMENT, not per glyph, and there are
# open Maestro issues about partially-visible elements being reported as
# fully visible (mobile-dev-inc/maestro #1275, #2411). The filler is made
# very wide (~220 chars, comfortably wider than any emulator screen at this
# monospace font size) specifically to keep the marker's actual rendered
# position off past the right edge at rest. Screenshots are taken at every
# step so a flake here can be diagnosed visually, not just from the
# assertion's pass/fail.
- launchApp:
clearState: true
- assertVisible:
id: "telemetry-consent-card"
- tapOn:
id: "telemetry-decline-button"
- assertVisible:
text: "No Connection"
- tapOn:
id: "add-connection-button"
- tapOn:
id: "connect-ip-input"
- inputText: "127.0.0.1:4100"
- hideKeyboard
- tapOn:
id: "connect-submit-button"
# 40s margin: see activation-positive.yaml — the connect handshake's own
# fetches are individually capped at 30s (src/lib/sdk.ts REQUEST_TIMEOUT_MS).
- extendedWaitUntil:
visible:
id: "connection-status-dot"
timeout: 40000
- takeScreenshot: diffscroll-S1_connected
# The seeded session must appear on the home tab's own-directory list (no
# SSE, no roots=true — this is the plain GET /session directory-scoped path).
- extendedWaitUntil:
visible:
text: "Wide Diff Session"
timeout: 15000
- tapOn:
id: "session-item-seed-diff"
- extendedWaitUntil:
visible:
id: "chat-message-input"
timeout: 15000
# History loaded via GET /session/seed-diff/message (not SSE) must already
# render both the tool card and the markdown code block.
- assertVisible:
text: "Edit wide_diff_target.ts"
- assertVisible:
text: "Here is a wide code sample"
- takeScreenshot: diffscroll-S2_history_loaded
# --- CodeBlock: the wide fenced code block is already expanded (no tap
# needed — Markdown renders it immediately, unlike the collapsible tool card).
- assertVisible:
id: "code-block-scroll"
- assertNotVisible:
text: "ZZZ_CODE_SCROLL_TARGET_ZZZ"
- takeScreenshot: diffscroll-S3_codeblock_marker_offscreen
- swipe:
from:
id: "code-block-scroll"
direction: LEFT
- assertVisible:
text: "ZZZ_CODE_SCROLL_TARGET_ZZZ"
- takeScreenshot: diffscroll-S4_codeblock_marker_revealed
# --- DiffView: lives inside the collapsible tool card's detail section, so
# expand it first.
- tapOn:
text: "Edit wide_diff_target.ts"
- assertVisible:
text: "src/wide_diff_target.ts"
- assertVisible:
id: "diff-view-scroll"
- assertNotVisible:
text: "ZZZ_DIFF_SCROLL_TARGET_ZZZ"
- takeScreenshot: diffscroll-S5_diffview_marker_offscreen
- swipe:
from:
id: "diff-view-scroll"
direction: LEFT
- assertVisible:
text: "ZZZ_DIFF_SCROLL_TARGET_ZZZ"
- takeScreenshot: diffscroll-S6_diffview_marker_revealed

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Runs the Maestro activation E2E flows against the mock opencode servers the # Runs the Maestro activation E2E flows against the mock opencode servers the
# workflow started on the host (ports 4096-4099). Invoked as a single line from # workflow started on the host (ports 4096-4100). Invoked as a single line from
# activation-e2e.yml's emulator-runner `script:` so cd/trap/loop actually work. # activation-e2e.yml's emulator-runner `script:` so cd/trap/loop actually work.
# #
# Networking: `adb reverse` maps each mock port so the emulator's own # Networking: `adb reverse` maps each mock port so the emulator's own
@@ -11,14 +11,14 @@ set -uo pipefail
ROOT="$(pwd)" # capture BEFORE any cd, so diag paths are absolute ROOT="$(pwd)" # capture BEFORE any cd, so diag paths are absolute
APK="android/app/build/outputs/apk/release/app-release.apk" APK="android/app/build/outputs/apk/release/app-release.apk"
FLOWS=(activation-positive activation-negative-401 directory-picker all-sessions variant-picker) FLOWS=(activation-positive activation-negative-401 directory-picker all-sessions variant-picker diff-scroll)
mkdir -p "$ROOT/artifacts/screenshots" "$ROOT/artifacts/diag" mkdir -p "$ROOT/artifacts/screenshots" "$ROOT/artifacts/diag"
echo "== installing APK ==" echo "== installing APK =="
adb install "$APK" adb install "$APK"
echo "== forwarding mock ports into the emulator (adb reverse) ==" echo "== forwarding mock ports into the emulator (adb reverse) =="
for p in 4096 4097 4098 4099; do adb reverse "tcp:$p" "tcp:$p"; done for p in 4096 4097 4098 4099 4100; do adb reverse "tcp:$p" "tcp:$p"; done
adb reverse --list adb reverse --list
# Decisive probe: can the EMULATOR actually reach the host mock via 127.0.0.1? # Decisive probe: can the EMULATOR actually reach the host mock via 127.0.0.1?

View File

@@ -1,4 +1,5 @@
import { View, Text, StyleSheet, Platform, ScrollView } from "react-native" import { View, Text, StyleSheet, Platform, ScrollView } from "react-native"
import { WIDE_CONTENT_SCROLL_CONFIG } from "../../lib/scroll-config"
const mono = Platform.OS === "ios" ? "Menlo" : "monospace" const mono = Platform.OS === "ios" ? "Menlo" : "monospace"
@@ -76,7 +77,7 @@ export function DiffView({ before, after, isDark }: Props) {
return ( return (
<View style={[s.container, isDark && s.containerDark]}> <View style={[s.container, isDark && s.containerDark]}>
<ScrollView horizontal showsHorizontalScrollIndicator> <ScrollView {...WIDE_CONTENT_SCROLL_CONFIG} testID="diff-view-scroll">
<View> <View>
{lines.map((line, idx) => ( {lines.map((line, idx) => (
<View <View

View File

@@ -1,6 +1,7 @@
import { useState } from "react" import { useState } from "react"
import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Platform, ScrollView } from "react-native" import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Platform, ScrollView } from "react-native"
import * as Clipboard from "expo-clipboard" import * as Clipboard from "expo-clipboard"
import { WIDE_CONTENT_SCROLL_CONFIG } from "../../lib/scroll-config"
interface Props { interface Props {
code: string code: string
@@ -27,7 +28,7 @@ export function CodeBlock({ code, language }: Props) {
<Text style={[styles.copyBtn, isDark && styles.copyBtnDark]}>{copied ? "Copied!" : "Copy"}</Text> <Text style={[styles.copyBtn, isDark && styles.copyBtnDark]}>{copied ? "Copied!" : "Copy"}</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<ScrollView horizontal showsHorizontalScrollIndicator contentContainerStyle={styles.codeScroll}> <ScrollView {...WIDE_CONTENT_SCROLL_CONFIG} testID="code-block-scroll" contentContainerStyle={styles.codeScroll}>
<Text style={[styles.code, isDark && styles.codeDark]} selectable> <Text style={[styles.code, isDark && styles.codeDark]} selectable>
{code} {code}
</Text> </Text>

View File

@@ -0,0 +1,45 @@
import { test } from "node:test"
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"
import { fileURLToPath } from "node:url"
import path from "node:path"
// GitHub issue #21 ("QA: verify DiffView + CodeBlock horizontal-scroll
// on-device with a populated diff"). The underlying fixes were:
// - src/components/chat/DiffView.tsx: wrap diff lines in a horizontal
// ScrollView (previously truncated with numberOfLines={1}).
// - src/components/markdown/CodeBlock.tsx: wrap code in a horizontal
// ScrollView (previously long lines wrapped/mangled).
//
// This repo's runtime is React Native, so these components can't be rendered
// with node:test (no react-test-renderer / RN jest preset is set up here —
// see package.json's "test" script, which only globs plain .test.ts files).
// Instead of skipping component-level coverage, this test reads the actual
// .tsx source and asserts on its structure: it's a plain-text/regex check,
// but it directly targets the two markers that would prove a regression —
// (a) the ScrollView wiring, (b) reintroducing line-truncation — so it can't
// pass on a source file where the fix was reverted.
function readComponent(relativePath: string): string {
const dir = path.dirname(fileURLToPath(import.meta.url))
return readFileSync(path.join(dir, relativePath), "utf8")
}
test("DiffView wraps diff lines in the shared horizontal-scroll ScrollView", () => {
const src = readComponent("chat/DiffView.tsx")
assert.match(src, /<ScrollView\s+\{\.\.\.WIDE_CONTENT_SCROLL_CONFIG\}/)
assert.doesNotMatch(src, /numberOfLines/, "DiffView must not truncate diff line text with numberOfLines")
})
test("CodeBlock wraps code in the shared horizontal-scroll ScrollView", () => {
const src = readComponent("markdown/CodeBlock.tsx")
assert.match(src, /<ScrollView\s+\{\.\.\.WIDE_CONTENT_SCROLL_CONFIG\}/)
assert.doesNotMatch(src, /numberOfLines/, "CodeBlock must not truncate code text with numberOfLines")
})
test("DiffView and CodeBlock both source the config from the shared, unit-tested module", () => {
const diffView = readComponent("chat/DiffView.tsx")
const codeBlock = readComponent("markdown/CodeBlock.tsx")
assert.match(diffView, /from ["']\.\.\/\.\.\/lib\/scroll-config["']/)
assert.match(codeBlock, /from ["']\.\.\/\.\.\/lib\/scroll-config["']/)
})

View File

@@ -0,0 +1,26 @@
import { test } from "node:test"
import assert from "node:assert/strict"
import { WIDE_CONTENT_SCROLL_CONFIG } from "./scroll-config.ts"
// GitHub issue #21: DiffView + CodeBlock must render wide content in a
// horizontally-scrollable container (not wrapped, not truncated). Both
// components spread WIDE_CONTENT_SCROLL_CONFIG onto their ScrollView (see
// src/components/chat/DiffView.tsx and src/components/markdown/CodeBlock.tsx)
// so asserting on this object here is asserting on the actual runtime props,
// not a parallel copy that can drift out of sync.
test("wide-content scroll config enables horizontal scrolling", () => {
assert.equal(WIDE_CONTENT_SCROLL_CONFIG.horizontal, true)
})
test("wide-content scroll config shows the horizontal scroll indicator", () => {
// Regression guard: a container that scrolls but hides its indicator is
// easy to mistake for content that simply doesn't overflow. Keep the
// indicator visible so on-device QA (and screenshots) can tell scrollable
// content apart from clipped/truncated content.
assert.equal(WIDE_CONTENT_SCROLL_CONFIG.showsHorizontalScrollIndicator, true)
})
test("wide-content scroll config has no unexpected keys", () => {
assert.deepEqual(Object.keys(WIDE_CONTENT_SCROLL_CONFIG).sort(), ["horizontal", "showsHorizontalScrollIndicator"])
})

18
src/lib/scroll-config.ts Normal file
View File

@@ -0,0 +1,18 @@
// Pure (no React Native imports) horizontal-scroll configuration shared by
// src/components/chat/DiffView.tsx and src/components/markdown/CodeBlock.tsx.
//
// GitHub issue #21: wide diff lines and wide code-block lines must render in
// a horizontally-scrollable container instead of being wrap-broken or
// truncated with `numberOfLines`. Centralizing the actual runtime props in
// one plain object lets that decision be unit-tested with node:test (no React
// Native renderer needed) while both components spread the SAME object onto
// their ScrollView, so the test and the real components can't drift apart.
export interface HorizontalScrollConfig {
horizontal: true
showsHorizontalScrollIndicator: boolean
}
export const WIDE_CONTENT_SCROLL_CONFIG: HorizontalScrollConfig = {
horizontal: true,
showsHorizontalScrollIndicator: true,
}

View File

@@ -68,6 +68,7 @@
// node tests/fixtures/mock-opencode-server.ts --port 4096 // node tests/fixtures/mock-opencode-server.ts --port 4096
// node tests/fixtures/mock-opencode-server.ts --port 4097 --fail-auth // node tests/fixtures/mock-opencode-server.ts --port 4097 --fail-auth
// node tests/fixtures/mock-opencode-server.ts --port 4098 --seed-sessions // node tests/fixtures/mock-opencode-server.ts --port 4098 --seed-sessions
// node tests/fixtures/mock-opencode-server.ts --port 4100 --seed-diff
import http from "node:http" import http from "node:http"
import { randomUUID } from "node:crypto" import { randomUUID } from "node:crypto"
@@ -88,6 +89,20 @@ export interface MockServerOptions {
* open regression for GitHub issues #46/#48). * open regression for GitHub issues #46/#48).
*/ */
seedSessions?: boolean seedSessions?: boolean
/**
* Pre-populate a session (id "seed-diff", in DEFAULT_DIRECTORY so it shows
* up on the normal home-tab session list) with ONE assistant message that
* already contains a wide `edit` tool part (renders via DiffView) and a
* wide fenced code block in its text (renders via CodeBlock) — used by
* .maestro/flows/diff-scroll.yaml (GitHub issue #21) to prove both
* components' horizontal ScrollView actually scrolls on-device.
*
* Deliberately seeded as pre-existing history, fetched via GET
* /session/:id/message, NOT delivered over SSE — the positive-flow SSE
* render bug (issue #90) is being fixed separately, and this flow must not
* depend on it landing first.
*/
seedDiff?: boolean
} }
interface StoredSession { interface StoredSession {
@@ -106,6 +121,16 @@ interface StoredPart {
messageID: string messageID: string
type: string type: string
text?: string text?: string
// Tool part fields (type: "tool") — mirrors src/lib/sdk.ts's Part interface,
// needed to seed a populated `edit` tool call for --seed-diff.
tool?: string
callID?: string
state?: {
status: "pending" | "running" | "completed" | "error"
input?: unknown
output?: unknown
title?: string
}
} }
interface StoredMessageInfo { interface StoredMessageInfo {
@@ -166,13 +191,97 @@ export const FAKE_SERVER_PROJECTS = [
}, },
] ]
// Exported so .maestro/flows/diff-scroll.yaml's assertions and this file's
// own seeding logic share one source of truth for what's "off-screen until
// you scroll" in the seeded diff/code content.
export const SEED_DIFF_SESSION_ID = "seed-diff"
export const SEED_DIFF_TITLE = "Wide Diff Session"
export const SEED_DIFF_TOOL_TITLE = "Edit wide_diff_target.ts"
export const SEED_DIFF_LINE_MARKER = "ZZZ_DIFF_SCROLL_TARGET_ZZZ"
export const SEED_CODE_LINE_MARKER = "ZZZ_CODE_SCROLL_TARGET_ZZZ"
// Long, space-free filler so the RN <Text> can't word-wrap it — it just
// overflows its parent, which is exactly what the horizontal ScrollView (the
// fix under test) exists to make scrollable instead of clipped/truncated.
const WIDE_FILLER = "x".repeat(220)
export function createMockOpencodeServer(opts: MockServerOptions) { export function createMockOpencodeServer(opts: MockServerOptions) {
const { port, failAuth = false, replyText = DEFAULT_REPLY_TEXT, replyDelayMs = 300, seedSessions = false } = opts const {
port,
failAuth = false,
replyText = DEFAULT_REPLY_TEXT,
replyDelayMs = 300,
seedSessions = false,
seedDiff = false,
} = opts
const sessions = new Map<string, StoredSession>() const sessions = new Map<string, StoredSession>()
const messagesBySession = new Map<string, StoredMessage[]>() const messagesBySession = new Map<string, StoredMessage[]>()
const sseClients = new Set<http.ServerResponse>() const sseClients = new Set<http.ServerResponse>()
if (seedDiff) {
const now = Date.now()
const session: StoredSession = {
id: SEED_DIFF_SESSION_ID,
slug: "seed-diff",
projectID: "mock-project",
directory: DEFAULT_DIRECTORY,
title: SEED_DIFF_TITLE,
version: "0.0.0-mock",
time: { created: now - 30_000, updated: now - 20_000 },
}
sessions.set(session.id, session)
const messageID = "seed-diff-msg"
const info: StoredMessageInfo = {
id: messageID,
sessionID: session.id,
role: "assistant",
time: { created: now - 25_000, completed: now - 20_000 },
modelID: "mock-model",
providerID: "mock",
}
// Populated `edit` tool call: renders via ToolCallCard -> EditDetail ->
// DiffView (src/components/chat/DiffView.tsx) once the card is expanded.
// One short unchanged line + one wide added line, so DiffView's diff has
// both a context row and an "add" row whose text overflows the ScrollView.
const toolPart: StoredPart = {
id: "seed-diff-tool",
sessionID: session.id,
messageID,
type: "tool",
tool: "edit",
callID: "seed-diff-call-1",
state: {
status: "completed",
title: SEED_DIFF_TOOL_TITLE,
input: {
filePath: "src/wide_diff_target.ts",
oldString: "const shortLine = 1",
newString: `const shortLine = 1\nconst wideLine = "${WIDE_FILLER}${SEED_DIFF_LINE_MARKER}"`,
},
output: "applied",
},
}
// Wide fenced code block in the reply text: renders via
// Markdown -> CustomRenderer.code -> CodeBlock
// (src/components/markdown/CodeBlock.tsx).
const textPart: StoredPart = {
id: "seed-diff-text",
sessionID: session.id,
messageID,
type: "text",
text:
"Here is a wide code sample:\n\n```typescript\n" +
`const wideCodeLine = "${WIDE_FILLER}${SEED_CODE_LINE_MARKER}"\n` +
"```\n",
}
messagesBySession.set(session.id, [{ info, parts: [toolPart, textPart] }])
}
if (seedSessions) { if (seedSessions) {
const now = Date.now() const now = Date.now()
const seedDefault: StoredSession = { const seedDefault: StoredSession = {
@@ -539,12 +648,13 @@ export function createMockOpencodeServer(opts: MockServerOptions) {
} }
} }
function parseArgs(argv: string[]): { port: number; failAuth: boolean; seedSessions: boolean } { function parseArgs(argv: string[]): { port: number; failAuth: boolean; seedSessions: boolean; seedDiff: boolean } {
const opts = { port: 4096, failAuth: false, seedSessions: false } const opts = { port: 4096, failAuth: false, seedSessions: false, seedDiff: false }
for (let i = 0; i < argv.length; i++) { for (let i = 0; i < argv.length; i++) {
if (argv[i] === "--port") opts.port = Number(argv[++i]) if (argv[i] === "--port") opts.port = Number(argv[++i])
else if (argv[i] === "--fail-auth") opts.failAuth = true else if (argv[i] === "--fail-auth") opts.failAuth = true
else if (argv[i] === "--seed-sessions") opts.seedSessions = true else if (argv[i] === "--seed-sessions") opts.seedSessions = true
else if (argv[i] === "--seed-diff") opts.seedDiff = true
} }
return opts return opts
} }
@@ -557,7 +667,7 @@ if (invokedDirectly) {
const mock = createMockOpencodeServer(opts) const mock = createMockOpencodeServer(opts)
mock.listen().then(() => { mock.listen().then(() => {
console.log( console.log(
`[mock-opencode-server] listening on ${mock.url} (failAuth=${opts.failAuth}, seedSessions=${opts.seedSessions})`, `[mock-opencode-server] listening on ${mock.url} (failAuth=${opts.failAuth}, seedSessions=${opts.seedSessions}, seedDiff=${opts.seedDiff})`,
) )
}) })
const shutdown = () => { const shutdown = () => {