boxmux 0.240.3373

YAML-driven terminal UI framework for rich, interactive CLI applications and dashboards with PTY support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
use crate::model::choice::Choice;
use crate::model::common::{Anchor, InputBounds};
use crate::tests::test_utils::TestDataFactory;
use crate::utils::screen_bounds;

#[test]
fn test_muxbox_bounds_calculation_within_terminal() {
    // Create a small muxbox with long content
    let mut muxbox = TestDataFactory::create_test_muxbox("test_muxbox");
    muxbox.position = InputBounds {
        x1: "10%".to_string(),
        y1: "10%".to_string(),
        x2: "90%".to_string(), // 80% width
        y2: "80%".to_string(), // 70% height
    };

    // Calculate muxbox bounds using the actual bounds() method
    let calculated_bounds = muxbox.bounds();

    // Get terminal bounds for comparison
    let terminal_bounds = screen_bounds();

    // Verify bounds are within terminal limits
    assert!(
        calculated_bounds.x1 >= 0,
        "MuxBox x1 {} is negative",
        calculated_bounds.x1
    );
    assert!(
        calculated_bounds.y1 >= 0,
        "MuxBox y1 {} is negative",
        calculated_bounds.y1
    );
    assert!(
        calculated_bounds.x2 <= terminal_bounds.x2,
        "MuxBox x2 {} exceeds terminal width {}",
        calculated_bounds.x2,
        terminal_bounds.x2
    );
    assert!(
        calculated_bounds.y2 <= terminal_bounds.y2,
        "MuxBox y2 {} exceeds terminal height {}",
        calculated_bounds.y2,
        terminal_bounds.y2
    );

    // Verify muxbox has positive dimensions
    assert!(
        calculated_bounds.x2 > calculated_bounds.x1,
        "MuxBox width is not positive: x1={}, x2={}",
        calculated_bounds.x1,
        calculated_bounds.x2
    );
    assert!(
        calculated_bounds.y2 > calculated_bounds.y1,
        "MuxBox height is not positive: y1={}, y2={}",
        calculated_bounds.y1,
        calculated_bounds.y2
    );

    println!("✅ MuxBox bounds properly calculated within terminal limits");
}

#[test]
fn test_muxbox_with_choices_bounds_validation() {
    // Create a small muxbox with many choices
    let mut muxbox = TestDataFactory::create_test_muxbox("choice_muxbox");
    muxbox.position = InputBounds {
        x1: "5%".to_string(),
        y1: "5%".to_string(),
        x2: "95%".to_string(), // 90% width
        y2: "50%".to_string(), // 45% height
    };

    // Add many choices with long content
    let mut choices = Vec::new();
    for i in 1..=20 {
        choices.push(Choice {
            id: format!("choice_{}", i),
            content: Some(format!(
                "This is a very long choice label number {} that should be clipped",
                i
            )),
            script: Some(vec![format!("echo 'Choice {}'", i)]),
            redirect_output: None,
            append_output: None,
            execution_mode: crate::model::common::ExecutionMode::default(),
            selected: false,
            hovered: false,
            waiting: false,
        });
    }
    muxbox.choices = Some(choices);

    // Calculate muxbox bounds
    let calculated_bounds = muxbox.bounds();
    let terminal_bounds = screen_bounds();

    // Verify muxbox bounds are within terminal
    assert!(
        calculated_bounds.x2 <= terminal_bounds.x2,
        "MuxBox with choices x2 {} exceeds terminal width {}",
        calculated_bounds.x2,
        terminal_bounds.x2
    );
    assert!(
        calculated_bounds.y2 <= terminal_bounds.y2,
        "MuxBox with choices y2 {} exceeds terminal height {}",
        calculated_bounds.y2,
        terminal_bounds.y2
    );

    // Verify choices don't impact bounds calculation negatively
    let muxbox_width = calculated_bounds.x2 - calculated_bounds.x1;
    let muxbox_height = calculated_bounds.y2 - calculated_bounds.y1;

    assert!(muxbox_width > 0, "MuxBox width should be positive");
    assert!(muxbox_height > 0, "MuxBox height should be positive");

    println!("✅ MuxBox with choices has valid bounds within terminal limits");
}

