1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#![feature(let_chains)]
#![feature(box_patterns)]
#![feature(iterator_try_reduce)]
#![feature(assert_matches)]
mod directive;
mod eval;
mod expression;
mod literal;
mod plugin;
mod subject;
pub type NomSpan<'a> = LocatedSpan<&'a str, Span>;
use nom_locate::LocatedSpan;
use swc_core::common::Span;
pub use directive::*;
pub use expression::*;
pub use literal::*;
pub use plugin::*;
pub use subject::*;
#[cfg(test)]
mod test {
use std::assert_matches::assert_matches;
use crate::{
Border, Directive, Expression, Literal, Max, Plugin, Position, Subject, SubjectValue,
};
use nom_locate::LocatedSpan;
use swc_core::common::DUMMY_SP;
use test_case::test_case;
#[test]
fn directive() -> anyhow::Result<()> {
let (rest, d) = Directive::parse(LocatedSpan::new_extra(
"-h-4 md:bg-blue text-white! hover:(text-blue bg-white lg:text-black!)",
DUMMY_SP,
))?;
assert!(rest.len() == 0);
Ok(())
}
#[test_case("flex!", None, None, true ; "important")]
#[test_case("underline!", None, None, true ; "important with transparent command")]
#[test_case("min-w-4!", Some(SubjectValue::Value("4")), None, true ; "important with rootless command")]
#[test_case("text-blue-500/40", Some(SubjectValue::Value("blue-500")), Some(SubjectValue::Value("40")), false ; "handles transparent")]
#[test_case("text-white/40!", Some(SubjectValue::Value("white")), Some(SubjectValue::Value("40")), true ; "handles transparent and important")]
fn expression(
s: &str,
value_exp: Option<SubjectValue>,
transparency: Option<SubjectValue>,
important: bool,
) {
let (rest, d) = Expression::parse(LocatedSpan::new_extra(s, DUMMY_SP)).unwrap();
if let Subject::Literal(Literal { value, .. }) = d.subject {
assert_eq!(value, value_exp);
}
assert_eq!(d.important, important);
assert_eq!(d.alpha, transparency);
assert_matches!(*rest, "");
}
#[test_case("relative", Plugin::Position(Position::Relative), None ; "when a subject has no value")]
#[test_case("pl-3.5", Plugin::Pl, Some(SubjectValue::Value("3.5")) ; "when a subject has a dot in it")]
#[test_case("text-red-500", Plugin::Text, Some(SubjectValue::Value("red-500")) ; "when a subject has a dash")]
#[test_case("border-b-4", Plugin::Border(Some(Border::B)), Some(SubjectValue::Value("4")) ; "dash in plugin")]
#[test_case("border-4", Plugin::Border(None), Some(SubjectValue::Value("4")) ; "empty plugin subcommand")]
#[test_case("max-w-4", Plugin::Max(Max::W), Some(SubjectValue::Value("4")) ; "rootless subcommand")]
#[test_case("w-3/4", Plugin::W, Some(SubjectValue::Value("3/4")) ; "when a subject has a forward slash")]
#[test_case("border-[10px]", Plugin::Border(None), Some(SubjectValue::Css("10px")) ; "arbitrary css")]
#[test_case("border-[repeat(6,1fr)]", Plugin::Border(None), Some(SubjectValue::Css("repeat(6,1fr)")) ; "when braces are in arbitrary css")]
#[test_case("border-[min-content min-content]", Plugin::Border(None), Some(SubjectValue::Css("min-content min-content")) ; "when spaces are in arbitrary css")]
fn plugin(s: &str, p: Plugin, v: Option<SubjectValue>) {
let (rest, s) = Subject::parse(LocatedSpan::new_extra(s, DUMMY_SP)).unwrap();
let lit = match s {
Subject::Literal(l) => l,
_ => panic!("should be a group"),
};
assert_eq!(lit.cmd, p, "correct plugin");
assert_eq!(lit.value, v, "correct value");
assert_matches!(*rest, "");
}
#[test_case("text-lg p-4" ; "basic")]
#[test_case("border-b-4 p-4" ; "with subcommand")]
#[test_case(" p-4 border-4" ; "when a statement has irregular gaps")]
#[test_case("dash-modifier:p-4" ; "when a modifier has a dash in it")]
#[test_case("mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8" ; "random prefixes")]
#[test_case("relative rounded-2xl px-6 py-10 bg-primary-500 overflow-hidden shadow-xl sm:px-12 sm:py-20"; "example")]
#[test_case("text-white/40 bg-white/50" ; "chained transparency")]
fn directive_tests(s: &str) {
Directive::parse(LocatedSpan::new_extra(s, DUMMY_SP)).unwrap();
}
#[should_panic]
#[test_case("-mod:sub" ; "when the minus is in the wrong place")]
#[test_case("m0d:p-4" ; "when modifier has a number")]
#[test_case("()" ; "rejects empty group")]
fn parse_failure_tests(s: &str) {
let (rest, d) = Directive::parse(LocatedSpan::new_extra(s, DUMMY_SP)).unwrap();
assert_matches!(*rest, "");
}
#[test_case("40" ; "a number")]
#[test_case("blue-500" ; "a color")]
#[test_case("[10px]" ; "csss")]
fn subject_value(s: &str) {
let (rest, s) = SubjectValue::parse(LocatedSpan::new_extra(s, DUMMY_SP)).unwrap();
assert_matches!(*rest, "");
}
}