@tui-sandbox/library 9.5.0 → 9.6.0

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/browser/assets/{index-06LPWRXA.js → index-Dm7lp9YI.js} +6 -6
  3. package/dist/browser/index.html +1 -1
  4. package/dist/src/browser/neovim-client.d.ts +2 -1
  5. package/dist/src/browser/neovim-client.js +3 -0
  6. package/dist/src/browser/neovim-client.js.map +1 -1
  7. package/dist/src/client/neovim-terminal-client.d.ts +2 -1
  8. package/dist/src/client/neovim-terminal-client.js +9 -0
  9. package/dist/src/client/neovim-terminal-client.js.map +1 -1
  10. package/dist/src/server/cypress-support/contents.js +20 -1
  11. package/dist/src/server/cypress-support/contents.js.map +1 -1
  12. package/dist/src/server/dirtree/index.test.js +5 -0
  13. package/dist/src/server/dirtree/index.test.js.map +1 -1
  14. package/dist/src/server/neovim/index.d.ts +5 -0
  15. package/dist/src/server/neovim/index.js +33 -0
  16. package/dist/src/server/neovim/index.js.map +1 -1
  17. package/dist/src/server/server.d.ts +34 -0
  18. package/dist/src/server/server.js +17 -2
  19. package/dist/src/server/server.js.map +1 -1
  20. package/dist/src/server/utilities/timeout.js +1 -1
  21. package/dist/src/server/utilities/timeout.js.map +1 -1
  22. package/dist/src/server/utilities/timeoutable.d.ts +1 -0
  23. package/dist/src/server/utilities/timeoutable.js +18 -0
  24. package/dist/src/server/utilities/timeoutable.js.map +1 -0
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +9 -9
  27. package/src/browser/neovim-client.ts +5 -1
  28. package/src/client/neovim-terminal-client.ts +10 -1
  29. package/src/server/cypress-support/contents.ts +20 -1
  30. package/src/server/dirtree/index.test.ts +5 -0
  31. package/src/server/neovim/index.ts +56 -0
  32. package/src/server/server.ts +22 -2
  33. package/src/server/utilities/timeout.ts +1 -1
  34. package/src/server/utilities/timeoutable.ts +19 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tui-sandbox/library",
3
- "version": "9.5.0",
3
+ "version": "9.6.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,21 +8,21 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@catppuccin/palette": "1.7.1",
11
- "@trpc/client": "11.0.0-rc.798",
12
- "@trpc/server": "11.0.0-rc.798",
11
+ "@trpc/client": "11.0.0-rc.824",
12
+ "@trpc/server": "11.0.0-rc.824",
13
13
  "@xterm/addon-attach": "0.11.0",
14
14
  "@xterm/addon-fit": "0.10.0",
15
15
  "@xterm/xterm": "5.5.0",
16
16
  "command-exists": "1.2.9",
17
- "core-js": "3.40.0",
17
+ "core-js": "3.41.0",
18
18
  "cors": "2.8.5",
19
19
  "dree": "5.1.5",
20
20
  "express": "4.21.2",
21
21
  "neovim": "5.3.0",
22
22
  "node-pty": "1.0.0",
23
- "prettier": "3.5.1",
23
+ "prettier": "3.5.3",
24
24
  "tsx": "4.19.3",
25
- "type-fest": "4.35.0",
25
+ "type-fest": "4.37.0",
26
26
  "winston": "3.17.0",
27
27
  "zod": "3.24.2"
28
28
  },
@@ -31,10 +31,10 @@
31
31
  "@types/command-exists": "1.2.3",
32
32
  "@types/cors": "2.8.17",
33
33
  "@types/express": "5.0.0",
34
- "@types/node": "22.13.5",
34
+ "@types/node": "22.13.9",
35
35
  "nodemon": "3.1.9",
36
- "vite": "6.1.1",
37
- "vitest": "3.0.6"
36
+ "vite": "6.2.0",
37
+ "vitest": "3.0.7"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "cypress": "^13 || ^14",
@@ -1,7 +1,7 @@
1
1
  import { TerminalClient as NeovimTerminalClient } from "../client/index.js"
2
2
  import { TerminalTerminalClient } from "../client/terminal-terminal-client.js"
3
3
  import type { BlockingCommandClientInput } from "../server/blockingCommandInputSchema.js"
4
- import type { ExCommandClientInput, LuaCodeClientInput } from "../server/server.js"
4
+ import type { ExCommandClientInput, LuaCodeClientInput, PollLuaCodeClientInput } from "../server/server.js"
5
5
  import type { StartTerminalGenericArguments } from "../server/terminal/TerminalTestApplication.js"