#[test]
fn test_muxbox_with_title_content_bounds() {
    // Create muxbox with title and content
    let mut muxbox = TestDataFactory::create_test_muxbox("titled_muxbox");
    muxbox.position = InputBounds {
        x1: "0%".to_string(),
        y1: "0%".to_string(),
        x2: "40%".to_string(), // 40% width
        y2: "30%".to_string(), // 30% height
    };

    muxbox.title = Some("Very Long MuxBox Title That Should Be Clipped".to_string());
    muxbox.content = Some("Line 1 of content that is too long\nLine 2 also too long\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8".to_string());

    // Calculate bounds
    let calculated_bounds = muxbox.bounds();
    let terminal_bounds = screen_bounds();

    // Verify bounds are within terminal
    assert!(
        calculated_bounds.x1 >= 0,
        "MuxBox x1 should be non-negative"
    );
    assert!(
        calculated_bounds.y1 >= 0,
        "MuxBox y1 should be non-negative"
    );
    assert!(
        calculated_bounds.x2 <= terminal_bounds.x2,
        "MuxBox with title x2 {} exceeds terminal width {}",
        calculated_bounds.x2,
        terminal_bounds.x2
    );
    assert!(
        calculated_bounds.y2 <= terminal_bounds.y2,
        "MuxBox with title y2 {} exceeds terminal height {}",
        calculated_bounds.y2,
        terminal_bounds.y2
    );

    // Verify muxbox dimensions are reasonable for content
    let muxbox_width = calculated_bounds.x2 - calculated_bounds.x1;
    let muxbox_height = calculated_bounds.y2 - calculated_bounds.y1;

    assert!(muxbox_width > 0, "MuxBox width should be positive");
    assert!(muxbox_height > 0, "MuxBox height should be positive");

    // With title and content, muxbox should have minimum reasonable size
    assert!(
        muxbox_width >= 10,
        "MuxBox width {} should be at least 10 for title",
        muxbox_width
    );
    assert!(
        muxbox_height >= 3,
        "MuxBox height {} should be at least 3 for title + content",
        muxbox_height
    );

    println!("✅ MuxBox with title and content has valid bounds");
}

#[test]
fn test_minimal_muxbox_bounds_handling() {
    // Create extremely small muxbox (edge case)
    let mut muxbox = TestDataFactory::create_test_muxbox("tiny_muxbox");
    muxbox.position = InputBounds {
        x1: "10%".to_string(),
        y1: "10%".to_string(),
        x2: "12%".to_string(), // Only 2% width
        y2: "12%".to_string(), // Only 2% height
    };

    muxbox.content = Some("Content".to_string());
    muxbox.choices = Some(vec![Choice {
        id: "choice1".to_string(),
        content: Some("Long choice".to_string()),
        script: Some(vec!["echo test".to_string()]),
        redirect_output: None,
        append_output: None,
        execution_mode: crate::model::common::ExecutionMode::default(),
        selected: false,
        hovered: false,
        waiting: false,
    }]);

    // Calculate bounds
    let calculated_bounds = muxbox.bounds();
    let terminal_bounds = screen_bounds();

    // Verify tiny muxbox bounds are still within terminal
    assert!(
        calculated_bounds.x2 <= terminal_bounds.x2,
        "Tiny muxbox x2 {} exceeds terminal width {}",
        calculated_bounds.x2,
        terminal_bounds.x2
    );
    assert!(
        calculated_bounds.y2 <= terminal_bounds.y2,
        "Tiny muxbox y2 {} exceeds terminal height {}",
        calculated_bounds.y2,
        terminal_bounds.y2
    );

    // Verify tiny muxboxes still have some positive dimensions
    let muxbox_width = calculated_bounds.x2 - calculated_bounds.x1;
    let muxbox_height = calculated_bounds.y2 - calculated_bounds.y1;

    assert!(
        muxbox_width >= 0,
        "Tiny muxbox width should be non-negative"
    );
    assert!(
        muxbox_height >= 0,
        "Tiny muxbox height should be non-negative"
    );

    println!("✅ Minimal muxbox bounds properly handled");
}

