@tui-sandbox/library 12.3.2 → 12.4.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.
- package/dist/browser/assets/{index-DZMdP7t7.js → index-CBAsIfwR.js} +4 -4
- package/dist/browser/index.html +1 -1
- package/dist/src/client/drawTextBox.d.ts +2 -0
- package/dist/src/client/drawTextBox.js +17 -0
- package/dist/src/client/drawTextBox.js.map +1 -0
- package/dist/src/client/drawTextBox.test.d.ts +1 -0
- package/dist/src/client/drawTextBox.test.js +32 -0
- package/dist/src/client/drawTextBox.test.js.map +1 -0
- package/dist/src/client/index.d.ts +1 -0
- package/dist/src/client/index.js +1 -0
- package/dist/src/client/index.js.map +1 -1
- package/dist/src/server/cypress-support/contents.js +5 -271
- package/dist/src/server/cypress-support/contents.js.map +1 -1
- package/dist/src/server/dirtree/index.js +2 -2
- package/dist/src/server/dirtree/index.js.map +1 -1
- package/dist/src/server/utilities/DisposableSingleApplication.test.js +1 -1
- package/dist/src/server/utilities/DisposableSingleApplication.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -9
- package/src/server/cypress-support/tui-sandbox-template.ts +344 -0
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tui-sandbox/library",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.4.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/mikavilpas/tui-sandbox"
|
|
7
7
|
},
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"type": "module",
|
|
10
|
+
"imports": {
|
|
11
|
+
"#tui-sandbox-template": "./src/server/cypress-support/tui-sandbox-template.ts"
|
|
12
|
+
},
|
|
10
13
|
"exports": {
|
|
11
14
|
".": {
|
|
12
15
|
"types": "./dist/src/client/index.d.ts",
|
|
@@ -33,12 +36,13 @@
|
|
|
33
36
|
"tui": "dist/src/scripts/tui.js"
|
|
34
37
|
},
|
|
35
38
|
"files": [
|
|
36
|
-
"dist"
|
|
39
|
+
"dist",
|
|
40
|
+
"src/server/cypress-support/tui-sandbox-template.ts"
|
|
37
41
|
],
|
|
38
42
|
"dependencies": {
|
|
39
|
-
"@catppuccin/palette": "1.
|
|
40
|
-
"@trpc/client": "11.
|
|
41
|
-
"@trpc/server": "11.
|
|
43
|
+
"@catppuccin/palette": "1.8.0",
|
|
44
|
+
"@trpc/client": "11.16.0",
|
|
45
|
+
"@trpc/server": "11.16.0",
|
|
42
46
|
"@xterm/addon-clipboard": "0.2.0",
|
|
43
47
|
"@xterm/addon-fit": "0.11.0",
|
|
44
48
|
"@xterm/addon-unicode11": "0.9.0",
|
|
@@ -60,11 +64,11 @@
|
|
|
60
64
|
"@types/cors": "2.8.19",
|
|
61
65
|
"@types/express": "5.0.6",
|
|
62
66
|
"@types/node": "24.12.0",
|
|
63
|
-
"cypress": "15.
|
|
67
|
+
"cypress": "15.13.0",
|
|
64
68
|
"nodemon": "3.1.14",
|
|
65
|
-
"type-fest": "5.
|
|
66
|
-
"vite": "8.0.
|
|
67
|
-
"vitest": "4.
|
|
69
|
+
"type-fest": "5.5.0",
|
|
70
|
+
"vite": "8.0.3",
|
|
71
|
+
"vitest": "4.1.2"
|
|
68
72
|
},
|
|
69
73
|
"peerDependencies": {
|
|
70
74
|
"cypress": "^13 || ^14 || ^15",
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
//
|
|
3
|
+
// This file is autogenerated by tui-sandbox. Do not edit it directly.
|
|
4
|
+
//
|
|
5
|
+
import type {
|
|
6
|
+
BrowserTerminalSettings,
|
|
7
|
+
GenericNeovimBrowserApi,
|
|
8
|
+
GenericTerminalBrowserApi,
|
|
9
|
+
} from "@tui-sandbox/library/browser/neovim-client.js"
|
|
10
|
+
import type { MyNeovimConfigModification } from "@tui-sandbox/library/client"
|
|
11
|
+
import { drawTextBox } from "@tui-sandbox/library/client"
|
|
12
|
+
import type {
|
|
13
|
+
AllKeys,
|
|
14
|
+
BlockingCommandClientInput,
|
|
15
|
+
BlockingShellCommandOutput,
|
|
16
|
+
ExCommandClientInput,
|
|
17
|
+
LuaCodeClientInput,
|
|
18
|
+
PollLuaCodeClientInput,
|
|
19
|
+
RunExCommandOutput,
|
|
20
|
+
RunLuaCodeOutput,
|
|
21
|
+
RunLuaFileClientInput,
|
|
22
|
+
StartNeovimGenericArguments,
|
|
23
|
+
StartTerminalGenericArguments,
|
|
24
|
+
TestDirectory,
|
|
25
|
+
} from "@tui-sandbox/library/server"
|
|
26
|
+
import type { OverrideProperties } from "type-fest"
|
|
27
|
+
import type { MyNeovimAppName, MyTestDirectory, MyTestDirectoryFile } from "../../MyTestDirectory"
|
|
28
|
+
|
|
29
|
+
export type TerminalTestApplicationContext = {
|
|
30
|
+
/** Types text into the terminal, making the terminal application receive the
|
|
31
|
+
* keystrokes as input. Requires the application to be running. */
|
|
32
|
+
typeIntoTerminal(text: string, options?: Partial<Cypress.TypeOptions>): void
|
|
33
|
+
|
|
34
|
+
/** Runs a shell command in a blocking manner, waiting for the command to
|
|
35
|
+
* finish before returning. Requires the terminal to be running. */
|
|
36
|
+
runBlockingShellCommand(input: MyBlockingCommandClientInput): Cypress.Chainable<BlockingShellCommandOutput>
|
|
37
|
+
|
|
38
|
+
/** The test directory, providing type-safe access to its file and directory structure */
|
|
39
|
+
dir: TestDirectory<MyTestDirectory>
|
|
40
|
+
|
|
41
|
+
/** Access to the clipboard of the terminal */
|
|
42
|
+
clipboard: {
|
|
43
|
+
system(): Cypress.Chainable<string>
|
|
44
|
+
primary(): Cypress.Chainable<string>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
title: {
|
|
48
|
+
current(): Cypress.Chainable<string>
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The api that can be used in tests after a Neovim instance has been started. */
|
|
53
|
+
export type NeovimContext = {
|
|
54
|
+
/** Types text into the terminal, making the terminal application receive
|
|
55
|
+
* the keystrokes as input. Requires neovim to be running. */
|
|
56
|
+
typeIntoTerminal(text: string, options?: Partial<Cypress.TypeOptions>): void
|
|
57
|
+
|
|
58
|
+
/** Runs a shell command in a blocking manner, waiting for the command to
|
|
59
|
+
* finish before returning. Requires neovim to be running. */
|
|
60
|
+
runBlockingShellCommand(input: MyBlockingCommandClientInput): Cypress.Chainable<BlockingShellCommandOutput>
|
|
61
|
+
|
|
62
|
+
/** Runs a shell command in a blocking manner, waiting for the command to
|
|
63
|
+
* finish before returning. Requires neovim to be running. */
|
|
64
|
+
runLuaCode(input: LuaCodeClientInput): Cypress.Chainable<RunLuaCodeOutput>
|
|
65
|
+
|
|
66
|
+
/** Runs a Lua file in neovim after it has started. Can be used to keep
|
|
67
|
+
* complex lua logic in a separate file, still being able to run it after
|
|
68
|
+
* startup. This way additional tools like lua LSP servers, linters, etc. can
|
|
69
|
+
* be used to ensure the code is correct.
|
|
70
|
+
*/
|
|
71
|
+
doFile(input: MyRunLuaFileClientInput): Cypress.Chainable<RunExCommandOutput>
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Like runLuaCode, but waits until the given code (maybe using lua's return
|
|
75
|
+
* assert()) does not raise an error, and returns the first successful result.
|
|
76
|
+
*
|
|
77
|
+
* Useful for waiting until Neovim's internal state has changed in a way that
|
|
78
|
+
* means the test can continue executing. This can avoid timing issues that are
|
|
79
|
+
* otherwise hard to catch.
|
|
80
|
+
*/
|
|
81
|
+
waitForLuaCode(input: PollLuaCodeClientInput): Cypress.Chainable<RunLuaCodeOutput>
|
|
82
|
+
|
|
83
|
+
/** Run an ex command in neovim.
|
|
84
|
+
* @example "echo expand('%:.')" current file, relative to the cwd
|
|
85
|
+
*/
|
|
86
|
+
runExCommand(input: ExCommandClientInput): Cypress.Chainable<RunExCommandOutput>
|
|
87
|
+
|
|
88
|
+
/** The test directory, providing type-safe access to its file and directory structure */
|
|
89
|
+
dir: TestDirectory<MyTestDirectory>
|
|
90
|
+
|
|
91
|
+
/** Access to the clipboard of the terminal */
|
|
92
|
+
clipboard: {
|
|
93
|
+
system(): Cypress.Chainable<string>
|
|
94
|
+
primary(): Cypress.Chainable<string>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
title: {
|
|
98
|
+
current(): Cypress.Chainable<string>
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Arguments for starting the neovim server. They are built based on your test
|
|
103
|
+
* environment in a type safe manner. */
|
|
104
|
+
export type MyStartNeovimServerArguments = OverrideProperties<
|
|
105
|
+
StartNeovimGenericArguments,
|
|
106
|
+
{
|
|
107
|
+
NVIM_APPNAME?: MyNeovimAppName
|
|
108
|
+
filename?: MyTestDirectoryFile | { openInVerticalSplits: MyTestDirectoryFile[] }
|
|
109
|
+
startupScriptModifications?: Array<MyNeovimConfigModification<MyTestDirectoryFile>>
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
|
|
113
|
+
export type MyRunLuaFileClientInput = OverrideProperties<RunLuaFileClientInput, { luaFile: MyTestDirectoryFile }>
|
|
114
|
+
|
|
115
|
+
Cypress.Commands.add("startNeovim", (startArguments?: MyStartNeovimServerArguments) => {
|
|
116
|
+
cy.window().then(async win => {
|
|
117
|
+
const underlyingNeovim: GenericNeovimBrowserApi = await win.startNeovim(
|
|
118
|
+
startArguments as StartNeovimGenericArguments
|
|
119
|
+
)
|
|
120
|
+
testNeovim = underlyingNeovim
|
|
121
|
+
testEnvironmentPath = underlyingNeovim.dir.testEnvironmentPath
|
|
122
|
+
|
|
123
|
+
// wrap everything so that Cypress can await all the commands
|
|
124
|
+
Cypress.Commands.addAll({
|
|
125
|
+
nvim_runBlockingShellCommand: underlyingNeovim.runBlockingShellCommand,
|
|
126
|
+
nvim_runExCommand: underlyingNeovim.runExCommand,
|
|
127
|
+
nvim_runLuaCode: underlyingNeovim.runLuaCode,
|
|
128
|
+
nvim_waitForLuaCode: underlyingNeovim.waitForLuaCode,
|
|
129
|
+
nvim_doFile: underlyingNeovim.doFile,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const api: NeovimContext = {
|
|
133
|
+
runBlockingShellCommand(input) {
|
|
134
|
+
return cy.nvim_runBlockingShellCommand(input)
|
|
135
|
+
},
|
|
136
|
+
runExCommand(input) {
|
|
137
|
+
return cy.nvim_runExCommand(input)
|
|
138
|
+
},
|
|
139
|
+
runLuaCode(input) {
|
|
140
|
+
return cy.nvim_runLuaCode(input)
|
|
141
|
+
},
|
|
142
|
+
doFile(input) {
|
|
143
|
+
return cy.nvim_doFile(input)
|
|
144
|
+
},
|
|
145
|
+
waitForLuaCode(input) {
|
|
146
|
+
return cy.nvim_waitForLuaCode(input)
|
|
147
|
+
},
|
|
148
|
+
typeIntoTerminal(text, options) {
|
|
149
|
+
cy.typeIntoTerminal(text, options)
|
|
150
|
+
},
|
|
151
|
+
dir: underlyingNeovim.dir as TestDirectory<MyTestDirectory>,
|
|
152
|
+
|
|
153
|
+
clipboard: {
|
|
154
|
+
primary() {
|
|
155
|
+
return cy.then(() => underlyingNeovim.clipboard.primary())
|
|
156
|
+
},
|
|
157
|
+
system() {
|
|
158
|
+
return cy.then(() => underlyingNeovim.clipboard.system())
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
title: {
|
|
163
|
+
current: () => cy.then(() => underlyingNeovim.title.get()),
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return api
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
Cypress.Commands.add("nvim_isRunning", () => {
|
|
172
|
+
return cy.window().then(async _ => {
|
|
173
|
+
return !!testNeovim
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
Cypress.Commands.add("startTerminalApplication", (args: StartTerminalGenericArguments & BrowserTerminalSettings) => {
|
|
178
|
+
cy.window().then(async win => {
|
|
179
|
+
const terminal: GenericTerminalBrowserApi = await win.startTerminalApplication({
|
|
180
|
+
serverSettings: {
|
|
181
|
+
commandToRun: args.commandToRun,
|
|
182
|
+
additionalEnvironmentVariables: args.additionalEnvironmentVariables,
|
|
183
|
+
} satisfies AllKeys<StartTerminalGenericArguments>,
|
|
184
|
+
browserSettings: {
|
|
185
|
+
configureTerminal: args.configureTerminal,
|
|
186
|
+
} satisfies AllKeys<BrowserTerminalSettings>,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
testEnvironmentPath = terminal.dir.testEnvironmentPath
|
|
190
|
+
|
|
191
|
+
Cypress.Commands.addAll({
|
|
192
|
+
terminal_runBlockingShellCommand: terminal.runBlockingShellCommand,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const api: TerminalTestApplicationContext = {
|
|
196
|
+
dir: terminal.dir as TestDirectory<MyTestDirectory>,
|
|
197
|
+
runBlockingShellCommand(input) {
|
|
198
|
+
return cy.terminal_runBlockingShellCommand(input)
|
|
199
|
+
},
|
|
200
|
+
typeIntoTerminal(text, options) {
|
|
201
|
+
cy.typeIntoTerminal(text, options)
|
|
202
|
+
},
|
|
203
|
+
clipboard: {
|
|
204
|
+
primary: () => cy.then(() => terminal.clipboard.primary()),
|
|
205
|
+
system: () => cy.then(() => terminal.clipboard.system()),
|
|
206
|
+
},
|
|
207
|
+
title: {
|
|
208
|
+
current: () => cy.then(() => terminal.title.get()),
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return api
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
Cypress.Commands.add("typeIntoTerminal", (text: string, options?: Partial<Cypress.TypeOptions>) => {
|
|
217
|
+
// the syntax for keys is described here:
|
|
218
|
+
// https://docs.cypress.io/api/commands/type
|
|
219
|
+
cy.get("textarea").focus().type(text, options)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
let testNeovim: GenericNeovimBrowserApi | undefined
|
|
223
|
+
let testEnvironmentPath: string | undefined
|
|
224
|
+
|
|
225
|
+
before(function () {
|
|
226
|
+
// disable Cypress's default behavior of logging all XMLHttpRequests and
|
|
227
|
+
// fetches to the Command Log
|
|
228
|
+
// https://gist.github.com/simenbrekken/3d2248f9e50c1143bf9dbe02e67f5399?permalink_comment_id=4615046#gistcomment-4615046
|
|
229
|
+
cy.intercept({ resourceType: /xhr|fetch/ }, { log: false })
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
export type MyBlockingCommandClientInput = OverrideProperties<
|
|
233
|
+
BlockingCommandClientInput,
|
|
234
|
+
{ cwdRelative?: MyTestDirectoryFile | undefined }
|
|
235
|
+
>
|
|
236
|
+
|
|
237
|
+
declare global {
|
|
238
|
+
namespace Cypress {
|
|
239
|
+
interface Chainable {
|
|
240
|
+
startNeovim(args?: MyStartNeovimServerArguments): Chainable<NeovimContext>
|
|
241
|
+
startTerminalApplication(
|
|
242
|
+
args: StartTerminalGenericArguments & BrowserTerminalSettings
|
|
243
|
+
): Chainable<TerminalTestApplicationContext>
|
|
244
|
+
|
|
245
|
+
/** Types text into the terminal, making the terminal application receive
|
|
246
|
+
* the keystrokes as input. Requires neovim to be running. */
|
|
247
|
+
typeIntoTerminal(text: string, options?: Partial<Cypress.TypeOptions>): Chainable<void>
|
|
248
|
+
|
|
249
|
+
/** Runs a shell command in a blocking manner, waiting for the command to
|
|
250
|
+
* finish before returning. Requires neovim to be running. */
|
|
251
|
+
nvim_runBlockingShellCommand(input: MyBlockingCommandClientInput): Chainable<BlockingShellCommandOutput>
|
|
252
|
+
|
|
253
|
+
nvim_runLuaCode(input: LuaCodeClientInput): Chainable<RunLuaCodeOutput>
|
|
254
|
+
nvim_doFile(input: MyRunLuaFileClientInput): Chainable<RunExCommandOutput>
|
|
255
|
+
nvim_waitForLuaCode(input: PollLuaCodeClientInput): Chainable<RunLuaCodeOutput>
|
|
256
|
+
|
|
257
|
+
/** Run an ex command in neovim.
|
|
258
|
+
* @example "echo expand('%:.')" current file, relative to the cwd
|
|
259
|
+
*/
|
|
260
|
+
nvim_runExCommand(input: ExCommandClientInput): Chainable<RunExCommandOutput>
|
|
261
|
+
|
|
262
|
+
/** Returns true if neovim is running. Useful to conditionally run
|
|
263
|
+
* afterEach actions based on whether it's running. */
|
|
264
|
+
nvim_isRunning(): Chainable<boolean>
|
|
265
|
+
|
|
266
|
+
terminal_runBlockingShellCommand(input: MyBlockingCommandClientInput): Chainable<BlockingShellCommandOutput>
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
afterEach(function () {
|
|
272
|
+
testNeovim = undefined
|
|
273
|
+
|
|
274
|
+
if (testEnvironmentPath) {
|
|
275
|
+
const testErr = this.currentTest?.err
|
|
276
|
+
const terminalContent = getTerminalContent() ?? ""
|
|
277
|
+
|
|
278
|
+
const snapshot = {
|
|
279
|
+
testTitle: this.currentTest?.fullTitle() ?? "unknown",
|
|
280
|
+
testState: this.currentTest?.state ?? "unknown",
|
|
281
|
+
terminalContent,
|
|
282
|
+
error: testErr?.message ?? null,
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Write to testdirs/ which is already gitignored for all tui-sandbox consumers.
|
|
287
|
+
// YAML is used so that multiline terminal content is human/AI-readable
|
|
288
|
+
// without escape sequences.
|
|
289
|
+
const snapshotPath = `${testEnvironmentPath}/testdirs/.terminal-snapshot.yaml`
|
|
290
|
+
const indentedContent = snapshot.terminalContent
|
|
291
|
+
.split("\n")
|
|
292
|
+
.map(line => ` ${line}`)
|
|
293
|
+
.join("\n")
|
|
294
|
+
const testFile = this.currentTest?.file ?? "unknown"
|
|
295
|
+
const yaml = [
|
|
296
|
+
`testTitle: ${JSON.stringify(snapshot.testTitle)}`,
|
|
297
|
+
`testFile: ${JSON.stringify(testFile)}`,
|
|
298
|
+
`testState: ${snapshot.testState}`,
|
|
299
|
+
`error: ${snapshot.error ? JSON.stringify(snapshot.error) : "null"}`,
|
|
300
|
+
`timestamp: ${JSON.stringify(snapshot.timestamp)}`,
|
|
301
|
+
`terminalContent: |`,
|
|
302
|
+
indentedContent,
|
|
303
|
+
].join("\n")
|
|
304
|
+
cy.writeFile(snapshotPath, yaml, { log: false })
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
/** Read the current terminal content from the xterm.js DOM. */
|
|
309
|
+
function getTerminalContent(): string | undefined {
|
|
310
|
+
const rows = Cypress.$("div.xterm-rows > div")
|
|
311
|
+
if (rows.length === 0) return undefined
|
|
312
|
+
|
|
313
|
+
const lines: string[] = []
|
|
314
|
+
rows.each((_, row) => {
|
|
315
|
+
lines.push(Cypress.$(row).text())
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// Trim trailing empty lines for readability
|
|
319
|
+
while (lines.length > 0 && lines[lines.length - 1]?.trim() === "") {
|
|
320
|
+
lines.pop()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return lines.length > 0 ? lines.join("\n") : undefined
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// On test failure in headless mode (cypress run / CI), append the terminal
|
|
327
|
+
// buffer contents to the error message so it appears in logs. In interactive
|
|
328
|
+
// mode (cypress open), the terminal is already visible on screen so this
|
|
329
|
+
// would just add noise.
|
|
330
|
+
//
|
|
331
|
+
// Registered via beforeEach + cy.on (per-test) so that tests can override
|
|
332
|
+
// with their own cy.on('fail') handler to swallow expected errors.
|
|
333
|
+
beforeEach(function () {
|
|
334
|
+
cy.on("fail", (err: Error) => {
|
|
335
|
+
if (!Cypress.config("isInteractive")) {
|
|
336
|
+
const content = getTerminalContent()
|
|
337
|
+
if (content) {
|
|
338
|
+
err.message += `\n\n${drawTextBox("Terminal content at failure", content)}`
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
throw err
|
|
343
|
+
})
|
|
344
|
+
})
|