//! generated from ink/test layout corpus via _corpus_prep/manifest.json;
//! equivalence = plain-frame string equality (ADR-1 adjudication);
//! skipped blocks listed in docs/adr1-gate.md
#[cfg(test)]
mod tests {
mod helpers {
use inkferro_core::dom::{Arena, Kind, Style};
pub(crate) fn make_root(arena: &mut Arena, id: u32) {
inkferro_core::dom::apply(
arena,
&[inkferro_core::dom::Op::Create {
id,
kind: Kind::Root,
}],
);
}
pub(crate) fn make_root_zero(arena: &mut Arena) {
inkferro_core::dom::apply(
arena,
&[inkferro_core::dom::Op::Create {
id: 0,
kind: Kind::Root,
}],
);
}
pub(crate) fn make_box(arena: &mut Arena, id: u32, style: Style) {
inkferro_core::dom::apply(
arena,
&[
inkferro_core::dom::Op::Create {
id,
kind: Kind::Box,
},
inkferro_core::dom::Op::SetStyle {
id,
style: Box::new(style),
},
],
);
}
pub(crate) fn make_text(arena: &mut Arena, id: u32, text: &str) {
inkferro_core::dom::apply(
arena,
&[
inkferro_core::dom::Op::Create {
id,
kind: Kind::Text,
},
inkferro_core::dom::Op::SetText {
id,
text: text.to_owned(),
},
],
);
}
pub(crate) fn add_child(arena: &mut Arena, parent: u32, child: u32) {
inkferro_core::dom::apply(
arena,
&[inkferro_core::dom::Op::AppendChild { parent, child }],
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod borders (borders.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod borders {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{
Align, Arena, BorderStyle, ContentAlign, Dim, FlexDir, Lp, Style,
};
use inkferro_core::render::render_to_string;
// ── single node - full width box ─────────────────────────────────────────
// ink: renderToString(<Box borderStyle="round"><Text>Hello World</Text></Box>)
// Default columns=100 → content width = 98.
#[test]
fn borders_single_node_full_width_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮\n│Hello World │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯",
"single node - full width box"
);
}
// ── single node - full width box with colorful border ────────────────────
// ink: borderColor="green" wraps every border element in chalk.green().
// Expected contains ANSI SGR green (\x1b[32m...\x1b[39m).
#[test]
fn borders_single_node_full_width_box_with_colorful_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_color: Some("green".to_owned()),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"\x1b[32m╭──────────────────────────────────────────────────────────────────────────────────────────────────╮\x1b[39m\n\x1b[32m│\x1b[39mHello World \x1b[32m│\x1b[39m\n\x1b[32m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\x1b[39m",
"single node - full width box with colorful border"
);
}
// ── single node - fit-content box ────────────────────────────────────────
// ink: alignSelf="flex-start" shrinks the box to content width.
#[test]
fn borders_single_node_fit_content_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭───────────╮\n│Hello World│\n╰───────────╯",
"single node - fit-content box"
);
}
// ── single node - fit-content box with wide characters ───────────────────
// ink: borders.tsx "single node - fit-content box with wide characters"
// Japanese full-width chars (2 cols each), 5 chars = 10 display cols.
#[test]
fn borders_single_node_fit_content_box_with_wide_characters() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "こんにちは");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────╮\n│こんにちは│\n╰──────────╯",
"single node - fit-content box with wide characters"
);
}
// ── single node - fit-content box with emojis ────────────────────────────
// ink: borders.tsx "single node - fit-content box with emojis"
// Each 🌊 is 2 display cols; 2 emojis = 4 cols.
#[test]
fn borders_single_node_fit_content_box_with_emojis() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "🌊🌊");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────╮\n│🌊🌊│\n╰────╯",
"single node - fit-content box with emojis"
);
}
// ── single node - fit-content box with variation selector emojis ─────────
// ink: borders.tsx "single node - fit-content box with variation selector emojis"
// Issue #733: FE0F variation selectors must not double-count width.
#[test]
fn borders_single_node_fit_content_box_with_variation_selector_emojis() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "🌡️⚠️✅");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────╮\n│🌡️⚠️✅│\n╰──────╯",
"single node - fit-content box with variation selector emojis"
);
}
// ── single node - fixed width box ────────────────────────────────────────
// ink: borders.tsx "single node - fixed width box"
// width=20 → 18-col content area (minus 2 border cols).
#[test]
fn borders_single_node_fixed_width_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(20.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────╮\n│Hello World │\n╰──────────────────╯",
"single node - fixed width box"
);
}
// ── single node - fixed width and height box ──────────────────────────────
// ink: borders.tsx "single node - fixed width and height box"
// width=20, height=20 → 18×18 content area.
#[test]
fn borders_single_node_fixed_width_and_height_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(20.0)),
height: Some(Dim::Points(20.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────╮\n│Hello World │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n╰──────────────────╯",
"single node - fixed width and height box"
);
}
// ── single node - box with padding ───────────────────────────────────────
// ink: borders.tsx "single node - box with padding"
// padding=1 adds 1 space on all 4 sides; alignSelf=flex-start to shrink width.
#[test]
fn borders_single_node_box_with_padding() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
padding: Some(Lp::Points(1.0)),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭─────────────╮\n│ │\n│ Hello World │\n│ │\n╰─────────────╯",
"single node - box with padding"
);
}
// ── single node - box with horizontal alignment ───────────────────────────
// ink: borders.tsx "single node - box with horizontal alignment"
// justifyContent="center" in 18-col content area.
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn borders_single_node_box_with_horizontal_alignment() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(20.0)),
justify_content: Some(ContentAlign::Center),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────╮\n│ Hello World │\n╰──────────────────╯",
"single node - box with horizontal alignment"
);
}
// ── single node - box with vertical alignment ─────────────────────────────
// ink: borders.tsx "single node - box with vertical alignment"
// alignItems="center" with height=20; alignSelf="flex-start" to shrink width.
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn borders_single_node_box_with_vertical_alignment() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
height: Some(Dim::Points(20.0)),
align_items: Some(Align::Center),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭───────────╮\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│Hello World│\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n╰───────────╯",
"single node - box with vertical alignment"
);
}
// ── single node - box with wrapping ──────────────────────────────────────
// ink: borders.tsx "single node - box with wrapping"
// width=10 → 8-col content area; "Hello World" wraps.
#[test]
fn borders_single_node_box_with_wrapping() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────────╮\n│Hello │\n│World │\n╰────────╯",
"single node - box with wrapping"
);
}
// ── multiple nodes - full width box ──────────────────────────────────────
// ink: borders.tsx "multiple nodes - full width box"
// Two JSX text children: {'Hello '}World → squashed to "Hello World".
#[test]
fn borders_multiple_nodes_full_width_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
// Two virtual-text children squash to one Text node with combined text.
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮\n│Hello World │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯",
"multiple nodes - full width box"
);
}
// ── multiple nodes - full width box with colorful border ─────────────────
// ink: borders.tsx "multiple nodes - full width box with colorful border"
#[test]
fn borders_multiple_nodes_full_width_box_with_colorful_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_color: Some("green".to_owned()),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"\x1b[32m╭──────────────────────────────────────────────────────────────────────────────────────────────────╮\x1b[39m\n\x1b[32m│\x1b[39mHello World \x1b[32m│\x1b[39m\n\x1b[32m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\x1b[39m",
"multiple nodes - full width box with colorful border"
);
}
// ── multiple nodes - fit-content box ─────────────────────────────────────
// ink: borders.tsx "multiple nodes - fit-content box"
#[test]
fn borders_multiple_nodes_fit_content_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭───────────╮\n│Hello World│\n╰───────────╯",
"multiple nodes - fit-content box"
);
}
// ── multiple nodes - fixed width box ─────────────────────────────────────
// ink: borders.tsx "multiple nodes - fixed width box"
#[test]
fn borders_multiple_nodes_fixed_width_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(20.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────╮\n│Hello World │\n╰──────────────────╯",
"multiple nodes - fixed width box"
);
}
// ── multiple nodes - fixed width and height box ───────────────────────────
// ink: borders.tsx "multiple nodes - fixed width and height box"
#[test]
fn borders_multiple_nodes_fixed_width_and_height_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(20.0)),
height: Some(Dim::Points(20.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────╮\n│Hello World │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n╰──────────────────╯",
"multiple nodes - fixed width and height box"
);
}
// ── multiple nodes - box with padding ─────────────────────────────────────
// ink: borders.tsx "multiple nodes - box with padding"
#[test]
fn borders_multiple_nodes_box_with_padding() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
padding: Some(Lp::Points(1.0)),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭─────────────╮\n│ │\n│ Hello World │\n│ │\n╰─────────────╯",
"multiple nodes - box with padding"
);
}
// ── multiple nodes - box with horizontal alignment ────────────────────────
// ink: borders.tsx "multiple nodes - box with horizontal alignment"
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn borders_multiple_nodes_box_with_horizontal_alignment() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(20.0)),
justify_content: Some(ContentAlign::Center),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────╮\n│ Hello World │\n╰──────────────────╯",
"multiple nodes - box with horizontal alignment"
);
}
// ── multiple nodes - box with vertical alignment ──────────────────────────
// ink: borders.tsx "multiple nodes - box with vertical alignment"
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn borders_multiple_nodes_box_with_vertical_alignment() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
height: Some(Dim::Points(20.0)),
align_items: Some(Align::Center),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭───────────╮\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│Hello World│\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n╰───────────╯",
"multiple nodes - box with vertical alignment"
);
}
// ── multiple nodes - box with wrapping ────────────────────────────────────
// ink: borders.tsx "multiple nodes - box with wrapping"
#[test]
fn borders_multiple_nodes_box_with_wrapping() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────────╮\n│Hello │\n│World │\n╰────────╯",
"multiple nodes - box with wrapping"
);
}
// ── multiple nodes - box with wrapping and long first node ────────────────
// ink: borders.tsx "multiple nodes - box with wrapping and long first node"
// JSX: <Text>{'Helloooooo'} World</Text> → text = "Helloooooo World"
// 8-col content area: wraps after 8 chars.
#[test]
fn borders_multiple_nodes_box_with_wrapping_and_long_first_node() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Helloooooo World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────────╮\n│Helloooo│\n│oo World│\n╰────────╯",
"multiple nodes - box with wrapping and long first node"
);
}
// ── multiple nodes - box with wrapping and very long first node ───────────
// ink: borders.tsx "multiple nodes - box with wrapping and very long first node"
// JSX: <Text>{'Hellooooooooooooo'} World</Text> → text = "Hellooooooooooooo World"
#[test]
fn borders_multiple_nodes_box_with_wrapping_and_very_long_first_node() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hellooooooooooooo World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────────╮\n│Helloooo│\n│oooooooo│\n│o World │\n╰────────╯",
"multiple nodes - box with wrapping and very long first node"
);
}
// ── nested boxes ─────────────────────────────────────────────────────────
// ink: borders.tsx "nested boxes"
// Outer: borderStyle="round" width=40 padding=1
// Inner: borderStyle="round" justifyContent="center" padding=1
#[test]
fn borders_nested_boxes() {
let mut a = Arena::new();
make_root(&mut a, 0);
// outer box: width=40, padding=1, border=round
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(40.0)),
padding: Some(Lp::Points(1.0)),
..Style::default()
},
);
// inner box: border=round, justifyContent=center, padding=1
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
justify_content: Some(ContentAlign::Center),
padding: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────────────────────────╮\n│ │\n│ ╭─────────────╮ │\n│ │ │ │\n│ │ Hello World │ │\n│ │ │ │\n│ ╰─────────────╯ │\n│ │\n╰──────────────────────────────────────╯",
"nested boxes"
);
}
// ── nested boxes - fit-content box with wide characters on flex-direction row
// ink: borders.tsx "nested boxes - fit-content box with wide characters on flex-direction row"
// Outer: borderStyle="round" alignSelf="flex-start" (default row direction)
// Three inner boxes with wide-char content, side by side.
#[test]
fn borders_nested_boxes_fit_content_box_with_wide_characters_on_flex_direction_row() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "ミスター");
make_box(
&mut a,
4,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 5, "スポック");
make_box(
&mut a,
6,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 7, "カーク船長");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 1, 6);
add_child(&mut a, 6, 7);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────────────────────────────────╮\n│╭────────╮╭────────╮╭──────────╮│\n││ミスター││スポック││カーク船長││\n│╰────────╯╰────────╯╰──────────╯│\n╰────────────────────────────────╯",
"nested boxes - fit-content box with wide characters on flex-direction row"
);
}
// ── nested boxes - fit-content box with emojis on flex-direction row ──────
// ink: borders.tsx "nested boxes - fit-content box with emojis on flex-direction row"
#[test]
fn borders_nested_boxes_fit_content_box_with_emojis_on_flex_direction_row() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "🦾");
make_box(
&mut a,
4,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 5, "🌏");
make_box(
&mut a,
6,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 7, "😋");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 1, 6);
add_child(&mut a, 6, 7);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────────────╮\n│╭──╮╭──╮╭──╮│\n││🦾││🌏││😋││\n│╰──╯╰──╯╰──╯│\n╰────────────╯",
"nested boxes - fit-content box with emojis on flex-direction row"
);
}
// ── nested boxes - fit-content box with wide characters on flex-direction column
// ink: borders.tsx "nested boxes - fit-content box with wide characters on flex-direction column"
#[test]
fn borders_nested_boxes_fit_content_box_with_wide_characters_on_flex_direction_column() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "ミスター");
make_box(
&mut a,
4,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 5, "スポック");
make_box(
&mut a,
6,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 7, "カーク船長");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 1, 6);
add_child(&mut a, 6, 7);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────────────╮\n│╭──────────╮│\n││ミスター ││\n│╰──────────╯│\n│╭──────────╮│\n││スポック ││\n│╰──────────╯│\n│╭──────────╮│\n││カーク船長││\n│╰──────────╯│\n╰────────────╯",
"nested boxes - fit-content box with wide characters on flex-direction column"
);
}
// ── nested boxes - fit-content box with emojis on flex-direction column ───
// ink: borders.tsx "nested boxes - fit-content box with emojis on flex-direction column"
#[test]
fn borders_nested_boxes_fit_content_box_with_emojis_on_flex_direction_column() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "🦾");
make_box(
&mut a,
4,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 5, "🌏");
make_box(
&mut a,
6,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 7, "😋");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 1, 6);
add_child(&mut a, 6, 7);
assert_eq!(
render_to_string(&a, 0, 100),
"╭────╮\n│╭──╮│\n││🦾││\n│╰──╯│\n│╭──╮│\n││🌏││\n│╰──╯│\n│╭──╮│\n││😋││\n│╰──╯│\n╰────╯",
"nested boxes - fit-content box with emojis on flex-direction column"
);
}
// ── hide top border ───────────────────────────────────────────────────────
// ink: borders.tsx "hide top border"
// Outer column box (flexDirection=column, alignItems=flex-start) wraps
// "Above" text, a border box with borderTop=false, then "Below" text.
#[test]
fn borders_hide_top_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
// outer column box
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_top: Some(false),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n│Content│\n╰───────╯\nBelow",
"hide top border"
);
}
// ── hide bottom border ────────────────────────────────────────────────────
// ink: borders.tsx "hide bottom border"
#[test]
fn borders_hide_bottom_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_bottom: Some(false),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────╮\n│Content│\nBelow",
"hide bottom border"
);
}
// ── hide top and bottom borders ───────────────────────────────────────────
// ink: borders.tsx "hide top and bottom borders"
#[test]
fn borders_hide_top_and_bottom_borders() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_top: Some(false),
border_bottom: Some(false),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n│Content│\nBelow",
"hide top and bottom borders"
);
}
// ── hide left border ──────────────────────────────────────────────────────
// ink: borders.tsx "hide left border"
#[test]
fn borders_hide_left_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_left: Some(false),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n───────╮\nContent│\n───────╯\nBelow",
"hide left border"
);
}
// ── hide right border ─────────────────────────────────────────────────────
// ink: borders.tsx "hide right border"
#[test]
fn borders_hide_right_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_right: Some(false),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────\n│Content\n╰───────\nBelow",
"hide right border"
);
}
// ── hide left and right border ────────────────────────────────────────────
// ink: borders.tsx "hide left and right border"
#[test]
fn borders_hide_left_and_right_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_left: Some(false),
border_right: Some(false),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n───────\nContent\n───────\nBelow",
"hide left and right border"
);
}
// ── hide all borders ──────────────────────────────────────────────────────
// ink: borders.tsx "hide all borders"
#[test]
fn borders_hide_all_borders() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_top: Some(false),
border_bottom: Some(false),
border_left: Some(false),
border_right: Some(false),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\nContent\nBelow",
"hide all borders"
);
}
// ── change color of top border ────────────────────────────────────────────
// ink: borders.tsx "change color of top border"
// borderTopColor="green" → ANSI green wraps the top border line only.
// Expected: \x1b[32m╭───────╮\x1b[39m on the top line.
#[test]
fn borders_change_color_of_top_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_top_color: Some("green".to_owned()),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n\x1b[32m╭───────╮\x1b[39m\n│Content│\n╰───────╯\nBelow",
"change color of top border"
);
}
// ── change color of bottom border ─────────────────────────────────────────
// ink: borders.tsx "change color of bottom border"
#[test]
fn borders_change_color_of_bottom_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_bottom_color: Some("green".to_owned()),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────╮\n│Content│\n\x1b[32m╰───────╯\x1b[39m\nBelow",
"change color of bottom border"
);
}
// ── change color of left border ───────────────────────────────────────────
// ink: borders.tsx "change color of left border"
#[test]
fn borders_change_color_of_left_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_left_color: Some("green".to_owned()),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────╮\n\x1b[32m│\x1b[39mContent│\n╰───────╯\nBelow",
"change color of left border"
);
}
// ── change color of right border ──────────────────────────────────────────
// ink: borders.tsx "change color of right border"
#[test]
fn borders_change_color_of_right_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_right_color: Some("green".to_owned()),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────╮\n│Content\u{1b}[32m│\u{1b}[39m\n╰───────╯\nBelow",
"change color of right border"
);
}
// ── custom border style ───────────────────────────────────────────────────
// ink: borders.tsx "custom border style"
// borderStyle is an object with arrow-direction Unicode chars (= boxen "arrow" style).
// No width prop → full-width at columns=100.
#[test]
fn borders_custom_border_style() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Custom {
top_left: "↘".to_owned(),
top: "↓".to_owned(),
top_right: "↙".to_owned(),
left: "→".to_owned(),
bottom_left: "↗".to_owned(),
bottom: "↑".to_owned(),
bottom_right: "↖".to_owned(),
right: "←".to_owned(),
}),
..Style::default()
},
);
make_text(&mut a, 2, "Content");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"↘↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↙\n→Content ←\n↗↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↖",
"custom border style"
);
}
// ── dim border color ──────────────────────────────────────────────────────
// ink: borders.tsx "dim border color"
// borderDimColor is a Style field (threaded JS→core via Box's `...style`).
// The corpus renders at fixed level-3, so dim emits `\x1b[2m … \x1b[22m`
// (same SGR posture as the sibling border-color tests).
#[test]
fn borders_dim_border_color() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_dim_color: Some(true),
..Style::default()
},
);
make_text(&mut a, 2, "Content");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"\u{1b}[2m╭──────────────────────────────────────────────────────────────────────────────────────────────────╮\u{1b}[22m\n\u{1b}[2m│\u{1b}[22mContent \u{1b}[2m│\u{1b}[22m\n\u{1b}[2m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u{1b}[22m",
"dim border color"
);
}
// ── dim top border color ──────────────────────────────────────────────────
// ink: borders.tsx "dim top border color"
#[test]
fn borders_dim_top_border_color() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
// borderTopDimColor: Style field (per-edge dim).
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_top_dim_color: Some(true),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n\x1b[2m╭───────╮\x1b[22m\n│Content│\n╰───────╯\nBelow",
"dim top border color"
);
}
// ── dim bottom border color ───────────────────────────────────────────────
// ink: borders.tsx "dim bottom border color"
#[test]
fn borders_dim_bottom_border_color() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_bottom_dim_color: Some(true),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────╮\n│Content│\n\x1b[2m╰───────╯\x1b[22m\nBelow",
"dim bottom border color"
);
}
// ── dim left border color ─────────────────────────────────────────────────
// ink: borders.tsx "dim left border color"
#[test]
fn borders_dim_left_border_color() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_left_dim_color: Some(true),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────╮\n\u{1b}[2m│\u{1b}[22mContent│\n╰───────╯\nBelow",
"dim left border color"
);
}
// ── dim right border color ────────────────────────────────────────────────
// ink: borders.tsx "dim right border color"
#[test]
fn borders_dim_right_border_color() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Above");
make_box(
&mut a,
3,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
border_right_dim_color: Some(true),
..Style::default()
},
);
make_text(&mut a, 4, "Content");
make_text(&mut a, 5, "Below");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"Above\n╭───────╮\n│Content\u{1b}[2m│\u{1b}[22m\n╰───────╯\nBelow",
"dim right border color"
);
}
// ── single node - full width box - concurrent ─────────────────────────────
// ink: borders.tsx "single node - full width box - concurrent"
// Async concurrent-mode variant; same layout as the sync version.
#[test]
fn borders_single_node_full_width_box_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮\n│Hello World │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯",
"single node - full width box - concurrent"
);
}
// ── single node - fit-content box - concurrent ────────────────────────────
// ink: borders.tsx "single node - fit-content box - concurrent"
#[test]
fn borders_single_node_fit_content_box_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
align_self: Some(Align::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"╭───────────╮\n│Hello World│\n╰───────────╯",
"single node - fit-content box - concurrent"
);
}
// ── nested boxes - concurrent ─────────────────────────────────────────────
// ink: borders.tsx "nested boxes - concurrent"
// Async concurrent-mode variant; same layout as "nested boxes".
#[test]
fn borders_nested_boxes_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
width: Some(Dim::Points(40.0)),
padding: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("round".to_owned())),
justify_content: Some(ContentAlign::Center),
padding: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"╭──────────────────────────────────────╮\n│ │\n│ ╭─────────────╮ │\n│ │ │ │\n│ │ Hello World │ │\n│ │ │ │\n│ ╰─────────────╯ │\n│ │\n╰──────────────────────────────────────╯",
"nested boxes - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod flex (flex.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod flex {
use super::helpers::{add_child, make_box, make_root_zero as make_root, make_text};
use inkferro_core::dom::{Arena, Dim, FlexDir, Style};
use inkferro_core::render::render_to_string;
// ── flex_grow_equally ────────────────────────────────────────────────────
// ink: flex.tsx "grow equally"
// JSX: <Box width={6}><Box flexGrow={1}><Text>A</Text></Box>
// <Box flexGrow={1}><Text>B</Text></Box></Box>
// Both children grow equally → each 3 cols. A@0, B@3; trailing trimmed → "A B"
#[test]
fn flex_grow_equally() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_grow: Some(1.0),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_box(
&mut a,
4,
Style {
flex_grow: Some(1.0),
..Style::default()
},
);
make_text(&mut a, 5, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
assert_eq!(render_to_string(&a, 0, 100), "A B", "grow equally");
}
// ── flex_grow_one_element ────────────────────────────────────────────────
// ink: flex.tsx "grow one element"
// JSX: <Box width={6}><Box flexGrow={1}><Text>A</Text></Box><Text>B</Text></Box>
// Child 1 grows to fill remaining 5 cols; Text B natural width=1.
// A@0(5-wide box), B@5; trailing trimmed → "A B"
#[test]
fn flex_grow_one_element() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_grow: Some(1.0),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 100), "A B", "grow one element");
}
// ── flex_do_not_shrink ───────────────────────────────────────────────────
// ink: flex.tsx "do not shrink"
// JSX: <Box width={16}>
// <Box flexShrink={0} width={6}><Text>A</Text></Box>
// <Box flexShrink={0} width={6}><Text>B</Text></Box>
// <Box width={6}><Text>C</Text></Box>
// </Box>
// Total nominal=18 > 16. Children 1+2 refuse to shrink; child 3 absorbs
// all slack → gets 4 cols. A@0(6), B@6(6), C@12(4); trailing → "A B C"
#[test]
fn flex_do_not_shrink() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(16.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_shrink: Some(0.0),
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_box(
&mut a,
4,
Style {
flex_shrink: Some(0.0),
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_text(&mut a, 5, "B");
make_box(
&mut a,
6,
Style {
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_text(&mut a, 7, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 1, 6);
add_child(&mut a, 6, 7);
assert_eq!(
render_to_string(&a, 0, 100),
"A B C",
"do not shrink"
);
}
// ── flex_shrink_equally ──────────────────────────────────────────────────
// ink: flex.tsx "shrink equally"
// JSX: <Box width={10}>
// <Box flexShrink={1} width={6}><Text>A</Text></Box>
// <Box flexShrink={1} width={6}><Text>B</Text></Box>
// <Text>C</Text>
// </Box>
// Nominal=13 > 10; proportional shrink: child1→5, child2→4, child3(Text)→1.
// A@0(5), B@5(4), C@9(1); trailing → "A B C"
#[test]
fn flex_shrink_equally() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_shrink: Some(1.0),
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_box(
&mut a,
4,
Style {
flex_shrink: Some(1.0),
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_text(&mut a, 5, "B");
make_text(&mut a, 6, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 1, 6);
assert_eq!(render_to_string(&a, 0, 100), "A B C", "shrink equally");
}
// ── flex_basis_row ───────────────────────────────────────────────────────
// ink: flex.tsx "set flex basis with flexDirection="row" container"
// JSX: <Box width={6}><Box flexBasis={3}><Text>A</Text></Box><Text>B</Text></Box>
// Row direction; child1 flexBasis=3 → 3 cols. Text B gets 3 cols (natural=1).
// A@0(3), B@3; trailing → "A B"
#[test]
fn flex_basis_row() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_basis: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"A B",
"set flex basis with flexDirection=\"row\" container"
);
}
// ── flex_basis_percent_row ───────────────────────────────────────────────
// ink: flex.tsx "set flex basis in percent with flexDirection="row" container"
// JSX: <Box width={6}><Box flexBasis="50%"><Text>A</Text></Box><Text>B</Text></Box>
// 50% of 6 = 3 cols for child1. Same layout as flex_basis_row → "A B"
#[test]
fn flex_basis_percent_row() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_basis: Some(Dim::Percent(50.0)),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"A B",
"set flex basis in percent with flexDirection=\"row\" container"
);
}
// ── flex_basis_column ────────────────────────────────────────────────────
// ink: flex.tsx "set flex basis with flexDirection="column" container"
// JSX: <Box height={6} flexDirection="column">
// <Box flexBasis={3}><Text>A</Text></Box>
// <Text>B</Text>
// </Box>
// Column direction, height=6. Child1 flexBasis=3 → rows 0-2. Text B → row 3.
// Rows 4-5 empty. Trailing-per-line trimmed → "A\n\n\nB\n\n"
#[test]
fn flex_basis_column() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(6.0)),
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_basis: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"A\n\n\nB\n\n",
"set flex basis with flexDirection=\"column\" container"
);
}
// ── flex_basis_percent_column ────────────────────────────────────────────
// ink: flex.tsx "set flex basis in percent with flexDirection="column" container"
// JSX: <Box height={6} flexDirection="column">
// <Box flexBasis="50%"><Text>A</Text></Box>
// <Text>B</Text>
// </Box>
// 50% of 6 = 3 rows for child1. Same layout as flex_basis_column → "A\n\n\nB\n\n"
#[test]
fn flex_basis_percent_column() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(6.0)),
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_basis: Some(Dim::Percent(50.0)),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"A\n\n\nB\n\n",
"set flex basis in percent with flexDirection=\"column\" container"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod flex_align_items (flex-align-items.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod flex_align_items {
use super::helpers::{add_child, make_box, make_root_zero as make_root, make_text};
use inkferro_core::dom::{Align, Arena, BorderStyle, Dim, FlexDir, Style};
use inkferro_core::render::render_to_string;
// ── fai_row_align_text_to_center ─────────────────────────────────────────
// ink: <Box alignItems="center" height={3}><Text>Test</Text></Box>
// Row direction (default). alignItems="center" with height=3 centers the
// single text node vertically: one blank line above, text, one blank line below.
#[test]
fn fai_row_align_text_to_center() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
align_items: Some(Align::Center),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"\nTest\n",
"row - align text to center"
);
}
// ── fai_row_align_multiple_text_nodes_to_center ───────────────────────────
// ink: <Box alignItems="center" height={3}><Text>A</Text><Text>B</Text></Box>
// Row direction (default). alignItems="center" with height=3 and two inline
// Text children rendered side-by-side, centered vertically.
#[test]
fn fai_row_align_multiple_text_nodes_to_center() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
align_items: Some(Align::Center),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"\nAB\n",
"row - align multiple text nodes to center"
);
}
// ── fai_row_align_text_to_bottom ─────────────────────────────────────────
// ink: <Box alignItems="flex-end" height={3}><Text>Test</Text></Box>
// Row direction (default). alignItems="flex-end" with height=3 places text
// at the bottom: two blank lines above, text at end (no trailing newline).
#[test]
fn fai_row_align_text_to_bottom() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
align_items: Some(Align::FlexEnd),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"\n\nTest",
"row - align text to bottom"
);
}
// ── fai_row_align_multiple_text_nodes_to_bottom ───────────────────────────
// ink: <Box alignItems="flex-end" height={3}><Text>A</Text><Text>B</Text></Box>
// Row direction (default). alignItems="flex-end" with height=3 and two inline
// Text children placed at bottom.
#[test]
fn fai_row_align_multiple_text_nodes_to_bottom() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
align_items: Some(Align::FlexEnd),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"\n\nAB",
"row - align multiple text nodes to bottom"
);
}
// ── fai_column_align_text_to_center ──────────────────────────────────────
// ink: <Box flexDirection="column" alignItems="center" width={10}><Text>Test</Text></Box>
// Column direction. alignItems="center" with width=10 centers "Test" (4 chars)
// horizontally: floor((10-4)/2)=3 leading spaces. Result is single line ' Test'.
#[test]
fn fai_column_align_text_to_center() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::Center),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
" Test",
"column - align text to center"
);
}
// ── fai_column_align_text_to_right ───────────────────────────────────────
// ink: <Box flexDirection="column" alignItems="flex-end" width={10}><Text>Test</Text></Box>
// Column direction. alignItems="flex-end" with width=10 right-aligns "Test"
// (4 chars): 10-4=6 leading spaces. Result is single line ' Test'.
#[test]
fn fai_column_align_text_to_right() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
align_items: Some(Align::FlexEnd),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
" Test",
"column - align text to right"
);
}
// ── fai_row_align_items_stretch ──────────────────────────────────────────
// ink: <Box alignItems="stretch" height={5}><Box borderStyle="single"><Text>X</Text></Box></Box>
// Row direction. alignItems="stretch" with height=5 causes the inner Box with
// borderStyle="single" to stretch to fill the full 5-row height.
// Border glyphs: ┌─┐ top, │X│ content row, │ │ empty rows, └─┘ bottom.
#[test]
fn fai_row_align_items_stretch() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
align_items: Some(Align::Stretch),
height: Some(Dim::Points(5.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("single".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"┌─┐\n│X│\n│ │\n│ │\n└─┘",
"row - align items stretch"
);
}
// ── fai_row_default_align_items_stretches_children ───────────────────────
// ink: <Box height={5}><Box borderStyle="single"><Text>X</Text></Box></Box>
// Row direction with no explicit alignItems (defaults to "stretch"). Same
// expected output as the explicit stretch test. Verifies that default flex
// alignment is stretch.
#[test]
fn fai_row_default_align_items_stretches_children() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(5.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
border_style: Some(BorderStyle::Named("single".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"┌─┐\n│X│\n│ │\n│ │\n└─┘",
"row - default align items stretches children"
);
}
// ── fai_row_align_text_to_baseline ───────────────────────────────────────
// ink: <Box alignItems="baseline" height={3}>
// <Text>A<Newline/>B</Text>
// <Text>X</Text>
// </Box>
// Row direction. alignItems="baseline" with height=3.
// First Text child: leaf Text node with text="A\nB" (two lines, height=2).
// <Newline/> squashes to '\n'; the whole first child squashes to "A\nB".
// Modelled as a single leaf (no VirtualText sub-children) because taffy
// treats any node with children as a container and ignores its measure fn;
// the squash result "A\nB" is what ink-text presents to yoga.
// Second Text child: leaf Text node with text="X" → height=1.
// Ink uses Yoga's ALIGN_BASELINE treating the bottom edge as the baseline.
// Both items align their bottom edges: the 2-line child occupies rows 0-1,
// the 1-line child aligns its bottom to row 1.
// Result row-by-row: row0="A", row1="BX", row2="" (trailing newline from height=3).
// Expected: 'A\nBX\n'
#[test]
fn fai_row_align_text_to_baseline() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
align_items: Some(Align::Baseline),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
// First Text child: squashed content of <Text>A<Newline/>B</Text> = "A\nB".
make_text(&mut a, 2, "A\nB");
// Second Text child: <Text>X</Text>.
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"A\nBX\n",
"row - align text to baseline"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod flex_justify_content (flex-justify-content.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod flex_justify_content {
use super::helpers::{add_child, make_box, make_root_zero as make_root, make_text};
use inkferro_core::dom::{Arena, ContentAlign, Dim, FlexDir, Style};
use inkferro_core::render::render_to_string;
// ── fjc_row_align_text_to_center ─────────────────────────────────────────
// ink: <Box justifyContent="center" width={10}><Text>Test</Text></Box>
// width=10, "Test" is 4 chars; center: (10-4)/2 = 3 spaces prefix.
#[test]
fn fjc_row_align_text_to_center() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
justify_content: Some(ContentAlign::Center),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
" Test",
"row - align text to center"
);
}
// ── fjc_row_align_multiple_text_nodes_to_center ───────────────────────────
// ink: <Box justifyContent="center" width={10}><Text>A</Text><Text>B</Text></Box>
// width=10, "AB" is 2 chars; center: (10-2)/2 = 4 spaces prefix.
#[test]
fn fjc_row_align_multiple_text_nodes_to_center() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
justify_content: Some(ContentAlign::Center),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
" AB",
"row - align multiple text nodes to center"
);
}
// ── fjc_row_align_text_to_right ───────────────────────────────────────────
// ink: <Box justifyContent="flex-end" width={10}><Text>Test</Text></Box>
// width=10, "Test" is 4 chars; flex-end: 6 spaces prefix.
#[test]
fn fjc_row_align_text_to_right() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
justify_content: Some(ContentAlign::FlexEnd),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
" Test",
"row - align text to right"
);
}
// ── fjc_row_align_multiple_text_nodes_to_right ────────────────────────────
// ink: <Box justifyContent="flex-end" width={10}><Text>A</Text><Text>B</Text></Box>
// width=10, "AB" is 2 chars; flex-end: 8 spaces prefix.
#[test]
fn fjc_row_align_multiple_text_nodes_to_right() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
justify_content: Some(ContentAlign::FlexEnd),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
" AB",
"row - align multiple text nodes to right"
);
}
// ── fjc_row_align_two_text_nodes_on_the_edges ─────────────────────────────
// ink: <Box justifyContent="space-between" width={4}><Text>A</Text><Text>B</Text></Box>
// width=4, 2 children each 1 char; space-between: 2 spaces between A and B.
#[test]
fn fjc_row_align_two_text_nodes_on_the_edges() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
justify_content: Some(ContentAlign::SpaceBetween),
width: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"A B",
"row - align two text nodes on the edges"
);
}
// ── fjc_row_space_evenly_two_text_nodes ───────────────────────────────────
// ink: <Box justifyContent="space-evenly" width={10}><Text>A</Text><Text>B</Text></Box>
// width=10, 2 children each 1 char; 8 remaining spaces into 3 slots.
// floor(8/3)=2 for outer, 3 for middle (remainder). Output: " A B" (trailing trimmed).
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn fjc_row_space_evenly_two_text_nodes() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
justify_content: Some(ContentAlign::SpaceEvenly),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
" A B",
"row - space evenly two text nodes"
);
}
// ── fjc_col_align_text_to_center ──────────────────────────────────────────
// ink: <Box flexDirection="column" justifyContent="center" height={3}><Text>Test</Text></Box>
// height=3, 1 row content; center: placed at row 1 → "\nTest\n".
#[test]
fn fjc_col_align_text_to_center() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
justify_content: Some(ContentAlign::Center),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"\nTest\n",
"column - align text to center"
);
}
// ── fjc_col_align_text_to_bottom ──────────────────────────────────────────
// ink: <Box flexDirection="column" justifyContent="flex-end" height={3}><Text>Test</Text></Box>
// height=3, 1 row content; flex-end: placed at row 2 → "\n\nTest".
#[test]
fn fjc_col_align_text_to_bottom() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
justify_content: Some(ContentAlign::FlexEnd),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"\n\nTest",
"column - align text to bottom"
);
}
// ── fjc_col_align_two_text_nodes_on_the_edges ─────────────────────────────
// ink: <Box flexDirection="column" justifyContent="space-between" height={4}>
// <Text>A</Text><Text>B</Text></Box>
// height=4, 2 children; space-between: A at row 0, B at row 3 → "A\n\n\nB".
#[test]
fn fjc_col_align_two_text_nodes_on_the_edges() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
justify_content: Some(ContentAlign::SpaceBetween),
height: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"A\n\n\nB",
"column - align two text nodes on the edges"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod margin (margin.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod margin {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Dim, Lp, Style};
use inkferro_core::render::render_to_string;
// ── margin ───────────────────────────────────────────────────────────────
// ink: margin.tsx "margin"
// JSX: <Box margin={2}><Text>X</Text></Box>
// Expected: '\n\n X\n\n'
#[test]
fn margin_margin() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "\n\n X\n\n", "margin");
}
// ── margin X ─────────────────────────────────────────────────────────────
// ink: margin.tsx "margin X"
// JSX: <Box><Box marginX={2}><Text>X</Text></Box><Text>Y</Text></Box>
// Expected: ' X Y'
#[test]
fn margin_margin_x() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(&mut a, 1, Style::default());
make_box(
&mut a,
2,
Style {
margin_x: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
make_text(&mut a, 4, "Y");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 80), " X Y", "margin X");
}
// ── margin Y ─────────────────────────────────────────────────────────────
// ink: margin.tsx "margin Y"
// JSX: <Box marginY={2}><Text>X</Text></Box>
// Expected: '\n\nX\n\n'
#[test]
fn margin_margin_y() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin_y: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "\n\nX\n\n", "margin Y");
}
// ── margin top ───────────────────────────────────────────────────────────
// ink: margin.tsx "margin top"
// JSX: <Box marginTop={2}><Text>X</Text></Box>
// Expected: '\n\nX'
#[test]
fn margin_margin_top() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin_top: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "\n\nX", "margin top");
}
// ── margin bottom ────────────────────────────────────────────────────────
// ink: margin.tsx "margin bottom"
// JSX: <Box marginBottom={2}><Text>X</Text></Box>
// Expected: 'X\n\n'
#[test]
fn margin_margin_bottom() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin_bottom: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "X\n\n", "margin bottom");
}
// ── margin left ──────────────────────────────────────────────────────────
// ink: margin.tsx "margin left"
// JSX: <Box marginLeft={2}><Text>X</Text></Box>
// Expected: ' X'
#[test]
fn margin_margin_left() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin_left: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), " X", "margin left");
}
// ── margin right ─────────────────────────────────────────────────────────
// ink: margin.tsx "margin right"
// JSX: <Box><Box marginRight={2}><Text>X</Text></Box><Text>Y</Text></Box>
// Expected: 'X Y'
#[test]
fn margin_margin_right() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(&mut a, 1, Style::default());
make_box(
&mut a,
2,
Style {
margin_right: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
make_text(&mut a, 4, "Y");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 80), "X Y", "margin right");
}
// ── nested margin ────────────────────────────────────────────────────────
// ink: margin.tsx "nested margin"
// JSX: <Box margin={2}><Box margin={2}><Text>X</Text></Box></Box>
// Expected: '\n\n\n\n X\n\n\n\n'
#[test]
fn margin_nested_margin() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n\n\n X\n\n\n\n",
"nested margin"
);
}
// ── margin with multiline string ─────────────────────────────────────────
// ink: margin.tsx "margin with multiline string"
// JSX: <Box margin={2}><Text>{'A\nB'}</Text></Box>
// Text content is the string literal "A\nB" (real newline).
// Expected: '\n\n A\n B\n\n'
#[test]
fn margin_margin_with_multiline_string() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A\nB");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n A\n B\n\n",
"margin with multiline string"
);
}
// ── apply margin to text with newlines ───────────────────────────────────
// ink: margin.tsx "apply margin to text with newlines"
// JSX: <Box margin={1}><Text>Hello{'\n'}World</Text></Box>
// JSX adjacent expressions squash to "Hello\nWorld".
// Expected: '\n Hello\n World\n'
#[test]
fn margin_apply_margin_to_text_with_newlines() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n Hello\n World\n",
"apply margin to text with newlines"
);
}
// ── apply margin to wrapped text ─────────────────────────────────────────
// ink: margin.tsx "apply margin to wrapped text"
// JSX: <Box margin={1} width={6}><Text>Hello World</Text></Box>
// width=6 on the Box; margin=1 all sides. Inner width = 6 - 2 = 4, but
// ink margin is outside layout so the box is width=6 total. Text wraps
// "Hello" / "World" inside the 5-char effective inner space.
// Expected: '\n Hello\n World\n'
#[test]
fn margin_apply_margin_to_wrapped_text() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin: Some(Lp::Points(1.0)),
width: Some(Dim::Points(6.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n Hello\n World\n",
"apply margin to wrapped text"
);
}
// ── margin - concurrent ──────────────────────────────────────────────────
// ink: margin.tsx "margin - concurrent"
// JSX: <Box margin={2}><Text>X</Text></Box> (renderToStringAsync)
// Concurrent mode; same tree and output as synchronous 'margin' test.
// Expected: '\n\n X\n\n'
#[test]
fn margin_margin_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n X\n\n",
"margin - concurrent"
);
}
// ── nested margin - concurrent ───────────────────────────────────────────
// ink: margin.tsx "nested margin - concurrent"
// JSX: <Box margin={2}><Box margin={2}><Text>X</Text></Box></Box>
// (renderToStringAsync) Same tree and output as 'nested margin'.
// Expected: '\n\n\n\n X\n\n\n\n'
#[test]
fn margin_nested_margin_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
margin: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n\n\n X\n\n\n\n",
"nested margin - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod measure_element (measure-element.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod measure_element {
use super::helpers::{add_child, make_box, make_root_zero as make_root, make_text};
use inkferro_core::dom::{Arena, FlexDir, Style};
use inkferro_core::render::render_to_string;
// ── me_measure_element_after_state_update ────────────────────────────────
// ink: measure-element.tsx "measure element after state update"
// Block [1]: "measure element after state update"
//
// Final settled DOM (after render → delay(50) → setTestItems(['line 1','line 2','line 3'])
// → delay(50), useEffect fires and sets height=3):
//
// <Box flexDirection="column"> n1
// <Box ref={ref} flexDirection="column"> n2
// <Text>line 1</Text> n3
// <Text>line 2</Text> n4
// <Text>line 3</Text> n5
// </Box>
// <Text>Height: 3</Text> n6
// </Box>
//
// Expected (manifest-literal): "line 1\nline 2\nline 3\nHeight: 3"
#[test]
fn me_measure_element_after_state_update() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_text(&mut a, 3, "line 1");
make_text(&mut a, 4, "line 2");
make_text(&mut a, 5, "line 3");
make_text(&mut a, 6, "Height: 3");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 2, 4);
add_child(&mut a, 2, 5);
add_child(&mut a, 1, 6);
assert_eq!(
render_to_string(&a, 0, 100),
"line 1\nline 2\nline 3\nHeight: 3",
"measure element after state update"
);
}
// ── me_measure_element_after_multiple_state_updates ──────────────────────
// ink: measure-element.tsx "measure element after multiple state updates"
// Block [2]: "measure element after multiple state updates"
//
// Final settled DOM (after render → delay(50) → setTestItems(['line 1','line 2','line 3'])
// → delay(50) → setTestItems(['line 1']) → delay(50), useEffect fires and sets height=1):
//
// <Box flexDirection="column"> n1
// <Box ref={ref} flexDirection="column"> n2
// <Text>line 1</Text> n3
// </Box>
// <Text>Height: 1</Text> n4
// </Box>
//
// Expected (manifest-literal): "line 1\nHeight: 1"
#[test]
fn me_measure_element_after_multiple_state_updates() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_text(&mut a, 3, "line 1");
make_text(&mut a, 4, "Height: 1");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"line 1\nHeight: 1",
"measure element after multiple state updates"
);
}
// ── me_measure_element_in_use_layout_effect_after_state_update ───────────
// ink: measure-element.tsx "measure element in useLayoutEffect after state update"
// Block [3]: "measure element in useLayoutEffect after state update"
//
// Identical final DOM to block [1] — useLayoutEffect vs useEffect changes
// render-cycle timing but not the settled frame shape.
//
// Final settled DOM:
// <Box flexDirection="column"> n1
// <Box ref={ref} flexDirection="column"> n2
// <Text>line 1</Text> n3
// <Text>line 2</Text> n4
// <Text>line 3</Text> n5
// </Box>
// <Text>Height: 3</Text> n6
// </Box>
//
// Expected (manifest-literal): "line 1\nline 2\nline 3\nHeight: 3"
#[test]
fn me_measure_element_in_use_layout_effect_after_state_update() {
let mut a = Arena::new();
make_root(&mut a);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_text(&mut a, 3, "line 1");
make_text(&mut a, 4, "line 2");
make_text(&mut a, 5, "line 3");
make_text(&mut a, 6, "Height: 3");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 2, 4);
add_child(&mut a, 2, 5);
add_child(&mut a, 1, 6);
assert_eq!(
render_to_string(&a, 0, 100),
"line 1\nline 2\nline 3\nHeight: 3",
"measure element in useLayoutEffect after state update"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod overflow (overflow.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod overflow {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, BorderStyle, Dim, FlexDir, Lp, Overflow, Style};
use inkferro_core::render::render_to_string;
// ink: overflow.tsx "overflowX - single text node in a box inside overflow container"
// ink default width=80; overflowX="hidden" resolves to overflow_x=Hidden (Box.tsx:90-92 shorthand).
#[test]
fn ov_overflow_x_single_text_node_in_a_box_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(16.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello",
"overflowX - single text node in a box inside overflow container"
);
}
// ink: overflow.tsx "overflowX - single text node inside overflow container with border" (oracle-materialized)
// idx=1 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: boxen('Hell', {borderStyle:'round'}) = "╭────╮\n│Hell│\n╰────╯"
// JSX: <Box width={6} overflowX="hidden" borderStyle="round"><Box width={16} flexShrink={0}><Text>Hello World</Text></Box></Box>
#[test]
fn ov_overflow_x_single_text_node_inside_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(16.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭────╮\n│Hell│\n╰────╯",
"overflowX - single text node inside overflow container with border"
);
}
// ink: overflow.tsx "overflowX - single text node in a box with border inside overflow container" (oracle-materialized)
// idx=2 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: clipX(box('Hello'), 6) = "╭─────\n│Hello\n╰─────"
// JSX: <Box width={6} overflowX="hidden"><Box width={16} flexShrink={0} borderStyle="round"><Text>Hello World</Text></Box></Box>
#[test]
fn ov_overflow_x_single_text_node_in_a_box_with_border_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(16.0)),
flex_shrink: Some(0.0),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭─────\n│Hello\n╰─────",
"overflowX - single text node in a box with border inside overflow container"
);
}
// ink: overflow.tsx "overflowX - multiple text nodes in a box inside overflow container"
// idx=3 · excluded=false · manifest-literal
// JSX: <Box width={6} overflowX="hidden"><Box width={12} flexShrink={0}><Text>Hello </Text><Text>World</Text></Box></Box>
// Two sibling Text nodes; trailing space trimmed by renderer.
#[test]
fn ov_overflow_x_multiple_text_nodes_in_a_box_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(12.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello ");
make_text(&mut a, 4, "World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 2, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello",
"overflowX - multiple text nodes in a box inside overflow container"
);
}
// ink: overflow.tsx "overflowX - multiple text nodes in a box inside overflow container with border" (oracle-materialized)
// idx=4 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box('Hello ') = "╭──────╮\n│Hello │\n╰──────╯"
// JSX: <Box width={8} overflowX="hidden" borderStyle="round"><Box width={12} flexShrink={0}><Text>Hello </Text><Text>World</Text></Box></Box>
#[test]
fn ov_overflow_x_multiple_text_nodes_in_a_box_inside_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(8.0)),
overflow_x: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(12.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello ");
make_text(&mut a, 4, "World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 2, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"╭──────╮\n│Hello │\n╰──────╯",
"overflowX - multiple text nodes in a box inside overflow container with border"
);
}
// ink: overflow.tsx "overflowX - multiple text nodes in a box with border inside overflow container" (oracle-materialized)
// idx=5 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: clipX(box('HelloWo\n'), 8) = "╭───────\n│HelloWo\n│\n╰───────"
// JSX: <Box width={8} overflowX="hidden"><Box width={12} flexShrink={0} borderStyle="round"><Text>Hello </Text><Text>World</Text></Box></Box>
#[test]
fn ov_overflow_x_multiple_text_nodes_in_a_box_with_border_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(8.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(12.0)),
flex_shrink: Some(0.0),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "Hello ");
make_text(&mut a, 4, "World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 2, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"╭───────\n│HelloWo\n│\n╰───────",
"overflowX - multiple text nodes in a box with border inside overflow container"
);
}
// ink: overflow.tsx "overflowX - multiple boxes inside overflow container"
// idx=6 · excluded=false · manifest-literal
// JSX: <Box width={6} overflowX="hidden"><Box width={6} flexShrink={0}><Text>Hello </Text></Box><Box width={6} flexShrink={0}><Text>World</Text></Box></Box>
#[test]
fn ov_overflow_x_multiple_boxes_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello ");
make_box(
&mut a,
4,
Style {
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 5, "World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello",
"overflowX - multiple boxes inside overflow container"
);
}
// ink: overflow.tsx "overflowX - multiple boxes inside overflow container with border" (oracle-materialized)
// idx=7 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box('Hello ') = "╭──────╮\n│Hello │\n╰──────╯"
// JSX: <Box width={8} overflowX="hidden" borderStyle="round"><Box width={6} flexShrink={0}><Text>Hello </Text></Box><Box width={6} flexShrink={0}><Text>World</Text></Box></Box>
#[test]
fn ov_overflow_x_multiple_boxes_inside_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(8.0)),
overflow_x: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello ");
make_box(
&mut a,
4,
Style {
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 5, "World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 5);
assert_eq!(
render_to_string(&a, 0, 80),
"╭──────╮\n│Hello │\n╰──────╯",
"overflowX - multiple boxes inside overflow container with border"
);
}
// ink: overflow.tsx "overflowX - box before left edge of overflow container"
// idx=8 · excluded=false · manifest-literal
// JSX: <Box width={6} overflowX="hidden"><Box marginLeft={-12} width={6} flexShrink={0}><Text>Hello</Text></Box></Box>
// Child entirely before the left edge → empty output.
#[test]
fn ov_overflow_x_box_before_left_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_left: Some(Lp::Points(-12.0)),
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"",
"overflowX - box before left edge of overflow container"
);
}
// ink: overflow.tsx "overflowX - box before left edge of overflow container with border" (oracle-materialized)
// idx=9 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box(' ') = "╭────╮\n│ │\n╰────╯"
// JSX: <Box width={6} overflowX="hidden" borderStyle="round"><Box marginLeft={-12} width={6} flexShrink={0}><Text>Hello</Text></Box></Box>
#[test]
fn ov_overflow_x_box_before_left_edge_of_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_left: Some(Lp::Points(-12.0)),
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭────╮\n│ │\n╰────╯",
"overflowX - box before left edge of overflow container with border"
);
}
// ink: overflow.tsx "overflowX - box intersecting with left edge of overflow container"
// idx=10 · excluded=false · manifest-literal
// JSX: <Box width={6} overflowX="hidden"><Box marginLeft={-3} width={12} flexShrink={0}><Text>Hello World</Text></Box></Box>
// Child at x=-3, width=12. Visible cols 0..6 = chars 3..9 of 'Hello World' = 'lo Wor'.
#[test]
fn ov_overflow_x_box_intersecting_with_left_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_left: Some(Lp::Points(-3.0)),
width: Some(Dim::Points(12.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"lo Wor",
"overflowX - box intersecting with left edge of overflow container"
);
}
// ink: overflow.tsx "overflowX - box intersecting with left edge of overflow container with border" (oracle-materialized)
// idx=11 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box('lo Wor') = "╭──────╮\n│lo Wor│\n╰──────╯"
// JSX: <Box width={8} overflowX="hidden" borderStyle="round"><Box marginLeft={-3} width={12} flexShrink={0}><Text>Hello World</Text></Box></Box>
#[test]
fn ov_overflow_x_box_intersecting_with_left_edge_of_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(8.0)),
overflow_x: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_left: Some(Lp::Points(-3.0)),
width: Some(Dim::Points(12.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭──────╮\n│lo Wor│\n╰──────╯",
"overflowX - box intersecting with left edge of overflow container with border"
);
}
// ink: overflow.tsx "overflowX - box after right edge of overflow container"
// idx=12 · excluded=false · manifest-literal
// JSX: <Box width={6} overflowX="hidden"><Box marginLeft={6} width={6} flexShrink={0}><Text>Hello</Text></Box></Box>
// Child starts at x=6, beyond right edge (container width=6). Empty output.
#[test]
fn ov_overflow_x_box_after_right_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_left: Some(Lp::Points(6.0)),
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"",
"overflowX - box after right edge of overflow container"
);
}
// ink: overflow.tsx "overflowX - box intersecting with right edge of overflow container"
// idx=13 · excluded=false · manifest-literal
// JSX: <Box width={6} overflowX="hidden"><Box marginLeft={3} width={6} flexShrink={0}><Text>Hello</Text></Box></Box>
// Child at x=3 width=6. Visible cols 0..2 = spaces, 3..5 = 'Hel'.
#[test]
fn ov_overflow_x_box_intersecting_with_right_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_left: Some(Lp::Points(3.0)),
width: Some(Dim::Points(6.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
" Hel",
"overflowX - box intersecting with right edge of overflow container"
);
}
// ─────────────────────────────────────────────────────────────────────────
// overflowY tests
// ─────────────────────────────────────────────────────────────────────────
// ink: overflow.tsx "overflowY - single text node inside overflow container"
// idx=14 · excluded=false · manifest-literal
// JSX: <Box height={1} overflowY="hidden"><Text>Hello{'\n'}World</Text></Box>
// Text has embedded newline; height=1 clips to first line.
#[test]
fn ov_overflow_y_single_text_node_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(1.0)),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_text(&mut a, 2, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello",
"overflowY - single text node inside overflow container"
);
}
// ink: overflow.tsx "overflowY - single text node inside overflow container with border" (oracle-materialized)
// idx=15 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box('Hello'.padEnd(18, ' ')) = "╭──────────────────╮\n│Hello │\n╰──────────────────╯"
// JSX: <Box width={20} height={3} overflowY="hidden" borderStyle="round"><Text>Hello{'\n'}World</Text></Box>
#[test]
fn ov_overflow_y_single_text_node_inside_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(20.0)),
height: Some(Dim::Points(3.0)),
overflow_y: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_text(&mut a, 2, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"╭──────────────────╮\n│Hello │\n╰──────────────────╯",
"overflowY - single text node inside overflow container with border"
);
}
// ink: overflow.tsx "overflowY - multiple boxes inside overflow container"
// idx=16 · excluded=false · manifest-literal
// JSX: 4 boxes stacked, height=2 clips to first two lines.
#[test]
fn ov_overflow_y_multiple_boxes_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(2.0)),
overflow_y: Some(Overflow::Hidden),
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 6, "Line #1");
make_box(
&mut a,
3,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 7, "Line #2");
make_box(
&mut a,
4,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 8, "Line #3");
make_box(
&mut a,
5,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 9, "Line #4");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 6);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 7);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 8);
add_child(&mut a, 1, 5);
add_child(&mut a, 5, 9);
assert_eq!(
render_to_string(&a, 0, 80),
"Line #1\nLine #2",
"overflowY - multiple boxes inside overflow container"
);
}
// ink: overflow.tsx "overflowY - multiple boxes inside overflow container with border" (oracle-materialized)
// idx=17 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box('Line #1\nLine #2') = "╭───────╮\n│Line #1│\n│Line #2│\n╰───────╯"
// JSX: <Box width={9} height={4} overflowY="hidden" flexDirection="column" borderStyle="round"> 4 children </Box>
#[test]
fn ov_overflow_y_multiple_boxes_inside_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(9.0)),
height: Some(Dim::Points(4.0)),
overflow_y: Some(Overflow::Hidden),
flex_direction: Some(FlexDir::Column),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 6, "Line #1");
make_box(
&mut a,
3,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 7, "Line #2");
make_box(
&mut a,
4,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 8, "Line #3");
make_box(
&mut a,
5,
Style {
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 9, "Line #4");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 6);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 7);
add_child(&mut a, 1, 4);
add_child(&mut a, 4, 8);
add_child(&mut a, 1, 5);
add_child(&mut a, 5, 9);
assert_eq!(
render_to_string(&a, 0, 80),
"╭───────╮\n│Line #1│\n│Line #2│\n╰───────╯",
"overflowY - multiple boxes inside overflow container with border"
);
}
// ink: overflow.tsx "overflowY - box above top edge of overflow container"
// idx=18 · excluded=false · manifest-literal
// JSX: <Box height={1} overflowY="hidden"><Box marginTop={-2} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
// Child at marginTop=-2, height=2; ends at row -1 (above container top). Empty.
#[test]
fn ov_overflow_y_box_above_top_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(1.0)),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(-2.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"",
"overflowY - box above top edge of overflow container"
);
}
// ink: overflow.tsx "overflowY - box above top edge of overflow container with border" (oracle-materialized)
// idx=19 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box(' ') = "╭─────╮\n│ │\n╰─────╯"
// JSX: <Box width={7} height={3} overflowY="hidden" borderStyle="round"><Box marginTop={-3} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
#[test]
fn ov_overflow_y_box_above_top_edge_of_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(7.0)),
height: Some(Dim::Points(3.0)),
overflow_y: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(-3.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭─────╮\n│ │\n╰─────╯",
"overflowY - box above top edge of overflow container with border"
);
}
// ink: overflow.tsx "overflowY - box intersecting with top edge of overflow container"
// idx=20 · excluded=false · manifest-literal
// JSX: <Box height={1} overflowY="hidden"><Box marginTop={-1} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
// Child at marginTop=-1, height=2: row 0='Hello' (above), row 1='World' (visible).
#[test]
fn ov_overflow_y_box_intersecting_with_top_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(1.0)),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(-1.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"World",
"overflowY - box intersecting with top edge of overflow container"
);
}
// ink: overflow.tsx "overflowY - box intersecting with top edge of overflow container with border" (oracle-materialized)
// idx=21 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box('World') = "╭─────╮\n│World│\n╰─────╯"
// JSX: <Box width={7} height={3} overflowY="hidden" borderStyle="round"><Box marginTop={-1} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
#[test]
fn ov_overflow_y_box_intersecting_with_top_edge_of_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(7.0)),
height: Some(Dim::Points(3.0)),
overflow_y: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(-1.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭─────╮\n│World│\n╰─────╯",
"overflowY - box intersecting with top edge of overflow container with border"
);
}
// ink: overflow.tsx "overflowY - box below bottom edge of overflow container"
// idx=22 · excluded=false · manifest-literal
// JSX: <Box height={1} overflowY="hidden"><Box marginTop={1} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
// Child starts at row 1 = container height. Entirely below the bottom edge. Empty.
#[test]
fn ov_overflow_y_box_below_bottom_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(1.0)),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(1.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"",
"overflowY - box below bottom edge of overflow container"
);
}
// ink: overflow.tsx "overflowY - box below bottom edge of overflow container with border" (oracle-materialized)
// idx=23 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box(' ') = "╭─────╮\n│ │\n╰─────╯"
// JSX: <Box width={7} height={3} overflowY="hidden" borderStyle="round"><Box marginTop={2} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
#[test]
fn ov_overflow_y_box_below_bottom_edge_of_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(7.0)),
height: Some(Dim::Points(3.0)),
overflow_y: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(2.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭─────╮\n│ │\n╰─────╯",
"overflowY - box below bottom edge of overflow container with border"
);
}
// ink: overflow.tsx "overflowY - box intersecting with bottom edge of overflow container"
// idx=24 · excluded=false · manifest-literal
// JSX: <Box height={1} overflowY="hidden"><Box height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
// Child height=2 starts at row 0; container height=1. Only first row 'Hello' visible.
#[test]
fn ov_overflow_y_box_intersecting_with_bottom_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(1.0)),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello",
"overflowY - box intersecting with bottom edge of overflow container"
);
}
// ink: overflow.tsx "overflowY - box intersecting with bottom edge of overflow container with border" (oracle-materialized)
// idx=25 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: box('Hello') = "╭─────╮\n│Hello│\n╰─────╯"
// JSX: <Box width={7} height={3} overflowY="hidden" borderStyle="round"><Box height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box>
#[test]
fn ov_overflow_y_box_intersecting_with_bottom_edge_of_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(7.0)),
height: Some(Dim::Points(3.0)),
overflow_y: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"╭─────╮\n│Hello│\n╰─────╯",
"overflowY - box intersecting with bottom edge of overflow container with border"
);
}
// ─────────────────────────────────────────────────────────────────────────
// overflow (both axes) tests
// ─────────────────────────────────────────────────────────────────────────
// ink: overflow.tsx "overflow - single text node inside overflow container"
// idx=26 · excluded=false · manifest-literal
// JSX: <Box paddingBottom={1}><Box width={6} height={1} overflow="hidden"><Box width={12} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box></Box>
// overflow="hidden" resolves to overflow_x+overflow_y=Hidden (Box.tsx:90-92).
// paddingBottom=1 adds trailing newline.
#[test]
fn ov_overflow_single_text_node_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
// outer box with paddingBottom=1
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(1.0)),
..Style::default()
},
);
// overflow container
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(6.0)),
height: Some(Dim::Points(1.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
// oversized child
make_box(
&mut a,
3,
Style {
width: Some(Dim::Points(12.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 4, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 3, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello\n",
"overflow - single text node inside overflow container"
);
}
// ink: overflow.tsx "overflow - single text node inside overflow container with border" (oracle-materialized)
// idx=27 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: "╭──────╮\n│Hello │\n╰──────╯\n"
// JSX: <Box paddingBottom={1}><Box width={8} height={3} overflow="hidden" borderStyle="round"><Box width={12} height={2} flexShrink={0}><Text>Hello{'\n'}World</Text></Box></Box></Box>
#[test]
fn ov_overflow_single_text_node_inside_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(8.0)),
height: Some(Dim::Points(3.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
3,
Style {
width: Some(Dim::Points(12.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 4, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 3, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"╭──────╮\n│Hello │\n╰──────╯\n",
"overflow - single text node inside overflow container with border"
);
}
// ink: overflow.tsx "overflow - multiple boxes inside overflow container"
// idx=28 · excluded=false · manifest-literal
// overflow clips to 4x1. Two side-by-side boxes each 2x2. Only top row: 'TL'+'TR'='TLTR'. paddingBottom=1 adds newline.
#[test]
fn ov_overflow_multiple_boxes_inside_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(1.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
3,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 4, "TL\nBL");
make_box(
&mut a,
5,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 6, "TR\nBR");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 2, 5);
add_child(&mut a, 5, 6);
assert_eq!(
render_to_string(&a, 0, 80),
"TLTR\n",
"overflow - multiple boxes inside overflow container"
);
}
// ink: overflow.tsx "overflow - multiple boxes inside overflow container with border" (oracle-materialized)
// idx=29 · excluded=true, assertionKind=computed · oracle-materialized
// Expected: "╭────╮\n│TLTR│\n╰────╯\n"
// JSX: <Box paddingBottom={1}><Box width={6} height={3} overflow="hidden" borderStyle="round">...</Box></Box>
#[test]
fn ov_overflow_multiple_boxes_inside_overflow_container_with_border() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(6.0)),
height: Some(Dim::Points(3.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
make_box(
&mut a,
3,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 4, "TL\nBL");
make_box(
&mut a,
5,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 6, "TR\nBR");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 2, 5);
add_child(&mut a, 5, 6);
assert_eq!(
render_to_string(&a, 0, 80),
"╭────╮\n│TLTR│\n╰────╯\n",
"overflow - multiple boxes inside overflow container with border"
);
}
// ink: overflow.tsx "overflow - box intersecting with top left edge of overflow container"
// idx=30 · excluded=false · manifest-literal
// Child at (-2,-2), size 4x4. Container at (0,0) size 4x4.
// Visible rows 0..1 = child rows 2..3 = 'CCCC','DDDD'; x clipped to 2..3 = 'CC','DD'.
// Rows 2..3 of container empty. Output has trailing \n\n from 4-row height.
// JSX text squash: <Text>\n AAAA{'\n'}...\n</Text> → "AAAA\nBBBB\nCCCC\nDDDD"
// (JSX whitespace-only text nodes on their own line are dropped by React).
#[test]
fn ov_overflow_box_intersecting_with_top_left_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(-2.0)),
margin_left: Some(Lp::Points(-2.0)),
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "AAAA\nBBBB\nCCCC\nDDDD");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"CC\nDD\n\n",
"overflow - box intersecting with top left edge of overflow container"
);
}
// ink: overflow.tsx "overflow - box intersecting with top right edge of overflow container"
// idx=31 · excluded=false · manifest-literal
// Child at (2,-2), size 4x4. Container 4x4. Visible rows 0..1 from child rows 2..3.
// x offset at 2 in container: ' CC',' DD'. Rows 2..3 empty.
#[test]
fn ov_overflow_box_intersecting_with_top_right_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(-2.0)),
margin_left: Some(Lp::Points(2.0)),
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "AAAA\nBBBB\nCCCC\nDDDD");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
" CC\n DD\n\n",
"overflow - box intersecting with top right edge of overflow container"
);
}
// ink: overflow.tsx "overflow - box intersecting with bottom left edge of overflow container"
// idx=32 · excluded=false · manifest-literal
// Child at (-2,2), size 4x4. Container rows 0..1 empty (child starts at row 2).
// Rows 2..3: child rows 0..1 = 'AAAA','BBBB'; x clipped to cols 0..1 = 'AA','BB'.
#[test]
fn ov_overflow_box_intersecting_with_bottom_left_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(2.0)),
margin_left: Some(Lp::Points(-2.0)),
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "AAAA\nBBBB\nCCCC\nDDDD");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\nAA\nBB",
"overflow - box intersecting with bottom left edge of overflow container"
);
}
// ink: overflow.tsx "overflow - box intersecting with bottom right edge of overflow container"
// idx=33 · excluded=false · manifest-literal
// Child at (2,2), size 4x4. Container rows 0..1 empty.
// Rows 2..3: child cols 0..1 = 'AA','BB' at container x=2 = ' AA',' BB'.
#[test]
fn ov_overflow_box_intersecting_with_bottom_right_edge_of_overflow_container() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
margin_top: Some(Lp::Points(2.0)),
margin_left: Some(Lp::Points(2.0)),
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "AAAA\nBBBB\nCCCC\nDDDD");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n AA\n BB",
"overflow - box intersecting with bottom right edge of overflow container"
);
}
// ink: overflow.tsx "nested overflow"
// idx=34 · excluded=false · manifest-literal
// Nested overflow containers. paddingBottom=1 wraps the whole thing.
// Outer 4x4 column-flex overflow hidden.
// Inner top: 2x2 overflow hidden showing top-left 2x2 of 4x4 child = 'AA','BB'.
// Inner bottom: 4x3 shows rows 2..3 of outer = 'XXXX','YYYY'.
// JSX text squash: whitespace-only JSX text nodes on their own lines are dropped.
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn ov_nested_overflow() {
let mut a = Arena::new();
make_root(&mut a, 0);
// wrapper with paddingBottom=1
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(1.0)),
..Style::default()
},
);
// outer overflow container: 4x4, column, overflow hidden
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
// inner top: 2x2, overflow hidden
make_box(
&mut a,
3,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(2.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
// 4x4 child of inner top
make_box(
&mut a,
4,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 5, "AAAA\nBBBB\nCCCC\nDDDD");
// inner bottom: 4x3
make_box(
&mut a,
6,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 7, "XXXX\nYYYY\nZZZZ");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 2, 6);
add_child(&mut a, 6, 7);
assert_eq!(
render_to_string(&a, 0, 80),
"AA\nBB\nXXXX\nYYYY\n",
"nested overflow"
);
}
// ink: overflow.tsx "out of bounds writes do not crash" (oracle-materialized)
// idx=35 · excluded=true, assertionKind=computed · oracle-materialized
// columns=10; renders <Box width={12} height={10} borderStyle="round" />.
// Expected from ink oracle: "╭──────────╮\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n╰──────────╯"
#[test]
fn ov_out_of_bounds_writes_do_not_crash() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(12.0)),
height: Some(Dim::Points(10.0)),
border_style: Some(BorderStyle::Named("round".to_owned())),
..Style::default()
},
);
add_child(&mut a, 0, 1);
assert_eq!(
render_to_string(&a, 0, 10),
"╭──────────╮\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n│ │\n╰──────────╯",
"out of bounds writes do not crash"
);
}
// ─────────────────────────────────────────────────────────────────────────
// Concurrent variants (idx=36..39): same JSX as sync variants, same expected.
// The concurrent distinction is ink-renderer-internal; the Rust renderer is
// always synchronous. Test names carry the -concurrent suffix to track parity.
// ─────────────────────────────────────────────────────────────────────────
// ink: overflow.tsx "overflowX - single text node in a box inside overflow container - concurrent"
// idx=36 · excluded=false · manifest-literal
#[test]
fn ov_overflow_x_single_text_node_in_a_box_inside_overflow_container_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
overflow_x: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(16.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 3, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello",
"overflowX - single text node in a box inside overflow container - concurrent"
);
}
// ink: overflow.tsx "overflowY - single text node inside overflow container - concurrent"
// idx=37 · excluded=false · manifest-literal
#[test]
fn ov_overflow_y_single_text_node_inside_overflow_container_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(1.0)),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_text(&mut a, 2, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello",
"overflowY - single text node inside overflow container - concurrent"
);
}
// ink: overflow.tsx "overflow - single text node inside overflow container - concurrent"
// idx=38 · excluded=false · manifest-literal
#[test]
fn ov_overflow_single_text_node_inside_overflow_container_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(6.0)),
height: Some(Dim::Points(1.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
3,
Style {
width: Some(Dim::Points(12.0)),
height: Some(Dim::Points(2.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 4, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 3, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"Hello\n",
"overflow - single text node inside overflow container - concurrent"
);
}
// ink: overflow.tsx "nested overflow - concurrent"
// idx=39 · excluded=false · manifest-literal
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn ov_nested_overflow_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
3,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(2.0)),
overflow_x: Some(Overflow::Hidden),
overflow_y: Some(Overflow::Hidden),
..Style::default()
},
);
make_box(
&mut a,
4,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(4.0)),
flex_shrink: Some(0.0),
..Style::default()
},
);
make_text(&mut a, 5, "AAAA\nBBBB\nCCCC\nDDDD");
make_box(
&mut a,
6,
Style {
width: Some(Dim::Points(4.0)),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_text(&mut a, 7, "XXXX\nYYYY\nZZZZ");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 3, 4);
add_child(&mut a, 4, 5);
add_child(&mut a, 2, 6);
add_child(&mut a, 6, 7);
assert_eq!(
render_to_string(&a, 0, 80),
"AA\nBB\nXXXX\nYYYY\n",
"nested overflow - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod display (display.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod display {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Display, FlexDir, Style};
use inkferro_core::render::render_to_string;
// ink: display.tsx "display flex"
#[test]
fn display_display_flex() {
// JSX: <Box display="flex"><Text>X</Text></Box>
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
display: Some(Display::Flex),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 100), "X", "display flex");
}
// ink: display.tsx "display none"
#[test]
fn display_display_none() {
// JSX: <Box flexDirection="column"><Box display="none"><Text>Kitty!</Text></Box><Text>Doggo</Text></Box>
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
display: Some(Display::None),
..Style::default()
},
);
make_text(&mut a, 3, "Kitty!");
make_text(&mut a, 4, "Doggo");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 100), "Doggo", "display none");
}
// ink: display.tsx "display flex - concurrent"
#[test]
fn display_display_flex_concurrent() {
// JSX: <Box display="flex"><Text>X</Text></Box>
// Concurrent variant: only the JS render helper (renderToStringAsync) differs;
// the static frame is identical, so arena construction matches `display flex`.
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
display: Some(Display::Flex),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 100),
"X",
"display flex - concurrent"
);
}
// ink: display.tsx "display none - concurrent"
#[test]
fn display_display_none_concurrent() {
// JSX: <Box flexDirection="column"><Box display="none"><Text>Kitty!</Text></Box><Text>Doggo</Text></Box>
// Concurrent variant: only the JS render helper (renderToStringAsync) differs;
// the static frame is identical, so arena construction matches `display none`.
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
display: Some(Display::None),
..Style::default()
},
);
make_text(&mut a, 3, "Kitty!");
make_text(&mut a, 4, "Doggo");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"Doggo",
"display none - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod flex_align_content (flex-align-content.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod flex_align_content {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, ContentAlign, Dim, FlexWrap, Style};
use inkferro_core::render::render_to_string;
// ink: flex-align-content.tsx "align content flex-start"
#[test]
fn flex_align_content_align_content_flex_start() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::FlexStart),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"AB\nCD\n\n\n\n",
"align content flex-start"
);
}
// ink: flex-align-content.tsx "align content center"
#[test]
fn flex_align_content_align_content_center() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::Center),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"\n\nAB\nCD\n\n",
"align content center"
);
}
// ink: flex-align-content.tsx "align content flex-end"
#[test]
fn flex_align_content_align_content_flex_end() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::FlexEnd),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"\n\n\n\nAB\nCD",
"align content flex-end"
);
}
// ink: flex-align-content.tsx "align content space-between"
#[test]
fn flex_align_content_align_content_space_between() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::SpaceBetween),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"AB\n\n\n\n\nCD",
"align content space-between"
);
}
// ink: flex-align-content.tsx "align content space-around"
#[test]
fn flex_align_content_align_content_space_around() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::SpaceAround),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"\nAB\n\n\nCD\n",
"align content space-around"
);
}
// ink: flex-align-content.tsx "align content space-evenly"
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn flex_align_content_align_content_space_evenly() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::SpaceEvenly),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"\nAB\n\nCD\n\n",
"align content space-evenly"
);
}
// ink: flex-align-content.tsx "align content stretch"
#[test]
fn flex_align_content_align_content_stretch() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::Stretch),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"AB\n\n\nCD\n\n",
"align content stretch"
);
}
// ink: flex-align-content.tsx "align content defaults to flex-start"
#[test]
fn flex_align_content_align_content_defaults_to_flex_start() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"AB\nCD\n\n\n\n",
"align content defaults to flex-start"
);
}
// ink: flex-align-content.tsx "align content does not add extra spacing when there is no free cross-axis space"
#[test]
fn flex_align_content_align_content_does_not_add_extra_spacing_when_there_is_no_free_cross_axis_space()
{
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(2.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::Center),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"AB\nCD",
"align content does not add extra spacing when there is no free cross-axis space"
);
}
// ink: flex-align-content.tsx "align content center - concurrent"
// Source uses renderToStringAsync; layout/output identical to sync render.
#[test]
fn flex_align_content_align_content_center_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
height: Some(Dim::Points(6.0)),
flex_wrap: Some(FlexWrap::Wrap),
align_content: Some(ContentAlign::Center),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
make_text(&mut a, 5, "D");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
add_child(&mut a, 1, 5);
assert_eq!(
render_to_string(&a, 0, 100),
"\n\nAB\nCD\n\n",
"align content center - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod flex_align_self (flex-align-self.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod flex_align_self {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Align, Arena, BorderStyle, Dim, FlexDir, Style};
use inkferro_core::render::render_to_string;
// ink: flex-align-self.tsx "row - align text to center"
#[test]
fn flex_align_self_row_align_text_to_center() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::Center),
..Style::default()
},
);
make_text(&mut a, 3, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\nTest\n",
"row - align text to center"
);
}
// ink: flex-align-self.tsx "row - align multiple text nodes to center"
#[test]
fn flex_align_self_row_align_multiple_text_nodes_to_center() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::Center),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 2, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"\nAB\n",
"row - align multiple text nodes to center"
);
}
// ink: flex-align-self.tsx "row - align text to bottom"
#[test]
fn flex_align_self_row_align_text_to_bottom() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::FlexEnd),
..Style::default()
},
);
make_text(&mut a, 3, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\nTest",
"row - align text to bottom"
);
}
// ink: flex-align-self.tsx "row - align multiple text nodes to bottom"
#[test]
fn flex_align_self_row_align_multiple_text_nodes_to_bottom() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::FlexEnd),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 2, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\nAB",
"row - align multiple text nodes to bottom"
);
}
// ink: flex-align-self.tsx "column - align text to center"
#[test]
fn flex_align_self_column_align_text_to_center() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::Center),
..Style::default()
},
);
make_text(&mut a, 3, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
" Test",
"column - align text to center"
);
}
// ink: flex-align-self.tsx "column - align text to right"
#[test]
fn flex_align_self_column_align_text_to_right() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
width: Some(Dim::Points(10.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::FlexEnd),
..Style::default()
},
);
make_text(&mut a, 3, "Test");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
" Test",
"column - align text to right"
);
}
// ink: flex-align-self.tsx "column - align self stretch"
#[test]
fn flex_align_self_column_align_self_stretch() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
width: Some(Dim::Points(7.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::Stretch),
border_style: Some(BorderStyle::Named("single".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"┌─────┐\n│X │\n└─────┘",
"column - align self stretch"
);
}
// ink: flex-align-self.tsx "row - align self stretch"
#[test]
fn flex_align_self_row_align_self_stretch() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(5.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
align_self: Some(Align::Stretch),
border_style: Some(BorderStyle::Named("single".to_owned())),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"┌─┐\n│X│\n│ │\n│ │\n└─┘",
"row - align self stretch"
);
}
// ink: flex-align-self.tsx "row - align self baseline"
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn flex_align_self_row_align_self_baseline() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
align_items: Some(Align::FlexEnd),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
// First Text child: squashed content of <Text>A<Newline/>B</Text> = "A\nB".
make_text(&mut a, 2, "A\nB");
// Second child: <Box alignSelf="baseline"><Text>X</Text></Box>.
make_box(
&mut a,
3,
Style {
align_self: Some(Align::Baseline),
..Style::default()
},
);
make_text(&mut a, 4, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 3, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"AX\nB\n",
"row - align self baseline"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod flex_direction (flex-direction.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod flex_direction {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Dim, FlexDir, Style};
use inkferro_core::render::render_to_string;
// ink: flex-direction.tsx "direction row"
#[test]
fn flex_direction_direction_row() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Row),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(render_to_string(&a, 0, 100), "AB", "direction row");
}
// ink: flex-direction.tsx "direction row reverse"
#[test]
fn flex_direction_direction_row_reverse() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::RowReverse),
width: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
" BA",
"direction row reverse"
);
}
// ink: flex-direction.tsx "direction column"
#[test]
fn flex_direction_direction_column() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(render_to_string(&a, 0, 100), "A\nB", "direction column");
}
// ink: flex-direction.tsx "direction column reverse"
#[test]
fn flex_direction_direction_column_reverse() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::ColumnReverse),
height: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"\n\nB\nA",
"direction column reverse"
);
}
// ink: flex-direction.tsx "don’t squash text nodes when column direction is applied"
#[test]
fn flex_direction_don_t_squash_text_nodes_when_column_direction_is_applied() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"A\nB",
"don\u{2019}t squash text nodes when column direction is applied"
);
}
// ink: flex-direction.tsx "direction row - concurrent"
#[test]
fn flex_direction_direction_row_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Row),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"AB",
"direction row - concurrent"
);
}
// ink: flex-direction.tsx "direction column - concurrent"
#[test]
fn flex_direction_direction_column_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 100),
"A\nB",
"direction column - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod flex_wrap (flex-wrap.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod flex_wrap {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Dim, FlexDir, FlexWrap, Style};
use inkferro_core::render::render_to_string;
// ink: flex-wrap.tsx "row - no wrap"
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn flex_wrap_row_no_wrap() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "BC");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(render_to_string(&a, 0, 100), "BC\n", "row - no wrap");
}
// ink: flex-wrap.tsx "column - no wrap"
// ADR-1 DIVERGENCE (gate signal): expected literal is ink-correct; the
// renderer does not yet emit it. Do NOT edit the literal to pass --
// see docs/adr1-gate.md.
#[test]
fn flex_wrap_column_no_wrap() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
height: Some(Dim::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 100), "B\nC", "column - no wrap");
}
// ink: flex-wrap.tsx "row - wrap content"
#[test]
fn flex_wrap_row_wrap_content() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(2.0)),
flex_wrap: Some(FlexWrap::Wrap),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "BC");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(render_to_string(&a, 0, 100), "A\nBC", "row - wrap content");
}
// ink: flex-wrap.tsx "column - wrap content"
#[test]
fn flex_wrap_column_wrap_content() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
height: Some(Dim::Points(2.0)),
flex_wrap: Some(FlexWrap::Wrap),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"AC\nB",
"column - wrap content"
);
}
// ink: flex-wrap.tsx "column - wrap content reverse"
#[test]
fn flex_wrap_column_wrap_content_reverse() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(FlexDir::Column),
height: Some(Dim::Points(2.0)),
width: Some(Dim::Points(3.0)),
flex_wrap: Some(FlexWrap::WrapReverse),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
" CA\n B",
"column - wrap content reverse"
);
}
// ink: flex-wrap.tsx "row - wrap content reverse"
#[test]
fn flex_wrap_row_wrap_content_reverse() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
height: Some(Dim::Points(3.0)),
width: Some(Dim::Points(2.0)),
flex_wrap: Some(FlexWrap::WrapReverse),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 100),
"\nC\nAB",
"row - wrap content reverse"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod gap (gap.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod gap {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Dim, FlexWrap, Style};
use inkferro_core::render::render_to_string;
// ink: gap.tsx "gap"
#[test]
fn gap_gap() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
gap: Some(1.0),
width: Some(Dim::Points(3.0)),
flex_wrap: Some(FlexWrap::Wrap),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 80), "A B\n\nC", "gap");
}
// ink: gap.tsx "column gap"
#[test]
fn gap_column_gap() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
gap: Some(1.0),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(render_to_string(&a, 0, 80), "A B", "column gap");
}
// ink: gap.tsx "row gap"
#[test]
fn gap_row_gap() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(inkferro_core::dom::FlexDir::Column),
gap: Some(1.0),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(render_to_string(&a, 0, 80), "A\n\nB", "row gap");
}
// ink: gap.tsx "gap - concurrent"
#[test]
fn gap_gap_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
gap: Some(1.0),
width: Some(Dim::Points(3.0)),
flex_wrap: Some(FlexWrap::Wrap),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
make_text(&mut a, 4, "C");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 80), "A B\n\nC", "gap - concurrent");
}
// ink: gap.tsx "column gap - concurrent"
#[test]
fn gap_column_gap_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
gap: Some(1.0),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"A B",
"column gap - concurrent"
);
}
// ink: gap.tsx "row gap - concurrent"
#[test]
fn gap_row_gap_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
flex_direction: Some(inkferro_core::dom::FlexDir::Column),
gap: Some(1.0),
..Style::default()
},
);
make_text(&mut a, 2, "A");
make_text(&mut a, 3, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"A\n\nB",
"row gap - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod padding (padding.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod padding {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Dim, Lp, Style};
use inkferro_core::render::render_to_string;
// ink: padding.tsx "padding"
#[test]
fn padding_padding() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "\n\n X\n\n", "padding");
}
// ink: padding.tsx "padding X"
#[test]
fn padding_padding_x() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(&mut a, 1, Style::default());
make_box(
&mut a,
2,
Style {
padding_x: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
make_text(&mut a, 4, "Y");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 80), " X Y", "padding X");
}
// ink: padding.tsx "padding Y"
#[test]
fn padding_padding_y() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_y: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "\n\nX\n\n", "padding Y");
}
// ink: padding.tsx "padding top"
#[test]
fn padding_padding_top() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_top: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "\n\nX", "padding top");
}
// ink: padding.tsx "padding bottom"
#[test]
fn padding_padding_bottom() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_bottom: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), "X\n\n", "padding bottom");
}
// ink: padding.tsx "padding left"
#[test]
fn padding_padding_left() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding_left: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(render_to_string(&a, 0, 80), " X", "padding left");
}
// ink: padding.tsx "padding right"
#[test]
fn padding_padding_right() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(&mut a, 1, Style::default());
make_box(
&mut a,
2,
Style {
padding_right: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
make_text(&mut a, 4, "Y");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(render_to_string(&a, 0, 80), "X Y", "padding right");
}
// ink: padding.tsx "nested padding"
#[test]
fn padding_nested_padding() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
padding: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n\n\n X\n\n\n\n",
"nested padding"
);
}
// ink: padding.tsx "padding with multiline string"
#[test]
fn padding_padding_with_multiline_string() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "A\nB");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n A\n B\n\n",
"padding with multiline string"
);
}
// ink: padding.tsx "apply padding to text with newlines"
#[test]
fn padding_apply_padding_to_text_with_newlines() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding: Some(Lp::Points(1.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello\nWorld");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n Hello\n World\n",
"apply padding to text with newlines"
);
}
// ink: padding.tsx "apply padding to wrapped text"
#[test]
fn padding_apply_padding_to_wrapped_text() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding: Some(Lp::Points(1.0)),
width: Some(Dim::Points(5.0)),
..Style::default()
},
);
make_text(&mut a, 2, "Hello World");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n Hel\n lo\n Wor\n ld\n",
"apply padding to wrapped text"
);
}
// ink: padding.tsx "padding - concurrent"
#[test]
fn padding_padding_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 2, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n X\n\n",
"padding - concurrent"
);
}
// ink: padding.tsx "nested padding - concurrent"
#[test]
fn padding_nested_padding_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
padding: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
padding: Some(Lp::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n\n\n X\n\n\n\n",
"nested padding - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod position (position.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod position {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Dim, Position, Style};
use inkferro_core::render::render_to_string;
// ink: position.tsx "absolute position with top and left offsets"
#[test]
fn position_absolute_position_with_top_and_left_offsets() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(5.0)),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
position: Some(Position::Absolute),
top: Some(Dim::Points(1.0)),
left: Some(Dim::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n X\n",
"absolute position with top and left offsets"
);
}
// ink: position.tsx "absolute position with bottom and right offsets"
#[test]
fn position_absolute_position_with_bottom_and_right_offsets() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
height: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
position: Some(Position::Absolute),
bottom: Some(Dim::Points(1.0)),
right: Some(Dim::Points(1.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n X\n",
"absolute position with bottom and right offsets"
);
}
// ink: position.tsx "absolute position with percentage offsets"
#[test]
fn position_absolute_position_with_percentage_offsets() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
height: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
position: Some(Position::Absolute),
top: Some(Dim::Percent(50.0)),
left: Some(Dim::Percent(50.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n\n X\n",
"absolute position with percentage offsets"
);
}
// ink: position.tsx "absolute position with percentage bottom and right offsets"
#[test]
fn position_absolute_position_with_percentage_bottom_and_right_offsets() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(6.0)),
height: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
position: Some(Position::Absolute),
bottom: Some(Dim::Percent(50.0)),
right: Some(Dim::Percent(50.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n X\n\n",
"absolute position with percentage bottom and right offsets"
);
}
// ink: position.tsx "relative position offsets visual position while keeping flow"
#[test]
fn position_relative_position_offsets_visual_position_while_keeping_flow() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(5.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
position: Some(Position::Relative),
left: Some(Dim::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "A");
make_text(&mut a, 4, "B");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 80),
" BA",
"relative position offsets visual position while keeping flow"
);
}
// ink: position.tsx "absolute position with top and left offsets - concurrent"
#[test]
fn position_absolute_position_with_top_and_left_offsets_concurrent() {
let mut a = Arena::new();
make_root(&mut a, 0);
make_box(
&mut a,
1,
Style {
width: Some(Dim::Points(5.0)),
height: Some(Dim::Points(3.0)),
..Style::default()
},
);
make_box(
&mut a,
2,
Style {
position: Some(Position::Absolute),
top: Some(Dim::Points(1.0)),
left: Some(Dim::Points(2.0)),
..Style::default()
},
);
make_text(&mut a, 3, "X");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"\n X\n",
"absolute position with top and left offsets - concurrent"
);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// mod text_width (text-width.tsx)
// ═══════════════════════════════════════════════════════════════════════════
pub mod text_width {
use super::helpers::{add_child, make_box, make_root, make_text};
use inkferro_core::dom::{Arena, Dim, Style};
use inkferro_core::render::render_to_string;
// ink: text-width.tsx "CJK characters occupy correct width in fixed-width Box"
// JSX: <Box><Box width={4}><Text>你好</Text></Box><Text>|</Text></Box>
// renderToString with no options → ink default columns=80 (render-to-string.js:34).
// Box width={4} fits exactly 2 CJK chars (each display width 2); '|' is the sibling.
#[test]
fn text_width_cjk_characters_occupy_correct_width_in_fixed_width_box() {
let mut a = Arena::new();
make_root(&mut a, 0);
// outer <Box> (default flexDirection:'row')
make_box(&mut a, 1, Style::default());
// inner <Box width={4}>
make_box(
&mut a,
2,
Style {
width: Some(Dim::Points(4.0)),
..Style::default()
},
);
make_text(&mut a, 3, "你好");
make_text(&mut a, 4, "|");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 2, 3);
add_child(&mut a, 1, 4);
assert_eq!(
render_to_string(&a, 0, 80),
"你好|",
"CJK characters occupy correct width in fixed-width Box"
);
}
// ink: text-width.tsx "empty Text does not affect sibling layout"
// JSX: <Box><Text /><Text>hello</Text></Box>
// renderToString with no options → ink default columns=80 (render-to-string.js:34).
// Self-closing <Text /> exists as a real sibling but emits no content.
#[test]
fn text_width_empty_text_does_not_affect_sibling_layout() {
let mut a = Arena::new();
make_root(&mut a, 0);
// outer <Box> (default flexDirection:'row')
make_box(&mut a, 1, Style::default());
// self-closing <Text /> — empty content, still a sibling node
make_text(&mut a, 2, "");
make_text(&mut a, 3, "hello");
add_child(&mut a, 0, 1);
add_child(&mut a, 1, 2);
add_child(&mut a, 1, 3);
assert_eq!(
render_to_string(&a, 0, 80),
"hello",
"empty Text does not affect sibling layout"
);
}
}
}