#[test]
fn test_muxbox_with_scroll_bounds_validation() {
    // Create muxbox with scrollable content
    let mut muxbox = TestDataFactory::create_test_muxbox("scroll_muxbox");
    muxbox.position = InputBounds {
        x1: "10%".to_string(),
        y1: "10%".to_string(),
        x2: "80%".to_string(),
        y2: "70%".to_string(),
    };

    // Add lots of content that would require scrolling
    let mut long_content = String::new();
    for i in 1..=100 {
        long_content.push_str(&format!(
            "This is content line {} with some text that might be long\n",
            i
        ));
    }
    muxbox.content = Some(long_content);

    // Set scroll position (simulate scrolled content)
    muxbox.vertical_scroll = Some(10.0);
    muxbox.horizontal_scroll = Some(5.0);

    // Calculate bounds
    let calculated_bounds = muxbox.bounds();
    let terminal_bounds = screen_bounds();

    // Verify scrollable muxbox bounds are still within terminal
    assert!(
        calculated_bounds.x2 <= terminal_bounds.x2,
        "Scrollable muxbox x2 {} exceeds terminal width {}",
        calculated_bounds.x2,
        terminal_bounds.x2
    );
    assert!(
        calculated_bounds.y2 <= terminal_bounds.y2,
        "Scrollable muxbox y2 {} exceeds terminal height {}",
        calculated_bounds.y2,
        terminal_bounds.y2
    );

    // Verify scroll settings don't affect bounds calculation
    let muxbox_width = calculated_bounds.x2 - calculated_bounds.x1;
    let muxbox_height = calculated_bounds.y2 - calculated_bounds.y1;

    assert!(
        muxbox_width > 0,
        "Scrollable muxbox width should be positive"
    );
    assert!(
        muxbox_height > 0,
        "Scrollable muxbox height should be positive"
    );

    println!("✅ Scrollable muxbox bounds properly validated");
}

#[test]
fn test_muxbox_with_chart_bounds_validation() {
    // Create muxbox with chart data
    let mut muxbox = TestDataFactory::create_test_muxbox("chart_muxbox");
    muxbox.position = InputBounds {
        x1: "5%".to_string(),
        y1: "5%".to_string(),
        x2: "75%".to_string(), // 70% width
        y2: "60%".to_string(), // 55% height
    };

    // Set chart type and data that might create large output
    muxbox.chart_type = Some("bar".to_string());
    muxbox.chart_data = Some("10.0,25.0,15.0,30.0,45.0,20.0,35.0,40.0,50.0,60.0".to_string());

    // Calculate bounds
    let calculated_bounds = muxbox.bounds();
    let terminal_bounds = screen_bounds();

    // Verify chart muxbox bounds are within terminal
    assert!(
        calculated_bounds.x2 <= terminal_bounds.x2,
        "Chart muxbox x2 {} exceeds terminal width {}",
        calculated_bounds.x2,
        terminal_bounds.x2
    );
    assert!(
        calculated_bounds.y2 <= terminal_bounds.y2,
        "Chart muxbox y2 {} exceeds terminal height {}",
        calculated_bounds.y2,
        terminal_bounds.y2
    );

    // Verify chart data doesn't affect bounds calculation
    let muxbox_width = calculated_bounds.x2 - calculated_bounds.x1;
    let muxbox_height = calculated_bounds.y2 - calculated_bounds.y1;

    assert!(muxbox_width > 0, "Chart muxbox width should be positive");
    assert!(muxbox_height > 0, "Chart muxbox height should be positive");

    // Chart muxboxes should have reasonable space for rendering
    assert!(
        muxbox_width >= 20,
        "Chart muxbox width {} should be at least 20",
        muxbox_width
    );
    assert!(
        muxbox_height >= 10,
        "Chart muxbox height {} should be at least 10",
        muxbox_height
    );

    println!("✅ Chart muxbox bounds properly validated");
}

