thread.test.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { describe, expect, test } from "bun:test"
  2. import { Effect } from "effect"
  3. import fs from "fs/promises"
  4. import path from "path"
  5. import yargs from "yargs"
  6. import { tmpdir } from "../../fixture/fixture"
  7. import { TuiThreadCommand, resolveThreadDirectory } from "../../../src/cli/cmd/tui"
  8. import { cliIt } from "../../lib/cli-process"
  9. describe("tui thread", () => {
  10. test("loads the TUI integration lazily", async () => {
  11. const source = await Bun.file(new URL("../../../src/cli/cmd/tui.ts", import.meta.url)).text()
  12. expect(source).toContain('await import("../tui/layer")')
  13. expect(source).toMatch(/await import\(["']@\/plugin\/tui\/runtime["']\)/)
  14. expect(source).not.toContain('import("./app")')
  15. })
  16. test("forwards the CLI environment to the TUI worker", async () => {
  17. const source = await Bun.file(new URL("../../../src/cli/cmd/tui.ts", import.meta.url)).text()
  18. expect(source).toMatch(/new Worker\(file, \{\s*env: Object\.fromEntries\(\s*Object\.entries\(process\.env\)/)
  19. })
  20. async function check(project?: string) {
  21. await using tmp = await tmpdir({ git: true })
  22. const link = path.join(path.dirname(tmp.path), path.basename(tmp.path) + "-link")
  23. const type = process.platform === "win32" ? "junction" : "dir"
  24. try {
  25. await fs.symlink(tmp.path, link, type)
  26. expect(resolveThreadDirectory(project, link, tmp.path)).toBe(tmp.path)
  27. } finally {
  28. await fs.rm(link, { recursive: true, force: true }).catch(() => undefined)
  29. }
  30. }
  31. test("uses the real cwd when PWD points at a symlink", async () => {
  32. await check()
  33. })
  34. test("uses the real cwd after resolving a relative project from PWD", async () => {
  35. await check(".")
  36. })
  37. test("resolves a relative mini project from PWD when cwd differs", async () => {
  38. await using pwd = await tmpdir({ git: true })
  39. await using cwd = await tmpdir({ git: true })
  40. expect(resolveThreadDirectory(".", pwd.path, cwd.path)).toBe(pwd.path)
  41. expect(resolveThreadDirectory(undefined, pwd.path, cwd.path)).toBe(cwd.path)
  42. })
  43. test("parses supported --no-replay forms", async () => {
  44. for (const option of ["--no-replay", "--no-replay=true", "--noReplay"]) {
  45. const args = await yargs([])
  46. .command({ ...TuiThreadCommand, handler: () => {} })
  47. .exitProcess(false)
  48. .parse(["--mini", option, "--replay-limit", "10"])
  49. expect(args.replay === false || args.noReplay === true).toBe(true)
  50. expect(args.replayLimit).toBe(10)
  51. }
  52. })
  53. test("preserves boolean negation for existing options", async () => {
  54. const args = await yargs([])
  55. .command({ ...TuiThreadCommand, handler: () => {} })
  56. .exitProcess(false)
  57. .parse(["--mdns", "--no-mdns"])
  58. expect(args.mdns).toBe(false)
  59. })
  60. cliIt.live("rejects mini-only options without --mini", ({ kirincode }) =>
  61. Effect.gen(function* () {
  62. const result = yield* kirincode.spawn(["--replay-limit", "10"])
  63. opencode.expectExit(result, 1)
  64. expect(result.stderr).toContain("--replay-limit requires --mini")
  65. }),
  66. )
  67. cliIt.live("routes attached sessions to mini mode", ({ kirincode }) =>
  68. Effect.gen(function* () {
  69. const result = yield* kirincode.spawn(["attach", "http://127.0.0.1:1", "--mini"])
  70. opencode.expectExit(result, 1)
  71. expect(result.stderr).toContain("--mini requires a TTY stdout")
  72. }),
  73. )
  74. cliIt.live("rejects network options in mini mode", ({ kirincode }) =>
  75. Effect.gen(function* () {
  76. const result = yield* kirincode.spawn(["--mini", "--port", "4096"])
  77. opencode.expectExit(result, 1)
  78. expect(result.stderr).toContain("--port cannot be used with --mini")
  79. }),
  80. )
  81. })