Litavis
view release on metacpan or search on metacpan
t/10-regression.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use_ok('Litavis');
# Helper
sub compile_css {
my ($css, %opts) = @_;
my $d = Litavis->new(%opts);
$d->parse($css);
return $d->compile();
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Crayon dedup bug â hash modification during iteration
#
# Crayon's _dedupe_struct modified the hash it was iterating over,
# causing some identical selectors to NOT be merged, and others
# to be incorrectly merged when cascade position mattered.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $css = compile_css('.z { color: red; } .a { color: red; } .m { color: red; }');
# All three have identical props and no intervening conflicts â merge
like($css, qr/\.z/, 'hash iter bug: .z present');
like($css, qr/\.a/, 'hash iter bug: .a present');
like($css, qr/\.m/, 'hash iter bug: .m present');
# Should be merged into one rule
my @rules = ($css =~ /\{/g);
is(scalar @rules, 1, 'hash iter bug: all three merged into one rule');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Crayon unordered hash â sort keys alphabetises
#
# Crayon uses `sort keys` which alphabetises selectors, destroying
# source order. Litavis must preserve insertion order.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $css = compile_css('
.zebra { padding: 1px; }
.apple { padding: 2px; }
.mango { padding: 3px; }
', dedupe => 0);
# Must be in insertion order, NOT alphabetical
my ($z_pos) = ($css =~ /\.zebra/g) ? ($-[0]) : (-1);
my ($a_pos) = ($css =~ /\.apple/g) ? ($-[0]) : (-1);
my ($m_pos) = ($css =~ /\.mango/g) ? ($-[0]) : (-1);
ok($z_pos < $a_pos, 'order regression: .zebra before .apple');
ok($a_pos < $m_pos, 'order regression: .apple before .mango');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Cascade reordering â merging loses cascade position
#
# .reset { color: black; }
# .theme { color: red; }
# .override { color: black; }
#
# Merging .reset and .override would place .override before .theme,
# changing the cascade result for any element with both classes.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $css = compile_css('
.reset { color: black; }
.theme { color: red; }
.override { color: black; }
');
# .override must come AFTER .theme in the output
my ($theme_pos) = ($css =~ /\.theme/g) ? ($-[0]) : (-1);
my ($override_pos) = ($css =~ /\.override/g) ? ($-[0]) : (-1);
ok($override_pos > $theme_pos,
'cascade regression: .override stays after .theme');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Load order preserved across multiple parse calls
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $d = Litavis->new(dedupe => 0);
$d->parse('.first { color: red; }');
$d->parse('.second { color: blue; }');
$d->parse('.third { color: green; }');
my $css = $d->compile();
like($css, qr/\.first.*\.second.*\.third/s,
'load order regression: multiple parse calls preserve order');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Large file (200+ selectors) does not corrupt order
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $d = Litavis->new(dedupe => 0);
my @expected_order;
for my $i (1..200) {
$d->parse(".sel-$i { order: $i; }");
push @expected_order, $i;
}
my $css = $d->compile();
# Extract selector numbers in order from output
my @found_order;
while ($css =~ /\.sel-(\d+)/g) {
push @found_order, $1;
}
is_deeply(\@found_order, \@expected_order,
'large file regression: 200 selectors in correct order');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Same-selector merge must preserve later values
#
# If .btn appears twice with different color values, the later
# one must win (cascade rules).
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $css = compile_css('
.btn { color: red; padding: 8px; }
.btn { color: blue; margin: 4px; }
');
like($css, qr/color:blue/, 'same-sel regression: later color wins');
like($css, qr/padding:8px/, 'same-sel regression: first-only prop kept');
like($css, qr/margin:4px/, 'same-sel regression: second-only prop added');
unlike($css, qr/color:red/, 'same-sel regression: earlier color overwritten');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Variable resolution across parse calls
#
# Variables defined in one parse call must be visible in later
# parse calls (global scope accumulates).
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $d = Litavis->new;
$d->parse('$color: red;');
$d->parse('.a { color: $color; }');
my $css = $d->compile();
like($css, qr/\.a\{color:red;\}/, 'var cross-parse regression: resolved across calls');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Mixin expansion with literal values
#
# Mixin values should expand correctly when they contain
# literal (non-variable) values.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $css = compile_css('
%box: (
padding: 8px;
margin: 0;
);
.card { %box; color: red; }
.panel { %box; color: blue; }
');
like($css, qr/\.card.*padding:8px/s, 'mixin regression: card gets padding');
like($css, qr/\.panel.*padding:8px/s, 'mixin regression: panel gets padding');
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Regression: Comments between properties don't break parsing
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
{
my $css = compile_css('
.a {
color: red; /* primary */
background: blue; /* secondary */
/* border: none; -- disabled */
font-size: 14px;
}
( run in 2.058 seconds using v1.01-cache-2.11-cpan-df04353d9ac )