6
6
  import type {
7
7
  BlockingShellCommandOutput,
@@ -24,6 +24,7 @@ const terminalClient = new Lazy(() => new TerminalTerminalClient(app))
24
24
  export type GenericNeovimBrowserApi = {
25
25
  runBlockingShellCommand(input: BlockingCommandClientInput): Promise<BlockingShellCommandOutput>
26
26
  runLuaCode(input: LuaCodeClientInput): Promise<RunLuaCodeOutput>
27
+ waitForLuaCode(input: PollLuaCodeClientInput): Promise<RunLuaCodeOutput>
27
28
  runExCommand(input: ExCommandClientInput): Promise<RunExCommandOutput>
28
29
  dir: TestDirectory
29
30
  }
@@ -44,6 +45,9 @@ window.startNeovim = async function (startArgs?: StartNeovimGenericArguments): P
44
45
  runLuaCode(input) {
45
46
  return neovim.runLuaCode(input)
46
47
  },
48
+ waitForLuaCode(input) {
49
+ return neovim.waitForLuaCode(input)
50
+ },
47
51
  runExCommand(input) {
48
52
  return neovim.runExCommand(input)
49
53
  },
@@ -2,7 +2,7 @@ import { createTRPCClient, httpBatchLink, splitLink, unstable_httpSubscriptionLi
2
2
  import type { Terminal } from "@xterm/xterm"
3
3
  import "@xterm/xterm/css/xterm.css"
4
4
  import type { BlockingCommandClientInput } from "../server/blockingCommandInputSchema.js"
5
- import type { AppRouter, ExCommandClientInput, LuaCodeClientInput } from "../server/server.js"
5
+ import type { AppRouter, ExCommandClientInput, LuaCodeClientInput, PollLuaCodeClientInput } from "../server/server.js"
6
6
  import type {
7
7
  BlockingShellCommandOutput,
8
8
  RunExCommandOutput,
@@ -102,6 +102,15 @@ export class NeovimTerminalClient {
102
102
  return this.trpc.neovim.runLuaCode.mutate({ ...input, tabId: this.tabId })
103
103
  }
104
104
 
105
+ public async waitForLuaCode(input: PollLuaCodeClientInput): Promise<RunLuaCodeOutput> {
106
+ await this.ready
107
+ try {
108
+ return await this.trpc.neovim.waitForLuaCode.mutate({ ...input, tabId: this.tabId })
109
+ } catch (e) {
110
+ throw e
111
+ }
112
+ }
113
+
105
114
  public async runExCommand(input: ExCommandClientInput): Promise<RunExCommandOutput> {
106
115
  await this.ready
107
116
  return this.trpc.neovim.runExCommand.mutate({ ...input, tabId: this.tabId })
@@ -14,7 +14,11 @@ import type {
14
14
  GenericNeovimBrowserApi,
15
15
  GenericTerminalBrowserApi,
16
16
  } from "@tui-sandbox/library/dist/src/browser/neovim-client"
17
- import type { ExCommandClientInput, LuaCodeClientInput } from "@tui-sandbox/library/dist/src/server/server"
17
+ import type {
18
+ ExCommandClientInput,
19
+ LuaCodeClientInput,
20
+ PollLuaCodeClientInput,
21
+ } from "@tui-sandbox/library/dist/src/server/server"
18
22
  import type {
19
23
  BlockingShellCommandOutput,
20
24
  RunExCommandOutput,
@@ -54,6 +58,16 @@ export type NeovimContext = {
54
58
  * finish before returning. Requires neovim to be running. */
55
59
  runLuaCode(input: LuaCodeClientInput): Cypress.Chainable<RunLuaCodeOutput>
56
60
 
61
+ /**
62
+ * Like runLuaCode, but waits until the given code (maybe using lua's return
63
+ * assert()) does not raise an error, and returns the first successful result.
64
+ *
65
+ * Useful for waiting until Neovim's internal state has changed in a way that
66
+ * means the test can continue executing. This can avoid timing issues that are
67
+ * otherwise hard to catch.
68
+ */
69
+ waitForLuaCode(input: PollLuaCodeClientInput): Cypress.Chainable<RunLuaCodeOutput>
70
+
57
71
  /** Run an ex command in neovim.
58
72
  * @example "echo expand('%:.')" current file, relative to the cwd
59
73
  */
@@ -85,6 +99,7 @@ Cypress.Commands.add("startNeovim", (startArguments?: MyStartNeovimServerArgumen
85
99
  nvim_runBlockingShellCommand: underlyingNeovim.runBlockingShellCommand,
86
100
  nvim_runExCommand: underlyingNeovim.runExCommand,
87
101
  nvim_runLuaCode: underlyingNeovim.runLuaCode,
102
+ nvim_waitForLuaCode: underlyingNeovim.waitForLuaCode,
88
103
  })
89
104
 
90
105
  const api: NeovimContext = {
@@ -97,6 +112,9 @@ Cypress.Commands.add("startNeovim", (startArguments?: MyStartNeovimServerArgumen
97
112
  runLuaCode(input) {
98
113
  return cy.nvim_runLuaCode(input)
99
114
  },
115
+ waitForLuaCode(input) {
116
+ return cy.nvim_waitForLuaCode(input)
117
+ },
100
118
  typeIntoTerminal(text, options) {
101
119
  cy.typeIntoTerminal(text, options)
102
120
  },
@@ -164,6 +182,7 @@ declare global {
164
182
  nvim_runBlockingShellCommand(input: MyBlockingCommandClientInput): Chainable<BlockingShellCommandOutput>
165
183
 
166
184
  nvim_runLuaCode(input: LuaCodeClientInput): Chainable<RunLuaCodeOutput>
185
+ nvim_waitForLuaCode(input: PollLuaCodeClientInput): Chainable<RunLuaCodeOutput>
167
186
 
168
187
  /** Run an ex command in neovim.
169
188
  * @example "echo expand('%:.')" current file, relative to the cwd
@@ -63,6 +63,10 @@ describe("dirtree", () => {
63
63
  name: z.literal("add_command_to_count_open_buffers.lua"),
64
64
  type: z.literal("file"),
65
65
  }),
66
+ "add_command_to_update_buffer_after_timeout.lua": z.object({
67
+ name: z.literal("add_command_to_update_buffer_after_timeout.lua"),
68
+ type: z.literal("file"),
69
+ }),
66
70
  "don't_crash_when_modification_contains_unescaped_characters\\".lua": z.object({
67
71
  name: z.literal("don't_crash_when_modification_contains_unescaped_characters\\".lua"),
68
72
  type: z.literal("file"),
@@ -135,6 +139,7 @@ describe("dirtree", () => {
135
139
  ".config/nvim",
136
140
  ".config",
137
141
  "config-modifications/add_command_to_count_open_buffers.lua",
142
+ "config-modifications/add_command_to_update_buffer_after_timeout.lua",
138
143
  "config-modifications/don't_crash_when_modification_contains_unescaped_characters\\".lua",
139
144
  "config-modifications",
140
145
  "dir with spaces/file1.txt",
@@ -16,6 +16,7 @@ import type { DirectoriesConfig } from "../updateTestdirectorySchemaFile.js"
16
16
  import { convertEventEmitterToAsyncGenerator } from "../utilities/generator.js"
17
17
  import { Lazy } from "../utilities/Lazy.js"
18
18
  import type { TabId } from "../utilities/tabId.js"
19
+ import { timeout } from "../utilities/timeout.js"
19
20
  import type { StdoutOrStderrMessage, TerminalDimensions } from "./NeovimApplication.js"
20
21
  import { NeovimApplication } from "./NeovimApplication.js"
21
22
  import { prepareNewTestDirectory } from "./prepareNewTestDirectory.js"
@@ -154,6 +155,61 @@ export async function runLuaCode(options: LuaCodeInput): Promise<RunLuaCodeOutpu
154
155
  }
155
156
  }
156
157
 
158
+ export type PollLuaCodeInput = {
159
+ luaAssertion: string
160
+ tabId: TabId
161
+ }
162
+
163
+ export async function waitForLuaCode(
164
+ options: PollLuaCodeInput,
165
+ signal: AbortSignal | undefined
166
+ ): Promise<RunLuaCodeOutput> {
167
+ const neovim = neovims.get(options.tabId.tabId)
168
+ assert(
169
+ neovim !== undefined,
170
+ `Neovim instance for clientId not found - cannot pollLuaCode. Maybe neovim's not started yet?`
171
+ )
172
+ assert(
173
+ neovim.application,
174
+ `Neovim application not found for client id ${options.tabId.tabId}. Maybe it's not started yet?`
175
+ )
176
+
177
+ const api = await neovim.state?.client.get()
178
+ if (!api) {
179
+ throw new Error(`Neovim API not available for client id ${options.tabId.tabId}. Maybe it's not started yet?`)
180
+ }
181
+
182
+ console.log(`Neovim ${neovim.application.processId()} polling Lua code: ${options.luaAssertion}`)
183
+
184
+ let running: boolean = true
185
+ signal?.addEventListener("abort", () => {
186
+ console.log(`Polling Lua code: '${options.luaAssertion}' was aborted via signal`)
187
+ running = false
188
+ })
189
+
190
+ const maxIterations = 100
191
+ for (let iteration = 1; iteration <= maxIterations; iteration++) {
192
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
193
+ if (!running) {
194
+ throw new Error(`Polling Lua code: '${options.luaAssertion}' was aborted after ${iteration} iterations`)
195
+ }
196
+
197
+ try {
198
+ const value = await api.lua(options.luaAssertion)
199
+ console.log(`Lua code assertion passed: ${options.luaAssertion} (iteration ${iteration})`)
200
+
201
+ return { value }
202
+ } catch (e) {
203
+ console.error(`Caught error in iteration ${iteration}:`, e)
204
+ await timeout(100)
205
+ }
206
+ }
207
+
208
+ throw new Error(
209
+ `Polling Lua code: '${options.luaAssertion}' always raised an error after ${maxIterations} iterations`
210
+ )
211
+ }
212
+
157
213
  export async function runExCommand(options: ExCommandInput): Promise<RunExCommandOutput> {
158
214
  const neovim = neovims.get(options.tabId.tabId)
159
215
  assert(
@@ -9,11 +9,19 @@ import * as terminal from "./terminal/index.js"
9
9
  import { TestServer } from "./TestServer.js"
10
10
  import type { DirectoriesConfig, TestServerConfig } from "./updateTestdirectorySchemaFile.js"
11
11
  import { tabIdSchema } from "./utilities/tabId.js"
12
+ import { timeoutable } from "./utilities/timeoutable.js"
12
13
 
13
14
  const luaCodeInputSchema = z.object({ tabId: tabIdSchema, luaCode: z.string() })
14
15
  export type LuaCodeClientInput = Except<LuaCodeInput, "tabId">
15
16
  export type LuaCodeInput = z.infer<typeof luaCodeInputSchema>
16
17
 
18
+ const pollLuaCodeInputSchema = z.object({
19
+ tabId: tabIdSchema,
20
+ luaAssertion: z.string(),
21
+ timeoutMs: z.number().optional().default(10_000),
22
+ })
23
+ export type PollLuaCodeClientInput = Except<z.input<typeof pollLuaCodeInputSchema>, "tabId">
24
+
17
25
  const exCommandInputSchema = z.object({
18
26
  tabId: tabIdSchema,
19
27
  command: z.string(),
@@ -104,11 +112,23 @@ export async function createAppRouter(config: DirectoriesConfig) {
104
112
  }),
105
113
 
106
114
  runLuaCode: trpc.procedure.input(luaCodeInputSchema).mutation(options => {
107
- return neovim.runLuaCode(options.input)
115
+ return timeoutable(10_000, neovim.runLuaCode(options.input))
116
+ }),
117
+
118
+ waitForLuaCode: trpc.procedure.input(pollLuaCodeInputSchema).mutation(async options => {
119
+ try {
120
+ const result = await timeoutable(
121
+ options.input.timeoutMs,
122
+ neovim.waitForLuaCode(options.input, options.signal)
123
+ )
124
+ return result
125
+ } catch (e) {
126
+ throw e
127
+ }
108
128
  }),
109
129
 
110
130
  runExCommand: trpc.procedure.input(exCommandInputSchema).mutation(options => {
111
- return neovim.runExCommand(options.input)
131
+ return timeoutable(10_000, neovim.runExCommand(options.input))
112
132
  }),
113
133
  }),
114
134
  })
@@ -1,3 +1,3 @@
1
1
  export function timeout(ms: number): Promise<unknown> {
2
- return new Promise((_, reject) => setTimeout(reject, ms))
2
+ return new Promise(resolve => setTimeout(resolve, ms))
3
3
  }
@@ -0,0 +1,19 @@
1
+ import assert from "node:assert"
2
+
3
+ export async function timeoutable<T>(timeoutMs: number, promise: Promise<T>): Promise<T> {
4
+ let timeoutHandle: NodeJS.Timeout
5
+
6
+ const timeoutPromise = new Promise<T>((_, reject) => {
7
+ timeoutHandle = setTimeout(() => {
8
+ reject(new Error(`Operation timed out after ${timeoutMs}ms`))
9
+ }, timeoutMs)
10
+ })
11
+
12
+ try {
13
+ return await Promise.race([promise, timeoutPromise])
14
+ } finally {
15
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
16
+ assert(timeoutHandle!)
17
+ clearTimeout(timeoutHandle)
18
+ }
19
+ }