Selkie.git | t/ | 42-password-strength.rakutest
use Test;
use lib 'lib';
use Selkie::Widget::PasswordStrength :scoring;
# Scoring-only unit test. The widget itself is exercised visually via
# xt/ snapshots (needs notcurses); here we verify the scoring heuristic
# produces sensible numbers and the level-for mapping behaves.
plan 16;
# Empty → 0.
is score-password(''), 0, 'empty password scores 0';
# Very short single-class → weak band.
cmp-ok score-password('abc'), '<', 20, "'abc' is weak";
# Short single-class → still weak.
cmp-ok score-password('password'), '<', 40, "'password' (lowercase only) is below good";
# Adding a second class.
cmp-ok score-password('Password'), '>', score-password('password'),
'mixed-case scores higher than lowercase only';
cmp-ok score-password('Password1'), '>', score-password('Password'),
'digit bump';
cmp-ok score-password('Password1!'), '>', score-password('Password1'),
'symbol bump';
# Very long + diverse → high.
my $strong = 'Correct Horse Battery Staple!';
cmp-ok score-password($strong), '>=', 60, "long diverse passphrase is good or better";
# Monotonic in length for same class-set.
cmp-ok score-password('aaaa'), '<', score-password('aaaaaaaa'),
'longer same-class scores higher';
# Level mapping.
is level-for(0)<label>, '', 'score 0 → no label';
is level-for(5)<label>, 'weak', 'score 5 → weak';
is level-for(50)<label>, 'good', 'score 50 → good';
is level-for(100)<label>, 'very strong', 'score 100 → very strong';
is level-for(0)<slot>, 'password-empty', 'score 0 uses empty theme slot';
is level-for(5)<slot>, 'password-weak', 'score 5 uses weak theme slot';
is level-for(50)<slot>, 'password-good', 'score 50 uses good theme slot';
is level-for(100)<slot>, 'password-very-strong', 'score 100 uses strongest theme slot';