use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU32, Ordering};
use undercoat::{CompileError, Options, compile_file};
static COUNTER: AtomicU32 = AtomicU32::new(0);
fn project(files: &[(&str, &str)]) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"minibuild-scss-modules-{}-{}",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed)
));
std::fs::create_dir_all(&dir).expect("mkdir");
for (name, text) in files {
std::fs::write(dir.join(name), text).expect("write fixture");
}
dir.join("main.scss")
}
fn compile(path: &Path) -> Result<undercoat::Output, CompileError> {
let source = std::fs::read_to_string(path).expect("read scss");
compile_file(path, &source, &Options::default())
}
fn compile_err(path: &Path) -> CompileError {
match compile(path) {
Ok(out) => panic!(
"{} should have failed, but produced:\n{}",
path.display(),
out.css
),
Err(e) => e,
}
}
#[test]
fn star_module_variable_is_not_exported() {
let path = project(&[
("_two.scss", "$v: two;\n"),
("_relay.scss", "@use 'two' as *;\n"),
("main.scss", "@use 'relay' as *;\n.z { v: $v }\n"),
]);
let err = compile_err(&path);
assert_eq!(err.message, "Undefined variable.");
assert_eq!((err.line, err.column), (Some(2), Some(8)));
let path = project(&[
("_two.scss", "$v: two;\n"),
("_relay.scss", "@use 'two' as *;\n"),
("main.scss", "@use 'relay';\n.z { v: relay.$v }\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined variable.");
}
#[test]
fn star_module_mixin_and_function_are_not_exported() {
let path = project(&[
(
"_m.scss",
"@mixin mm { x: 1 }\n@function ff() { @return 42; }\n",
),
("_relay.scss", "@use 'm' as *;\n"),
("main.scss", "@use 'relay' as *;\n.z { @include mm; }\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined mixin.");
let path = project(&[
(
"_m.scss",
"@mixin mm { x: 1 }\n@function ff() { @return 42; }\n",
),
("_relay.scss", "@use 'm' as *;\n"),
("main.scss", "@use 'relay' as *;\n.z { f: ff() }\n"),
]);
let out = compile(&path).expect("compile");
assert!(out.css.contains("f: ff()"), "{}", out.css);
}
#[test]
fn star_module_builtin_variable_is_not_exported() {
let path = project(&[
("_relay.scss", "@use 'sass:math' as *;\n"),
("main.scss", "@use 'relay' as *;\n.z { v: $pi }\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined variable.");
}
#[test]
fn star_members_stay_usable_inside_the_module() {
let path = project(&[
(
"_m.scss",
"$v: two;\n@mixin mm { x: 1 }\n@function ff() { @return 42; }\n",
),
(
"_relay.scss",
"@use 'm' as *;\n.from-relay { v: $v; @include mm; f: ff() }\n",
),
("main.scss", "@use 'relay';\n"),
]);
let out = compile(&path).expect("compile");
let expected = ".from-relay {\n v: two;\n x: 1;\n f: 42;\n}";
assert_eq!(out.css, expected);
}
#[test]
fn meta_existence_sees_star_members_only_in_scope() {
let path = project(&[
(
"_m.scss",
"$v: two;\n@mixin mm { x: 1 }\n@function ff() { @return 42; }\n",
),
(
"_relay.scss",
"@use 'm' as *;\n.from-relay { v: variable-exists(v); m: mixin-exists(mm); f: function-exists(ff) }\n",
),
("main.scss", "@use 'relay';\n"),
]);
let out = compile(&path).expect("compile");
assert!(
out.css.contains("v: true") && out.css.contains("m: true") && out.css.contains("f: true"),
"{}",
out.css
);
let path = project(&[
(
"_m.scss",
"$v: two;\n@mixin mm { x: 1 }\n@function ff() { @return 42; }\n",
),
("_relay.scss", "@use 'm' as *;\n.relay { v: $v }\n"),
(
"main.scss",
"@use 'relay' as *;\n.z { m: mixin-exists(mm); f: function-exists(ff); v: variable-exists(v) }\n",
),
]);
let out = compile(&path).expect("compile");
assert!(
out.css.contains("m: false")
&& out.css.contains("f: false")
&& out.css.contains("v: false"),
"{}",
out.css
);
}
#[test]
fn forward_still_exports_members() {
let path = project(&[
(
"_one.scss",
"$v: one;\n@mixin mm { x: 1 }\n@function ff() { @return 42; }\n",
),
("_relay.scss", "@forward 'one';\n"),
(
"main.scss",
"@use 'relay' as *;\n.z { v: $v; @include mm; f: ff() }\n",
),
]);
let out = compile(&path).expect("compile");
assert_eq!(out.css, ".z {\n v: one;\n x: 1;\n f: 42;\n}");
}
#[test]
fn forward_and_star_coexist_in_one_module() {
let path = project(&[
("_one.scss", "$v: one;\n"),
("_two.scss", "$v: two;\n$w: only-in-relay;\n"),
(
"_relay.scss",
"@forward 'one';\n@use 'two' as *;\n.in-relay { w: $w }\n",
),
("main.scss", "@use 'relay' as *;\n.z { v: $v }\n"),
]);
let out = compile(&path).expect("compile");
assert_eq!(
out.css,
".in-relay {\n w: only-in-relay;\n}\n\n.z {\n v: one;\n}"
);
let path = project(&[
("_one.scss", "$v: one;\n"),
("_two.scss", "$v: two;\n$w: only-in-relay;\n"),
("_relay.scss", "@forward 'one';\n@use 'two' as *;\n"),
("main.scss", "@use 'relay' as *;\n.z { w: $w }\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined variable.");
}
#[test]
fn star_members_do_not_shadow_forwarded_exports() {
let path = project(&[
("_one.scss", "$v: one;\n"),
("_two.scss", "$v: two;\n"),
("_relay.scss", "@use 'two' as *;\n"),
(
"main.scss",
"@use 'one' as *;\n@use 'relay' as *;\n.z { v: $v }\n",
),
]);
let out = compile(&path).expect("compile");
assert_eq!(out.css, ".z {\n v: one;\n}");
}