use std::assert_matches;
use crate::style::{ComputedStyle, LonghandId, Style, properties::BackgroundImage};
use super::*;
#[test]
fn test_box_sizing() {
assert_eq!(
TailwindProperty::parse("box-border"),
Some(TailwindProperty::BoxSizing(BoxSizing::BorderBox))
);
}
#[test]
fn test_parse_width() {
assert_eq!(
TailwindProperty::parse("w-64"),
Some(TailwindProperty::Width(Length::from_spacing(64.0)))
);
assert_eq!(
TailwindProperty::parse("h-32"),
Some(TailwindProperty::Height(Length::from_spacing(32.0)))
);
assert_eq!(
TailwindProperty::parse("justify-self-center"),
Some(TailwindProperty::JustifySelf(AlignItems::Center))
);
}
#[test]
fn test_parse_color() {
assert_eq!(
TailwindProperty::parse("text-black/30"),
Some(TailwindProperty::Color(ColorInput::Value(Color([
0,
0,
0,
(0.3_f32 * 255.0).round() as u8
]))))
);
}
#[test]
fn test_parse_red_500_color_utilities() {
let cases: &[(&str, TailwindProperty)] = &[
(
"shadow-red-500",
TailwindProperty::ShadowColor(ColorInput::Value(Color([251, 44, 54, 255]))),
),
(
"text-shadow-red-500",
TailwindProperty::TextShadowColor(ColorInput::Value(Color([251, 44, 54, 255]))),
),
(
"decoration-red-500",
TailwindProperty::TextDecorationColor(ColorInput::Value(Color([251, 44, 54, 255]))),
),
(
"bg-red-500",
TailwindProperty::BackgroundColor(ColorInput::Value(Color([251, 44, 54, 255]))),
),
];
for (input, expected) in cases {
assert_eq!(
TailwindProperty::parse(input),
Some(expected.clone()),
"{input}"
);
}
}
#[test]
fn test_parse_text_decoration_lines() {
assert_eq!(
TailwindProperty::parse("underline"),
Some(TailwindProperty::TextDecorationLine(
TextDecorationLines::UNDERLINE
))
);
assert_eq!(
TailwindProperty::parse("no-underline"),
Some(TailwindProperty::TextDecorationLine(
TextDecorationLines::empty()
))
);
}
#[test]
fn test_parse_arbitrary_color() {
assert_eq!(
TailwindProperty::parse("text-[rgb(0, 191, 255)]"),
Some(TailwindProperty::Color(ColorInput::Value(Color([
0, 191, 255, 255
]))))
);
}
#[test]
fn test_parse_arbitrary_mask_image_url() {
assert_eq!(
TailwindProperty::parse("mask-[url('https://example.com/logo.svg')]"),
Some(TailwindProperty::MaskImage(BackgroundImage::Url(
"https://example.com/logo.svg".into()
)))
);
}
#[test]
fn test_split_variant_ignores_colons_in_arbitrary_values() {
assert_eq!(split_variant("sm:mt-0"), Some(("sm", "mt-0")));
assert_eq!(split_variant("sm:hover:mt-0"), Some(("sm", "hover:mt-0")));
assert_eq!(split_variant("mt-0"), None);
assert_eq!(
split_variant("mask-[url('https://example.com/a.svg')]"),
None
);
assert_eq!(split_variant("grid-cols-[repeat(2,minmax(0,1fr))]"), None);
assert_eq!(split_variant("[mask:foo]"), None);
assert_eq!(
split_variant("md:mask-[url('https://example.com/a.svg')]"),
Some(("md", "mask-[url('https://example.com/a.svg')]"))
);
assert_eq!(split_variant(r#"content-['a:b]']"#), None);
assert_eq!(split_variant(r#"content-["a\":b"]"#), None);
}
#[test]
fn test_decode_arbitrary_value() {
assert_eq!(decode_arbitrary_value("3_1_auto"), "3 1 auto");
assert_eq!(decode_arbitrary_value("10px"), "10px");
assert_eq!(decode_arbitrary_value(r"foo\_bar"), "foo_bar");
assert_eq!(
decode_arbitrary_value("url('https://example.com/my_logo.svg')"),
"url('https://example.com/my_logo.svg')"
);
assert_eq!(decode_arbitrary_value("var(--my_color)"), "var(--my_color)");
assert_eq!(
decode_arbitrary_value("theme(--spacing_4)"),
"theme(--spacing_4)"
);
assert_eq!(decode_arbitrary_value("var(--x,_a_b)"), "var(--x, a b)");
assert_eq!(decode_arbitrary_value("calc(1_+_2)"), "calc(1 + 2)");
}
#[test]
fn test_extract_arbitrary_value_preserves_url_underscores() {
assert_eq!(
TailwindProperty::parse("mask-[url('https://example.com/my_logo.svg')]"),
Some(TailwindProperty::MaskImage(BackgroundImage::Url(
"https://example.com/my_logo.svg".into()
)))
);
}
#[test]
fn test_parse_value_arbitrary_url_with_scheme_colon() {
let url_image =
TailwindProperty::MaskImage(BackgroundImage::Url("https://example.com/a.svg".into()));
assert_eq!(
TailwindValue::parse("mask-[url('https://example.com/a.svg')]"),
Some(TailwindValue {
property: TailwindProperty::MaskImage(BackgroundImage::Url(
"https://example.com/a.svg".into(),
)),
breakpoint: None,
important: false,
})
);
assert_eq!(
TailwindValue::parse("md:mask-[url('https://example.com/a.svg')]"),
Some(TailwindValue {
property: url_image,
breakpoint: Some(Breakpoint(Length::Rem(48.0))),
important: false,
})
);
}
#[test]
fn test_parse_arbitrary_flex_with_spaces() {
assert_eq!(
TailwindProperty::parse("flex-[3_1_auto]"),
Some(TailwindProperty::Flex(Flex {
grow: 3.0,
shrink: 1.0,
basis: Length::Auto,
}))
);
}
#[test]
fn test_parse_tailwind_animation_preset() {
assert_matches!(
TailwindProperty::parse("animate-spin"),
Some(TailwindProperty::Animation(animations))
if animations.as_ref() == [Animation {
duration: AnimationTime::from_milliseconds(1000.0),
timing_function: AnimationTimingFunction::Linear,
iteration_count: AnimationIterationCount::Infinite,
name: Some("spin".to_string()),
..Animation::default()
}]
);
}
#[test]
fn test_parse_tailwind_animation_arbitrary_value() {
assert_matches!(
TailwindProperty::parse("animate-[wiggle_1s_ease-in-out_infinite]"),
Some(TailwindProperty::Animation(animations))
if animations.as_ref() == [Animation {
duration: AnimationTime::from_milliseconds(1000.0),
timing_function: AnimationTimingFunction::EaseInOut,
iteration_count: AnimationIterationCount::Infinite,
name: Some("wiggle".to_string()),
..Animation::default()
}]
);
}
#[test]
fn test_parse_negative_margin() {
assert_eq!(
TailwindProperty::parse("-ml-4"),
Some(TailwindProperty::MarginLeft(Length::from_spacing(-4.0)))
);
}
#[test]
fn test_parse_border_radius() {
assert_eq!(
TailwindProperty::parse("rounded-xs"),
Some(TailwindProperty::Rounded(TwRounded(Length::Rem(0.125))))
);
assert_eq!(
TailwindProperty::parse("rounded-full"),
Some(TailwindProperty::Rounded(TwRounded(Length::Px(9999.0))))
);
}
#[test]
fn test_parse_font_size_with_arbitrary_line_height() {
assert_eq!(
TailwindProperty::parse("text-base/[12.34]"),
Some(TailwindProperty::FontSize(TwFontSize {
font_size: (Length::Rem(1.0).into()),
line_height: Some(LineHeight::Unitless(12.34)),
}))
);
}
#[test]
fn test_parse_border_width() {
assert_eq!(
TailwindProperty::parse("border"),
Some(TailwindProperty::BorderDefault)
);
assert_eq!(
TailwindProperty::parse("border-t-2"),
Some(TailwindProperty::BorderTopWidth(TwBorderWidth(Length::Px(
2.0
))))
);
assert_eq!(
TailwindProperty::parse("border-x-4"),
Some(TailwindProperty::BorderXWidth(TwBorderWidth(Length::Px(
4.0
))))
);
assert_eq!(
TailwindProperty::parse("border-solid"),
Some(TailwindProperty::BorderStyle(BorderStyle::Solid))
);
assert_eq!(
TailwindProperty::parse("border-dashed"),
Some(TailwindProperty::BorderStyle(BorderStyle::Dashed))
);
assert_eq!(
TailwindProperty::parse("border-dotted"),
Some(TailwindProperty::BorderStyle(BorderStyle::Dotted))
);
assert_eq!(
TailwindProperty::parse("border-none"),
Some(TailwindProperty::BorderStyle(BorderStyle::None))
);
}
#[test]
fn test_parse_outline() {
assert_eq!(
TailwindProperty::parse("outline"),
Some(TailwindProperty::OutlineDefault)
);
assert_eq!(
TailwindProperty::parse("outline-2"),
Some(TailwindProperty::OutlineWidth(TwBorderWidth(Length::Px(
2.0
))))
);
assert_eq!(
TailwindProperty::parse("outline-red-500"),
Some(TailwindProperty::OutlineColor(ColorInput::Value(Color([
251, 44, 54, 255
]))))
);
assert_eq!(
TailwindProperty::parse("outline-solid"),
Some(TailwindProperty::OutlineStyle(BorderStyle::Solid))
);
assert_eq!(
TailwindProperty::parse("outline-dashed"),
Some(TailwindProperty::OutlineStyle(BorderStyle::Dashed))
);
assert_eq!(
TailwindProperty::parse("outline-offset-4"),
Some(TailwindProperty::OutlineOffset(TwBorderWidth(Length::Px(
4.0
))))
);
assert_eq!(
TailwindProperty::parse("outline-none"),
Some(TailwindProperty::OutlineStyle(BorderStyle::None))
);
}
#[test]
fn test_parse_col_end() {
assert_eq!(
TailwindProperty::parse("col-end-1"),
Some(TailwindProperty::GridColumnEnd(GridPlacement::Line(1)))
);
}
#[test]
fn test_grid_column_start_emits_only_start_longhand() {
let values = TailwindValues::from_str("col-start-2").expect("tailwind values should parse");
let declarations = values.into_declaration_block(Viewport::new((100, 100)));
assert_eq!(
declarations.iter().collect::<Vec<_>>(),
vec![&StyleDeclaration::grid_column_start(GridPlacement::Line(2))]
);
}
#[test]
fn test_grid_row_end_emits_only_end_longhand() {
let values = TailwindValues::from_str("row-end-3").expect("tailwind values should parse");
let declarations = values.into_declaration_block(Viewport::new((100, 100)));
assert_eq!(
declarations.iter().collect::<Vec<_>>(),
vec![&StyleDeclaration::grid_row_end(GridPlacement::Line(3))]
);
}
#[test]
fn test_grid_longhand_importance_is_tracked_per_side() {
let values =
TailwindValues::from_str("col-end-3 !col-start-2").expect("tailwind values should parse");
let declarations = values.into_declaration_block(Viewport::new((100, 100)));
assert_eq!(
declarations.iter().collect::<Vec<_>>(),
vec![
&StyleDeclaration::grid_column_start(GridPlacement::Line(2)),
&StyleDeclaration::grid_column_end(GridPlacement::Line(3)),
]
);
assert!(
declarations
.importance
.longhands
.contains(&LonghandId::GridColumnStart)
);
assert!(
!declarations
.importance
.longhands
.contains(&LonghandId::GridColumnEnd)
);
}
#[test]
fn test_parse_overflow_clip() {
let cases: &[(&str, TailwindProperty)] = &[
("overflow-clip", TailwindProperty::Overflow(Overflow::Clip)),
(
"overflow-x-clip",
TailwindProperty::OverflowX(Overflow::Clip),
),
(
"overflow-y-clip",
TailwindProperty::OverflowY(Overflow::Clip),
),
];
for (input, expected) in cases {
assert_eq!(
TailwindProperty::parse(input),
Some(expected.clone()),
"{input}"
);
}
}
#[test]
fn test_comprehensive_mappings() {
let should_parse = vec![
"flex",
"grid",
"hidden",
"block",
"inline",
"w-4",
"h-8",
"size-12",
"min-w-0",
"max-h-96",
"m-2",
"mx-4",
"my-auto",
"mt-8",
"mr-6",
"mb-4",
"ml-2",
"p-3",
"px-5",
"py-2",
"pt-1",
"pr-4",
"pb-3",
"pl-2",
"text-red-500",
"bg-blue-200",
"border-gray-300",
"text-sm",
"font-bold",
"font-stretch-condensed",
"font-stretch-ultra-expanded",
"font-stretch-75%",
"uppercase",
"tracking-wide",
"animate-spin",
"animate-[wiggle_1s_ease-in-out_infinite]",
"justify-center",
"items-end",
"self-start",
"flex-grow",
"shrink",
"border",
"border-t-2",
"border-solid",
"border-none",
"outline",
"outline-2",
"outline-red-500",
"outline-solid",
"outline-offset-2",
"rounded-lg",
"rotate-45",
"scale-75",
"translate-x-4",
"grid-cols-3",
"col-span-2",
"backdrop-blur-md",
"backdrop-brightness-50",
"backdrop-contrast-125",
"backdrop-grayscale",
"backdrop-hue-rotate-90",
"backdrop-invert",
"backdrop-opacity-50",
"backdrop-saturate-200",
"backdrop-sepia",
"backdrop-filter-[blur(4px)_brightness(0.5)]",
];
let should_not_parse = vec!["nonexistent-class", "invalid-prefix-1", "random-string"];
for class in should_parse {
assert!(
TailwindProperty::parse(class).is_some(),
"Expected '{}' to parse successfully",
class
);
}
for class in should_not_parse {
assert!(
TailwindProperty::parse(class).is_none(),
"Expected '{}' to fail parsing",
class
);
}
}
#[test]
fn test_breakpoint_matches() {
let viewport = Viewport::new((1000, 1000));
assert!(Breakpoint::parse("sm").is_some_and(|bp| bp.matches(viewport)));
}
#[test]
fn test_breakpoint_does_not_match() {
let viewport = Viewport::new((1000, 1000));
assert!(Breakpoint::parse("xl").is_some_and(|bp| !bp.matches(viewport)));
}
#[test]
fn test_value_parsing() {
assert_eq!(
TailwindValue::parse("md:!mt-4"),
Some(TailwindValue {
property: TailwindProperty::MarginTop(Length::Rem(1.0)),
breakpoint: Some(Breakpoint(Length::Rem(48.0))),
important: true,
})
);
}
#[test]
fn test_values_sorting() {
assert_eq!(
TailwindValues::from_str("md:!mt-4 sm:mt-8 !mt-12 mt-16"),
Ok(TailwindValues {
inner: vec![
TailwindValue {
property: TailwindProperty::MarginTop(Length::Rem(4.0)),
breakpoint: None,
important: false,
},
TailwindValue {
property: TailwindProperty::MarginTop(Length::Rem(2.0)),
breakpoint: Some(Breakpoint(Length::Rem(40.0))),
important: false,
},
TailwindValue {
property: TailwindProperty::MarginTop(Length::Rem(3.0)),
breakpoint: None,
important: true,
},
TailwindValue {
property: TailwindProperty::MarginTop(Length::Rem(1.0)),
breakpoint: Some(Breakpoint(Length::Rem(48.0))),
important: true,
},
]
})
)
}
#[test]
fn test_filters_append() {
use crate::style::properties::Filter;
let values = TailwindValues::from_str("blur-sm brightness-150 contrast-125")
.expect("tailwind values should parse");
let viewport = Viewport::new((100, 100));
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(
style.filter,
vec![
Filter::Blur(Length::Px(8.0)),
Filter::Brightness(PercentageNumber(1.5)),
Filter::Contrast(PercentageNumber(1.25))
]
)
}
#[test]
fn test_transform_utilities_resolve_to_standard_longhands() {
let values = TailwindValues::from_str("translate-x-4 translate-y-8 scale-75 scale-x-50")
.expect("tailwind values should parse");
let viewport = Viewport::new((100, 100));
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(
style.translate,
SpacePair::from_pair(Length::Rem(1.0), Length::Rem(2.0))
);
assert_eq!(
style.scale,
SpacePair::from_pair(PercentageNumber(0.5), PercentageNumber(0.75))
);
}
#[test]
fn test_parse_blend_mode() {
assert_eq!(
TailwindProperty::parse("mix-blend-multiply"),
Some(TailwindProperty::MixBlendMode(BlendMode::Multiply))
);
assert_eq!(
TailwindProperty::parse("bg-blend-screen"),
Some(TailwindProperty::BackgroundBlendMode(BlendMode::Screen))
);
}
#[test]
fn test_parse_vertical_align() {
let keywords: &[(&str, VerticalAlignKeyword)] = &[
("align-baseline", VerticalAlignKeyword::Baseline),
("align-top", VerticalAlignKeyword::Top),
("align-middle", VerticalAlignKeyword::Middle),
("align-bottom", VerticalAlignKeyword::Bottom),
("align-text-top", VerticalAlignKeyword::TextTop),
("align-text-bottom", VerticalAlignKeyword::TextBottom),
("align-sub", VerticalAlignKeyword::Sub),
("align-super", VerticalAlignKeyword::Super),
];
for (input, kw) in keywords {
assert_eq!(
TailwindProperty::parse(input),
Some(TailwindProperty::VerticalAlign(VerticalAlign::Keyword(*kw))),
"{input}"
);
}
assert_eq!(
TailwindProperty::parse("align-[10px]"),
Some(TailwindProperty::VerticalAlign(VerticalAlign::Length(
Length::Px(10.0)
)))
);
assert_eq!(
TailwindProperty::parse("align-[25%]"),
Some(TailwindProperty::VerticalAlign(VerticalAlign::Length(
Length::Percentage(25.0)
)))
);
assert_eq!(
TailwindProperty::parse("align-[-0.5em]"),
Some(TailwindProperty::VerticalAlign(VerticalAlign::Length(
Length::Em(-0.5)
)))
);
}
#[test]
fn test_parse_decoration_thickness() {
assert_eq!(
TailwindProperty::parse("decoration-4"),
Some(TailwindProperty::TextDecorationThickness(
TextDecorationThickness::Length(Length::Px(4.0))
))
);
assert_eq!(
TailwindProperty::parse("decoration-auto"),
Some(TailwindProperty::TextDecorationThickness(
TextDecorationThickness::Length(Length::Auto)
))
);
assert_eq!(
TailwindProperty::parse("decoration-from-font"),
Some(TailwindProperty::TextDecorationThickness(
TextDecorationThickness::FromFont
))
);
assert_eq!(
TailwindProperty::parse("decoration-[3px]"),
Some(TailwindProperty::TextDecorationThickness(
TextDecorationThickness::Length(Length::Px(3.0))
))
);
}
#[test]
fn test_linear_gradient_apply() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("bg-linear-to-r from-red-500 via-green-500 to-blue-500")
.expect("tailwind values should parse");
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(
style.background_image,
Some(
[BackgroundImage::Linear(LinearGradient {
repeating: false,
direction: crate::style::LinearGradientDirection::Angle(Angle::new(90.0)),
interpolation: ColorInterpolationMethod::default(),
stops: [
GradientStop::ColorHint {
color: ColorInput::Value(Color([251, 44, 54, 255])),
hint: Some(StopPosition(Length::Percentage(0.0))),
},
GradientStop::ColorHint {
color: ColorInput::Value(Color([0, 201, 80, 255])),
hint: Some(StopPosition(Length::Percentage(50.0))),
},
GradientStop::ColorHint {
color: ColorInput::Value(Color([43, 127, 255, 255])),
hint: Some(StopPosition(Length::Percentage(100.0))),
},
]
.into(),
})]
.into()
)
);
}
#[test]
fn test_shadow_color_overrides_shadow_preset_in_any_order() {
let viewport = Viewport::new((100, 100));
for classes in ["shadow-md shadow-red-500", "shadow-red-500 shadow-md"] {
let values = TailwindValues::from_str(classes)
.unwrap_or_else(|_| panic!("tailwind values should parse: {classes}"));
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(
style.box_shadow,
Some(
[
BoxShadow {
inset: false,
offset_x: Length::Px(0.0),
offset_y: Length::Px(4.0),
blur_radius: Length::Px(6.0),
spread_radius: Length::Px(-1.0),
color: ColorInput::Value(Color([251, 44, 54, 255])),
},
BoxShadow {
inset: false,
offset_x: Length::Px(0.0),
offset_y: Length::Px(2.0),
blur_radius: Length::Px(4.0),
spread_radius: Length::Px(-2.0),
color: ColorInput::Value(Color([251, 44, 54, 255])),
},
]
.into()
)
);
}
}
#[test]
fn test_text_shadow_color_overrides_preset_in_any_order() {
let viewport = Viewport::new((100, 100));
for classes in [
"text-shadow-sm text-shadow-red-500",
"text-shadow-red-500 text-shadow-sm",
] {
let values = TailwindValues::from_str(classes)
.unwrap_or_else(|_| panic!("tailwind values should parse: {classes}"));
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(
style.text_shadow,
Some(
[
TextShadow {
offset_x: Length::Px(0.0),
offset_y: Length::Px(1.0),
blur_radius: Length::Px(0.0),
color: ColorInput::Value(Color([251, 44, 54, 255])),
},
TextShadow {
offset_x: Length::Px(0.0),
offset_y: Length::Px(1.0),
blur_radius: Length::Px(1.0),
color: ColorInput::Value(Color([251, 44, 54, 255])),
},
TextShadow {
offset_x: Length::Px(0.0),
offset_y: Length::Px(2.0),
blur_radius: Length::Px(2.0),
color: ColorInput::Value(Color([251, 44, 54, 255])),
},
]
.into()
)
);
}
}
#[test]
fn test_bare_rounded_is_radius_sm() {
assert_eq!(
TailwindProperty::parse("rounded"),
Some(TailwindProperty::Rounded(TwRounded(Length::Rem(0.25))))
);
}
#[test]
fn test_negative_color_is_rejected() {
assert_eq!(TailwindProperty::parse("-bg-red-500"), None);
assert_eq!(TailwindProperty::parse("-text-blue-500"), None);
}
#[test]
fn test_negative_grid_line() {
assert_eq!(
TailwindProperty::parse("-col-start-1"),
Some(TailwindProperty::GridColumnStart(GridPlacement::Line(-1)))
);
}
#[test]
fn test_logical_resolves_to_physical_ltr() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("ms-4 me-2 ps-3 pe-1").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.margin_left, Length::from_spacing(4.0));
assert_eq!(style.margin_right, Length::from_spacing(2.0));
assert_eq!(style.padding_left, Length::from_spacing(3.0));
assert_eq!(style.padding_right, Length::from_spacing(1.0));
}
#[test]
fn test_logical_physical_cascade_order_ltr() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("ms-2 ml-4").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.margin_left, Length::from_spacing(4.0));
let values = TailwindValues::from_str("ml-4 ms-2").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.margin_left, Length::from_spacing(2.0));
}
#[test]
fn test_logical_resolves_to_physical_rtl() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("ms-4 me-2 ps-3 pe-1").unwrap();
let mut block = values.into_declaration_block(viewport);
block.push(StyleDeclaration::direction(Direction::Rtl), false);
let style = Style::from(block).inherit(&ComputedStyle::default());
assert_eq!(style.margin_right, Length::from_spacing(4.0));
assert_eq!(style.margin_left, Length::from_spacing(2.0));
assert_eq!(style.padding_right, Length::from_spacing(3.0));
assert_eq!(style.padding_left, Length::from_spacing(1.0));
}
#[test]
fn test_logical_resolves_when_direction_declared_after() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("ms-4").unwrap();
let mut block = values.into_declaration_block(viewport);
block.push(StyleDeclaration::direction(Direction::Rtl), false);
let style = Style::from(block).inherit(&ComputedStyle::default());
assert_eq!(style.margin_right, Length::from_spacing(4.0));
assert_eq!(style.margin_left, Length::Px(0.0));
}
#[test]
fn test_filter_none_clears_previous_filters() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("blur-sm brightness-150 filter-none").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.filter, Filters::default());
let values = TailwindValues::from_str("backdrop-blur-sm backdrop-filter-none").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.backdrop_filter, Filters::default());
}
#[test]
fn test_font_numeric_weight() {
assert_eq!(
TailwindProperty::parse("font-700"),
Some(TailwindProperty::FontWeight(FontWeight::from(700.0)))
);
}
#[test]
fn test_line_clamp_none() {
assert_eq!(
TailwindProperty::parse("line-clamp-none"),
Some(TailwindProperty::LineClamp(LineClamp::from(0)))
);
}
#[test]
fn test_bg_auto() {
assert_eq!(
TailwindProperty::parse("bg-auto"),
Some(TailwindProperty::BackgroundSize(BackgroundSize::Explicit {
width: Length::Auto,
height: Length::Auto,
}))
);
}
#[test]
fn test_bg_repeat_v4_names() {
assert_eq!(
TailwindProperty::parse("bg-repeat-round"),
Some(TailwindProperty::BackgroundRepeat(BackgroundRepeat::round()))
);
assert_eq!(
TailwindProperty::parse("bg-repeat-space"),
Some(TailwindProperty::BackgroundRepeat(BackgroundRepeat::space()))
);
}
#[test]
fn test_grid_cols_none() {
assert_eq!(
TailwindProperty::parse("grid-cols-none"),
Some(TailwindProperty::GridTemplateColumns(TwGridTemplate(
Vec::new()
)))
);
}
#[test]
fn test_col_auto_row_auto() {
assert_eq!(
TailwindProperty::parse("col-auto"),
Some(TailwindProperty::GridColumn(GridLine {
start: GridPlacement::auto(),
end: GridPlacement::auto(),
}))
);
assert_eq!(
TailwindProperty::parse("row-auto"),
Some(TailwindProperty::GridRow(GridLine {
start: GridPlacement::auto(),
end: GridPlacement::auto(),
}))
);
}
#[test]
fn test_shadow_md_is_composite() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("shadow-md").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.box_shadow.as_ref().map(|s| s.len()), Some(2));
}
#[test]
fn test_text_shadow_sm_is_composite() {
let viewport = Viewport::new((100, 100));
let values = TailwindValues::from_str("text-shadow-sm").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.text_shadow.as_ref().map(|s| s.len()), Some(3));
}
#[test]
fn test_shadow_none_overrides_color_in_either_order() {
let viewport = Viewport::new((100, 100));
for classes in ["shadow-none shadow-red-500", "shadow-red-500 shadow-none"] {
let values = TailwindValues::from_str(classes).unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
assert_eq!(style.box_shadow, None, "case: {classes}");
}
}
#[test]
fn test_bg_conic_standalone() {
assert_eq!(
TailwindProperty::parse("bg-conic"),
Some(TailwindProperty::BgConicAngle(Angle::zero()))
);
}
#[test]
fn test_gradient_stop_position_is_used_in_apply() {
let viewport = Viewport::new((100, 100));
let values =
TailwindValues::from_str("bg-linear-to-r from-red-500 from-10% to-blue-500 to-80%").unwrap();
let style =
Style::from(values.into_declaration_block(viewport)).inherit(&ComputedStyle::default());
let images = style.background_image.as_deref().unwrap();
let [BackgroundImage::Linear(gradient)] = images else {
panic!("expected a single linear gradient");
};
let positions: Vec<Length> = gradient
.stops
.iter()
.filter_map(|s| match s {
GradientStop::ColorHint {
hint: Some(StopPosition(pos)),
..
} => Some(*pos),
_ => None,
})
.collect();
assert_eq!(
positions,
vec![Length::Percentage(10.0), Length::Percentage(80.0)]
);
}
#[test]
fn test_border_width_implies_solid_and_per_side_color() {
let viewport = Viewport::new((100, 100));
let computed = |tw: &str| {
Style::from(
TailwindValues::from_str(tw)
.expect("tailwind values should parse")
.into_declaration_block(viewport),
)
.inherit(&ComputedStyle::default())
};
let top = computed("border-t-8");
assert_eq!(top.border_top_width, Length::Px(8.0));
assert_eq!(top.border_top_style, BorderStyle::Solid);
assert_eq!(top.border_bottom_style, BorderStyle::None);
let all = computed("border-4");
assert_eq!(all.border_top_style, BorderStyle::Solid);
assert_eq!(all.border_right_style, BorderStyle::Solid);
assert_eq!(all.border_bottom_style, BorderStyle::Solid);
assert_eq!(all.border_left_style, BorderStyle::Solid);
let dashed = computed("border-2 border-dashed");
assert_eq!(dashed.border_top_style, BorderStyle::Dashed);
assert_eq!(
TailwindProperty::parse("border-t-blue-500"),
Some(TailwindProperty::BorderTopColor(ColorInput::Value(Color(
[43, 127, 255, 255]
))))
);
let bar = computed("border-t-8 border-t-blue-500");
assert_eq!(bar.border_top_width, Length::Px(8.0));
assert_eq!(bar.border_top_style, BorderStyle::Solid);
assert_eq!(
bar.border_top_color,
ColorInput::Value(Color([43, 127, 255, 255]))
);
}