[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

feat: box prompt (#363)

authored by

James Garbutt and committed by
GitHub
(Aug 10, 2025, 10:27 AM +0100) 76fd17f4 dc1a86cf

+840
+5
.changeset/shy-ideas-shout.md
··· 1 + --- 2 + "@clack/prompts": minor 3 + --- 4 + 5 + Added new `box` prompt for rendering boxed text, similar a note.
+128
packages/prompts/src/box.ts
··· 1 + import type { Writable } from 'node:stream'; 2 + import { getColumns } from '@clack/core'; 3 + import wrap from 'wrap-ansi'; 4 + import { 5 + type CommonOptions, 6 + S_BAR, 7 + S_BAR_END, 8 + S_BAR_END_RIGHT, 9 + S_BAR_H, 10 + S_BAR_START, 11 + S_BAR_START_RIGHT, 12 + S_CORNER_BOTTOM_LEFT, 13 + S_CORNER_BOTTOM_RIGHT, 14 + S_CORNER_TOP_LEFT, 15 + S_CORNER_TOP_RIGHT, 16 + } from './common.js'; 17 + 18 + export type BoxAlignment = 'left' | 'center' | 'right'; 19 + 20 + type BoxSymbols = [topLeft: string, topRight: string, bottomLeft: string, bottomRight: string]; 21 + 22 + const roundedSymbols: BoxSymbols = [ 23 + S_CORNER_TOP_LEFT, 24 + S_CORNER_TOP_RIGHT, 25 + S_CORNER_BOTTOM_LEFT, 26 + S_CORNER_BOTTOM_RIGHT, 27 + ]; 28 + const squareSymbols: BoxSymbols = [S_BAR_START, S_BAR_START_RIGHT, S_BAR_END, S_BAR_END_RIGHT]; 29 + 30 + export interface BoxOptions extends CommonOptions { 31 + contentAlign?: BoxAlignment; 32 + titleAlign?: BoxAlignment; 33 + width?: number | 'auto'; 34 + titlePadding?: number; 35 + contentPadding?: number; 36 + rounded?: boolean; 37 + includePrefix?: boolean; 38 + formatBorder?: (text: string) => string; 39 + } 40 + 41 + function getPaddingForLine( 42 + lineLength: number, 43 + innerWidth: number, 44 + padding: number, 45 + contentAlign: BoxAlignment | undefined 46 + ): [number, number] { 47 + let leftPadding = padding; 48 + let rightPadding = padding; 49 + if (contentAlign === 'center') { 50 + leftPadding = Math.floor((innerWidth - lineLength) / 2); 51 + } else if (contentAlign === 'right') { 52 + leftPadding = innerWidth - lineLength - padding; 53 + } 54 + 55 + rightPadding = innerWidth - leftPadding - lineLength; 56 + 57 + return [leftPadding, rightPadding]; 58 + } 59 + 60 + const defaultFormatBorder = (text: string) => text; 61 + 62 + export const box = (message = '', title = '', opts?: BoxOptions) => { 63 + const output: Writable = opts?.output ?? process.stdout; 64 + const columns = getColumns(output); 65 + const borderWidth = 1; 66 + const borderTotalWidth = borderWidth * 2; 67 + const titlePadding = opts?.titlePadding ?? 1; 68 + const contentPadding = opts?.contentPadding ?? 2; 69 + const width = opts?.width === undefined || opts.width === 'auto' ? 1 : Math.min(1, opts.width); 70 + const linePrefix = opts?.includePrefix ? `${S_BAR} ` : ''; 71 + const formatBorder = opts?.formatBorder ?? defaultFormatBorder; 72 + const symbols = (opts?.rounded ? roundedSymbols : squareSymbols).map(formatBorder); 73 + const hSymbol = formatBorder(S_BAR_H); 74 + const vSymbol = formatBorder(S_BAR); 75 + const maxBoxWidth = columns - linePrefix.length; 76 + let boxWidth = Math.floor(columns * width) - linePrefix.length; 77 + if (opts?.width === 'auto') { 78 + const lines = message.split('\n'); 79 + let longestLine = title.length + titlePadding * 2; 80 + for (const line of lines) { 81 + const lineWithPadding = line.length + contentPadding * 2; 82 + if (lineWithPadding > longestLine) { 83 + longestLine = lineWithPadding; 84 + } 85 + } 86 + const longestLineWidth = longestLine + borderTotalWidth; 87 + if (longestLineWidth < boxWidth) { 88 + boxWidth = longestLineWidth; 89 + } 90 + } 91 + if (boxWidth % 2 !== 0) { 92 + if (boxWidth < maxBoxWidth) { 93 + boxWidth++; 94 + } else { 95 + boxWidth--; 96 + } 97 + } 98 + const innerWidth = boxWidth - borderTotalWidth; 99 + const maxTitleLength = innerWidth - titlePadding * 2; 100 + const truncatedTitle = 101 + title.length > maxTitleLength ? `${title.slice(0, maxTitleLength - 3)}...` : title; 102 + const [titlePaddingLeft, titlePaddingRight] = getPaddingForLine( 103 + truncatedTitle.length, 104 + innerWidth, 105 + titlePadding, 106 + opts?.titleAlign 107 + ); 108 + const wrappedMessage = wrap(message, innerWidth - contentPadding * 2, { 109 + hard: true, 110 + trim: false, 111 + }); 112 + output.write( 113 + `${linePrefix}${symbols[0]}${hSymbol.repeat(titlePaddingLeft)}${truncatedTitle}${hSymbol.repeat(titlePaddingRight)}${symbols[1]}\n` 114 + ); 115 + const wrappedLines = wrappedMessage.split('\n'); 116 + for (const line of wrappedLines) { 117 + const [leftLinePadding, rightLinePadding] = getPaddingForLine( 118 + line.length, 119 + innerWidth, 120 + contentPadding, 121 + opts?.contentAlign 122 + ); 123 + output.write( 124 + `${linePrefix}${vSymbol}${' '.repeat(leftLinePadding)}${line}${' '.repeat(rightLinePadding)}${vSymbol}\n` 125 + ); 126 + } 127 + output.write(`${linePrefix}${symbols[2]}${hSymbol.repeat(innerWidth)}${symbols[3]}\n`); 128 + };
+4
packages/prompts/src/common.ts
··· 17 17 export const S_BAR_START = unicodeOr('┌', 'T'); 18 18 export const S_BAR = unicodeOr('│', '|'); 19 19 export const S_BAR_END = unicodeOr('└', '—'); 20 + export const S_BAR_START_RIGHT = unicodeOr('┐', 'T'); 21 + export const S_BAR_END_RIGHT = unicodeOr('┘', '—'); 20 22 21 23 export const S_RADIO_ACTIVE = unicodeOr('●', '>'); 22 24 export const S_RADIO_INACTIVE = unicodeOr('○', ' '); ··· 29 31 export const S_CORNER_TOP_RIGHT = unicodeOr('╮', '+'); 30 32 export const S_CONNECT_LEFT = unicodeOr('├', '+'); 31 33 export const S_CORNER_BOTTOM_RIGHT = unicodeOr('╯', '+'); 34 + export const S_CORNER_BOTTOM_LEFT = unicodeOr('╰', '+'); 35 + export const S_CORNER_TOP_LEFT = unicodeOr('╭', '+'); 32 36 33 37 export const S_INFO = unicodeOr('●', '•'); 34 38 export const S_SUCCESS = unicodeOr('◆', '*');
+1
packages/prompts/src/index.ts
··· 1 1 export { type ClackSettings, isCancel, settings, updateSettings } from '@clack/core'; 2 2 3 3 export * from './autocomplete.js'; 4 + export * from './box.js'; 4 5 export * from './common.js'; 5 6 export * from './confirm.js'; 6 7 export * from './group.js';
+465
packages/prompts/test/__snapshots__/box.test.ts.snap
··· 1 + // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 + 3 + exports[`box (isCI = false) > cannot have width larger than 100% 1`] = ` 4 + [ 5 + "┌─title────────────────────────────────────────────────────────────────────────┐ 6 + ", 7 + "│ message │ 8 + ", 9 + "└──────────────────────────────────────────────────────────────────────────────┘ 10 + ", 11 + ] 12 + `; 13 + 14 + exports[`box (isCI = false) > renders as specified width 1`] = ` 15 + [ 16 + "┌─title────────────────────────────────┐ 17 + ", 18 + "│ short │ 19 + ", 20 + "│ somewhat questionably long line │ 21 + ", 22 + "└──────────────────────────────────────┘ 23 + ", 24 + ] 25 + `; 26 + 27 + exports[`box (isCI = false) > renders as wide as longest line with width: auto 1`] = ` 28 + [ 29 + "┌─title──────────────────────────────┐ 30 + ", 31 + "│ short │ 32 + ", 33 + "│ somewhat questionably long line │ 34 + ", 35 + "└────────────────────────────────────┘ 36 + ", 37 + ] 38 + `; 39 + 40 + exports[`box (isCI = false) > renders auto width with content longer than title 1`] = ` 41 + [ 42 + "┌─title──────────────────────────┐ 43 + ", 44 + "│ messagemessagemessagemessage │ 45 + ", 46 + "└────────────────────────────────┘ 47 + ", 48 + ] 49 + `; 50 + 51 + exports[`box (isCI = false) > renders auto width with title longer than content 1`] = ` 52 + [ 53 + "┌─titletitletitletitle─┐ 54 + ", 55 + "│ message │ 56 + ", 57 + "└──────────────────────┘ 58 + ", 59 + ] 60 + `; 61 + 62 + exports[`box (isCI = false) > renders center aligned content 1`] = ` 63 + [ 64 + "┌─title──────┐ 65 + ", 66 + "│ message │ 67 + ", 68 + "└────────────┘ 69 + ", 70 + ] 71 + `; 72 + 73 + exports[`box (isCI = false) > renders center aligned title 1`] = ` 74 + [ 75 + "┌───title────┐ 76 + ", 77 + "│ message │ 78 + ", 79 + "└────────────┘ 80 + ", 81 + ] 82 + `; 83 + 84 + exports[`box (isCI = false) > renders left aligned content 1`] = ` 85 + [ 86 + "┌─title──────┐ 87 + ", 88 + "│ message │ 89 + ", 90 + "└────────────┘ 91 + ", 92 + ] 93 + `; 94 + 95 + exports[`box (isCI = false) > renders left aligned title 1`] = ` 96 + [ 97 + "┌─title──────┐ 98 + ", 99 + "│ message │ 100 + ", 101 + "└────────────┘ 102 + ", 103 + ] 104 + `; 105 + 106 + exports[`box (isCI = false) > renders message 1`] = ` 107 + [ 108 + "┌──────────────────────────────────────────────────────────────────────────────┐ 109 + ", 110 + "│ message │ 111 + ", 112 + "└──────────────────────────────────────────────────────────────────────────────┘ 113 + ", 114 + ] 115 + `; 116 + 117 + exports[`box (isCI = false) > renders message with title 1`] = ` 118 + [ 119 + "┌─some title───────────────────────────────────────────────────────────────────┐ 120 + ", 121 + "│ message │ 122 + ", 123 + "└──────────────────────────────────────────────────────────────────────────────┘ 124 + ", 125 + ] 126 + `; 127 + 128 + exports[`box (isCI = false) > renders right aligned content 1`] = ` 129 + [ 130 + "┌─title──────┐ 131 + ", 132 + "│ message │ 133 + ", 134 + "└────────────┘ 135 + ", 136 + ] 137 + `; 138 + 139 + exports[`box (isCI = false) > renders right aligned title 1`] = ` 140 + [ 141 + "┌──────title─┐ 142 + ", 143 + "│ message │ 144 + ", 145 + "└────────────┘ 146 + ", 147 + ] 148 + `; 149 + 150 + exports[`box (isCI = false) > renders rounded corners when rounded is true 1`] = ` 151 + [ 152 + "╭─title──────╮ 153 + ", 154 + "│ message │ 155 + ", 156 + "╰────────────╯ 157 + ", 158 + ] 159 + `; 160 + 161 + exports[`box (isCI = false) > renders specified contentPadding 1`] = ` 162 + [ 163 + "┌─title──────────────┐ 164 + ", 165 + "│ message │ 166 + ", 167 + "└────────────────────┘ 168 + ", 169 + ] 170 + `; 171 + 172 + exports[`box (isCI = false) > renders specified titlePadding 1`] = ` 173 + [ 174 + "┌──────title───────┐ 175 + ", 176 + "│ message │ 177 + ", 178 + "└──────────────────┘ 179 + ", 180 + ] 181 + `; 182 + 183 + exports[`box (isCI = false) > renders truncated long titles 1`] = ` 184 + [ 185 + "┌─foofoofoo...─┐ 186 + ", 187 + "│ message │ 188 + ", 189 + "└──────────────┘ 190 + ", 191 + ] 192 + `; 193 + 194 + exports[`box (isCI = false) > renders with formatBorder formatting 1`] = ` 195 + [ 196 + "┌─title──────┐ 197 + ", 198 + "│ message │ 199 + ", 200 + "└────────────┘ 201 + ", 202 + ] 203 + `; 204 + 205 + exports[`box (isCI = false) > renders with prefix when includePrefix is true 1`] = ` 206 + [ 207 + "│ ┌─title──────┐ 208 + ", 209 + "│ │ message │ 210 + ", 211 + "│ └────────────┘ 212 + ", 213 + ] 214 + `; 215 + 216 + exports[`box (isCI = false) > wraps content to fit within specified width 1`] = ` 217 + [ 218 + "┌─title────────────────────────────────┐ 219 + ", 220 + "│ foo barfoo barfoo barfoo barfoo │ 221 + ", 222 + "│ barfoo barfoo barfoo barfoo barfoo │ 223 + ", 224 + "│ barfoo barfoo barfoo barfoo │ 225 + ", 226 + "│ barfoo barfoo barfoo barfoo barfoo │ 227 + ", 228 + "│ barfoo bar │ 229 + ", 230 + "└──────────────────────────────────────┘ 231 + ", 232 + ] 233 + `; 234 + 235 + exports[`box (isCI = true) > cannot have width larger than 100% 1`] = ` 236 + [ 237 + "┌─title────────────────────────────────────────────────────────────────────────┐ 238 + ", 239 + "│ message │ 240 + ", 241 + "└──────────────────────────────────────────────────────────────────────────────┘ 242 + ", 243 + ] 244 + `; 245 + 246 + exports[`box (isCI = true) > renders as specified width 1`] = ` 247 + [ 248 + "┌─title────────────────────────────────┐ 249 + ", 250 + "│ short │ 251 + ", 252 + "│ somewhat questionably long line │ 253 + ", 254 + "└──────────────────────────────────────┘ 255 + ", 256 + ] 257 + `; 258 + 259 + exports[`box (isCI = true) > renders as wide as longest line with width: auto 1`] = ` 260 + [ 261 + "┌─title──────────────────────────────┐ 262 + ", 263 + "│ short │ 264 + ", 265 + "│ somewhat questionably long line │ 266 + ", 267 + "└────────────────────────────────────┘ 268 + ", 269 + ] 270 + `; 271 + 272 + exports[`box (isCI = true) > renders auto width with content longer than title 1`] = ` 273 + [ 274 + "┌─title──────────────────────────┐ 275 + ", 276 + "│ messagemessagemessagemessage │ 277 + ", 278 + "└────────────────────────────────┘ 279 + ", 280 + ] 281 + `; 282 + 283 + exports[`box (isCI = true) > renders auto width with title longer than content 1`] = ` 284 + [ 285 + "┌─titletitletitletitle─┐ 286 + ", 287 + "│ message │ 288 + ", 289 + "└──────────────────────┘ 290 + ", 291 + ] 292 + `; 293 + 294 + exports[`box (isCI = true) > renders center aligned content 1`] = ` 295 + [ 296 + "┌─title──────┐ 297 + ", 298 + "│ message │ 299 + ", 300 + "└────────────┘ 301 + ", 302 + ] 303 + `; 304 + 305 + exports[`box (isCI = true) > renders center aligned title 1`] = ` 306 + [ 307 + "┌───title────┐ 308 + ", 309 + "│ message │ 310 + ", 311 + "└────────────┘ 312 + ", 313 + ] 314 + `; 315 + 316 + exports[`box (isCI = true) > renders left aligned content 1`] = ` 317 + [ 318 + "┌─title──────┐ 319 + ", 320 + "│ message │ 321 + ", 322 + "└────────────┘ 323 + ", 324 + ] 325 + `; 326 + 327 + exports[`box (isCI = true) > renders left aligned title 1`] = ` 328 + [ 329 + "┌─title──────┐ 330 + ", 331 + "│ message │ 332 + ", 333 + "└────────────┘ 334 + ", 335 + ] 336 + `; 337 + 338 + exports[`box (isCI = true) > renders message 1`] = ` 339 + [ 340 + "┌──────────────────────────────────────────────────────────────────────────────┐ 341 + ", 342 + "│ message │ 343 + ", 344 + "└──────────────────────────────────────────────────────────────────────────────┘ 345 + ", 346 + ] 347 + `; 348 + 349 + exports[`box (isCI = true) > renders message with title 1`] = ` 350 + [ 351 + "┌─some title───────────────────────────────────────────────────────────────────┐ 352 + ", 353 + "│ message │ 354 + ", 355 + "└──────────────────────────────────────────────────────────────────────────────┘ 356 + ", 357 + ] 358 + `; 359 + 360 + exports[`box (isCI = true) > renders right aligned content 1`] = ` 361 + [ 362 + "┌─title──────┐ 363 + ", 364 + "│ message │ 365 + ", 366 + "└────────────┘ 367 + ", 368 + ] 369 + `; 370 + 371 + exports[`box (isCI = true) > renders right aligned title 1`] = ` 372 + [ 373 + "┌──────title─┐ 374 + ", 375 + "│ message │ 376 + ", 377 + "└────────────┘ 378 + ", 379 + ] 380 + `; 381 + 382 + exports[`box (isCI = true) > renders rounded corners when rounded is true 1`] = ` 383 + [ 384 + "╭─title──────╮ 385 + ", 386 + "│ message │ 387 + ", 388 + "╰────────────╯ 389 + ", 390 + ] 391 + `; 392 + 393 + exports[`box (isCI = true) > renders specified contentPadding 1`] = ` 394 + [ 395 + "┌─title──────────────┐ 396 + ", 397 + "│ message │ 398 + ", 399 + "└────────────────────┘ 400 + ", 401 + ] 402 + `; 403 + 404 + exports[`box (isCI = true) > renders specified titlePadding 1`] = ` 405 + [ 406 + "┌──────title───────┐ 407 + ", 408 + "│ message │ 409 + ", 410 + "└──────────────────┘ 411 + ", 412 + ] 413 + `; 414 + 415 + exports[`box (isCI = true) > renders truncated long titles 1`] = ` 416 + [ 417 + "┌─foofoofoo...─┐ 418 + ", 419 + "│ message │ 420 + ", 421 + "└──────────────┘ 422 + ", 423 + ] 424 + `; 425 + 426 + exports[`box (isCI = true) > renders with formatBorder formatting 1`] = ` 427 + [ 428 + "┌─title──────┐ 429 + ", 430 + "│ message │ 431 + ", 432 + "└────────────┘ 433 + ", 434 + ] 435 + `; 436 + 437 + exports[`box (isCI = true) > renders with prefix when includePrefix is true 1`] = ` 438 + [ 439 + "│ ┌─title──────┐ 440 + ", 441 + "│ │ message │ 442 + ", 443 + "│ └────────────┘ 444 + ", 445 + ] 446 + `; 447 + 448 + exports[`box (isCI = true) > wraps content to fit within specified width 1`] = ` 449 + [ 450 + "┌─title────────────────────────────────┐ 451 + ", 452 + "│ foo barfoo barfoo barfoo barfoo │ 453 + ", 454 + "│ barfoo barfoo barfoo barfoo barfoo │ 455 + ", 456 + "│ barfoo barfoo barfoo barfoo │ 457 + ", 458 + "│ barfoo barfoo barfoo barfoo barfoo │ 459 + ", 460 + "│ barfoo bar │ 461 + ", 462 + "└──────────────────────────────────────┘ 463 + ", 464 + ] 465 + `;
+237
packages/prompts/test/box.test.ts
··· 1 + import colors from 'picocolors'; 2 + import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; 3 + import * as prompts from '../src/index.js'; 4 + import { MockReadable, MockWritable } from './test-utils.js'; 5 + 6 + describe.each(['true', 'false'])('box (isCI = %s)', (isCI) => { 7 + let originalCI: string | undefined; 8 + let output: MockWritable; 9 + let input: MockReadable; 10 + 11 + beforeAll(() => { 12 + originalCI = process.env.CI; 13 + process.env.CI = isCI; 14 + }); 15 + 16 + afterAll(() => { 17 + process.env.CI = originalCI; 18 + }); 19 + 20 + beforeEach(() => { 21 + output = new MockWritable(); 22 + input = new MockReadable(); 23 + }); 24 + 25 + afterEach(() => { 26 + vi.restoreAllMocks(); 27 + }); 28 + 29 + test('renders message', () => { 30 + prompts.box('message', undefined, { 31 + input, 32 + output, 33 + }); 34 + 35 + expect(output.buffer).toMatchSnapshot(); 36 + }); 37 + 38 + test('renders message with title', () => { 39 + prompts.box('message', 'some title', { 40 + input, 41 + output, 42 + }); 43 + 44 + expect(output.buffer).toMatchSnapshot(); 45 + }); 46 + 47 + test('renders as wide as longest line with width: auto', () => { 48 + prompts.box('short\nsomewhat questionably long line', 'title', { 49 + input, 50 + output, 51 + width: 'auto', 52 + }); 53 + 54 + expect(output.buffer).toMatchSnapshot(); 55 + }); 56 + 57 + test('renders as specified width', () => { 58 + prompts.box('short\nsomewhat questionably long line', 'title', { 59 + input, 60 + output, 61 + width: 0.5, 62 + }); 63 + 64 + expect(output.buffer).toMatchSnapshot(); 65 + }); 66 + 67 + test('wraps content to fit within specified width', () => { 68 + prompts.box('foo bar'.repeat(20), 'title', { 69 + input, 70 + output, 71 + width: 0.5, 72 + }); 73 + 74 + expect(output.buffer).toMatchSnapshot(); 75 + }); 76 + 77 + test('renders specified titlePadding', () => { 78 + prompts.box('message', 'title', { 79 + input, 80 + output, 81 + titlePadding: 6, 82 + width: 'auto', 83 + }); 84 + 85 + expect(output.buffer).toMatchSnapshot(); 86 + }); 87 + 88 + test('renders specified contentPadding', () => { 89 + prompts.box('message', 'title', { 90 + input, 91 + output, 92 + contentPadding: 6, 93 + width: 'auto', 94 + }); 95 + 96 + expect(output.buffer).toMatchSnapshot(); 97 + }); 98 + 99 + test('renders with prefix when includePrefix is true', () => { 100 + prompts.box('message', 'title', { 101 + input, 102 + output, 103 + includePrefix: true, 104 + width: 'auto', 105 + }); 106 + 107 + expect(output.buffer).toMatchSnapshot(); 108 + }); 109 + 110 + test('renders truncated long titles', () => { 111 + prompts.box('message', 'foo'.repeat(20), { 112 + input, 113 + output, 114 + width: 0.2, 115 + }); 116 + 117 + expect(output.buffer).toMatchSnapshot(); 118 + }); 119 + 120 + test('cannot have width larger than 100%', () => { 121 + prompts.box('message', 'title', { 122 + input, 123 + output, 124 + width: 1.1, 125 + }); 126 + 127 + expect(output.buffer).toMatchSnapshot(); 128 + }); 129 + 130 + test('renders rounded corners when rounded is true', () => { 131 + prompts.box('message', 'title', { 132 + input, 133 + output, 134 + rounded: true, 135 + width: 'auto', 136 + }); 137 + 138 + expect(output.buffer).toMatchSnapshot(); 139 + }); 140 + 141 + test('renders auto width with content longer than title', () => { 142 + prompts.box('message'.repeat(4), 'title', { 143 + input, 144 + output, 145 + width: 'auto', 146 + }); 147 + 148 + expect(output.buffer).toMatchSnapshot(); 149 + }); 150 + 151 + test('renders auto width with title longer than content', () => { 152 + prompts.box('message', 'title'.repeat(4), { 153 + input, 154 + output, 155 + width: 'auto', 156 + }); 157 + 158 + expect(output.buffer).toMatchSnapshot(); 159 + }); 160 + 161 + test('renders left aligned title', () => { 162 + prompts.box('message', 'title', { 163 + input, 164 + output, 165 + titleAlign: 'left', 166 + width: 'auto', 167 + }); 168 + 169 + expect(output.buffer).toMatchSnapshot(); 170 + }); 171 + 172 + test('renders right aligned title', () => { 173 + prompts.box('message', 'title', { 174 + input, 175 + output, 176 + titleAlign: 'right', 177 + width: 'auto', 178 + }); 179 + 180 + expect(output.buffer).toMatchSnapshot(); 181 + }); 182 + 183 + test('renders center aligned title', () => { 184 + prompts.box('message', 'title', { 185 + input, 186 + output, 187 + titleAlign: 'center', 188 + width: 'auto', 189 + }); 190 + 191 + expect(output.buffer).toMatchSnapshot(); 192 + }); 193 + 194 + test('renders left aligned content', () => { 195 + prompts.box('message', 'title', { 196 + input, 197 + output, 198 + contentAlign: 'left', 199 + width: 'auto', 200 + }); 201 + 202 + expect(output.buffer).toMatchSnapshot(); 203 + }); 204 + 205 + test('renders right aligned content', () => { 206 + prompts.box('message', 'title', { 207 + input, 208 + output, 209 + contentAlign: 'right', 210 + width: 'auto', 211 + }); 212 + 213 + expect(output.buffer).toMatchSnapshot(); 214 + }); 215 + 216 + test('renders center aligned content', () => { 217 + prompts.box('message', 'title', { 218 + input, 219 + output, 220 + contentAlign: 'center', 221 + width: 'auto', 222 + }); 223 + 224 + expect(output.buffer).toMatchSnapshot(); 225 + }); 226 + 227 + test('renders with formatBorder formatting', () => { 228 + prompts.box('message', 'title', { 229 + input, 230 + output, 231 + width: 'auto', 232 + formatBorder: colors.red, 233 + }); 234 + 235 + expect(output.buffer).toMatchSnapshot(); 236 + }); 237 + });