#[test]
fn test_muxbox_with_table_bounds_validation() {
    // Create muxbox with table data
    let mut muxbox = TestDataFactory::create_test_muxbox("table_muxbox");
    muxbox.position = InputBounds {
        x1: "0%".to_string(),
        y1: "0%".to_string(),
        x2: "80%".to_string(),
        y2: "60%".to_string(),
    };

    // Set table content with wide data
    muxbox.content = Some(
        r#"Name,Age,Email,Department,Position,Location,Salary
John Smith,30,john.smith@example.com,Engineering,Senior Developer,New York,75000
Jane Doe,28,jane.doe@example.com,Marketing,Marketing Manager,Los Angeles,65000
Bob Johnson,35,bob.johnson@example.com,Sales,Sales Director,Chicago,85000
Alice Brown,32,alice.brown@example.com,Engineering,Tech Lead,Seattle,90000"#
            .to_string(),
    );

    // Enable table parsing
    muxbox.table_data = Some(muxbox.content.clone().unwrap_or_default());

    // Calculate bounds
    let calculated_bounds = muxbox.bounds();
    let terminal_bounds = screen_bounds();

    // Verify table muxbox bounds are within terminal
    assert!(
        calculated_bounds.x2 <= terminal_bounds.x2,
        "Table muxbox x2 {} exceeds terminal width {}",
        calculated_bounds.x2,
        terminal_bounds.x2
    );
    assert!(
        calculated_bounds.y2 <= terminal_bounds.y2,
        "Table muxbox y2 {} exceeds terminal height {}",
        calculated_bounds.y2,
        terminal_bounds.y2
    );

    // Verify table data doesn't affect bounds calculation
    let muxbox_width = calculated_bounds.x2 - calculated_bounds.x1;
    let muxbox_height = calculated_bounds.y2 - calculated_bounds.y1;

    assert!(muxbox_width > 0, "Table muxbox width should be positive");
    assert!(muxbox_height > 0, "Table muxbox height should be positive");

    // Table muxboxes should have reasonable space for table rendering
    assert!(
        muxbox_width >= 30,
        "Table muxbox width {} should be at least 30",
        muxbox_width
    );
    assert!(
        muxbox_height >= 5,
        "Table muxbox height {} should be at least 5",
        muxbox_height
    );

    println!("✅ Table muxbox bounds properly validated");
}

#[test]
fn test_muxbox_bounds_with_different_anchors() {
    let anchors = vec![
        Anchor::TopLeft,
        Anchor::TopRight,
        Anchor::BottomLeft,
        Anchor::BottomRight,
        Anchor::Center,
    ];

    for anchor in anchors {
        let mut muxbox = TestDataFactory::create_test_muxbox("anchor_muxbox");
        muxbox.position = InputBounds {
            x1: "20%".to_string(),
            y1: "20%".to_string(),
            x2: "60%".to_string(),
            y2: "60%".to_string(),
        };
        muxbox.anchor = anchor.clone();

        // Add content that could exceed bounds
        muxbox.content = Some("This is a long line of content that tests anchor positioning and bounds clipping\nSecond line\nThird line with more content".to_string());

        // Calculate bounds with anchor
        let calculated_bounds = muxbox.bounds();
        let terminal_bounds = screen_bounds();

        // Verify bounds are within terminal
        assert!(
            calculated_bounds.x1 >= 0,
            "MuxBox with anchor {:?} has x1={} < 0",
            anchor,
            calculated_bounds.x1
        );
        assert!(
            calculated_bounds.y1 >= 0,
            "MuxBox with anchor {:?} has y1={} < 0",
            anchor,
            calculated_bounds.y1
        );
        assert!(
            calculated_bounds.x2 <= terminal_bounds.x2,
            "MuxBox with anchor {:?} has x2={} exceeding terminal width {}",
            anchor,
            calculated_bounds.x2,
            terminal_bounds.x2
        );
        assert!(
            calculated_bounds.y2 <= terminal_bounds.y2,
            "MuxBox with anchor {:?} has y2={} exceeding terminal height {}",
            anchor,
            calculated_bounds.y2,
            terminal_bounds.y2
        );

        // Verify muxbox has positive dimensions
        assert!(
            calculated_bounds.x2 > calculated_bounds.x1,
            "MuxBox with anchor {:?} has invalid width: x1={}, x2={}",
            anchor,
            calculated_bounds.x1,
            calculated_bounds.x2
        );
        assert!(
            calculated_bounds.y2 > calculated_bounds.y1,
            "MuxBox with anchor {:?} has invalid height: y1={}, y2={}",
            anchor,
            calculated_bounds.y1,
            calculated_bounds.y2
        );
    }

    println!("✅ MuxBox bounds properly handled for all anchor types");
}