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-forward-{}-{}",
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 forwarded_members_are_not_visible_in_the_forwarding_module() {
let path = project(&[
("_lib.scss", "$v: 1;\n"),
("_relay.scss", "@forward 'lib';\n.r { v: $v }\n"),
("main.scss", "@use 'relay';\n"),
]);
let err = compile_err(&path);
assert_eq!(err.message, "Undefined variable.");
assert_eq!((err.line, err.column), (Some(2), Some(8)));
}
#[test]
fn star_import_does_not_leak_into_forwards() {
let path = project(&[
("_lib.scss", "$v: 1;\n"),
("_relay.scss", "@use 'lib' as *;\n"),
("main.scss", "@use 'relay' as *;\n.z { v: $v }\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined variable.");
}
#[test]
fn configuring_a_non_default_variable_is_an_error() {
let path = project(&[
("_lib.scss", "$v: 1;\n$w: 2 !default;\n"),
("main.scss", "@use 'lib' with ($v: 3);\n"),
]);
let err = compile_err(&path);
assert_eq!(
err.message,
"This variable was not declared with !default in the @used module."
);
assert_eq!((err.line, err.column), (Some(1), Some(17)));
}
#[test]
fn configuring_an_unknown_variable_is_an_error() {
let path = project(&[
("_lib.scss", "$v: 1 !default;\n"),
("main.scss", "@use 'lib' with ($nope: 3);\n"),
]);
assert_eq!(
compile_err(&path).message,
"This variable was not declared with !default in the @used module."
);
}
#[test]
fn configuring_a_module_twice_is_an_error() {
let path = project(&[
("_lib.scss", "$v: 1 !default;\n"),
(
"main.scss",
"@use 'lib' with ($v: 1);\n@use 'lib' as b with ($v: 2);\n",
),
]);
let err = compile_err(&path);
assert_eq!(
err.message,
"This module was already loaded, so it can't be configured using \"with\"."
);
assert_eq!(err.line, Some(2));
}
#[test]
fn configuring_an_already_loaded_module_is_an_error() {
let path = project(&[
("_lib.scss", "$v: 1 !default;\n"),
("main.scss", "@use 'lib';\n@use 'lib' as b with ($v: 2);\n"),
]);
assert_eq!(
compile_err(&path).message,
"This module was already loaded, so it can't be configured using \"with\"."
);
}
#[test]
fn two_forwards_defining_the_same_variable_conflict() {
let path = project(&[
("_a.scss", "$x: 1;\n"),
("_b.scss", "$x: 2;\n"),
("_relay.scss", "@forward 'a';\n@forward 'b';\n"),
("main.scss", "@use 'relay' as r;\n"),
]);
let err = compile_err(&path);
assert_eq!(
err.message,
"Two forwarded modules both define a variable named $x."
);
assert_eq!(err.line, Some(2));
}
#[test]
fn two_forwards_defining_the_same_function_conflict() {
let path = project(&[
("_a.scss", "@function ff() { @return 1; }\n"),
("_b.scss", "@function ff() { @return 2; }\n"),
("_relay.scss", "@forward 'a';\n@forward 'b';\n"),
("main.scss", "@use 'relay' as r;\n"),
]);
assert_eq!(
compile_err(&path).message,
"Two forwarded modules both define a function named ff."
);
}
#[test]
fn diamond_forwards_do_not_conflict() {
let path = project(&[
("_a.scss", "$x: 1;\n"),
("_c.scss", "@forward 'a';\n"),
("_d.scss", "@forward 'a';\n"),
("_relay.scss", "@forward 'c';\n@forward 'd';\n"),
("main.scss", "@use 'relay' as r;\n.z { v: r.$x }\n"),
]);
let out = compile(&path).expect("compile");
assert_eq!(out.css, ".z {\n v: 1;\n}");
}
#[test]
fn own_members_shadow_forwarded_ones_without_conflict() {
let path = project(&[
("_a.scss", "$x: 1;\n"),
("_relay.scss", "@forward 'a';\n$x: 9;\n"),
("main.scss", "@use 'relay' as r;\n.z { v: r.$x }\n"),
]);
let out = compile(&path).expect("compile");
assert_eq!(out.css, ".z {\n v: 9;\n}");
}
#[test]
fn private_members_are_not_forwarded() {
let path = project(&[
("_lib.scss", "$-p: 1;\n"),
("_relay.scss", "@forward 'lib';\n"),
("main.scss", "@use 'relay' as r;\n.z { v: r.$-p }\n"),
]);
let err = compile_err(&path);
assert_eq!(
err.message,
"Private members can't be accessed from outside their modules."
);
}
#[test]
fn import_of_a_forwarder_exposes_members_locally() {
let path = project(&[
(
"_lib.scss",
"$v: 1;\n@mixin mm { m: 2 }\n@function ff() { @return 3; }\n",
),
("_fwd.scss", "@forward 'lib';\n"),
(
"main.scss",
"@import 'fwd';\n.z { v: $v; @include mm; f: ff() }\n",
),
]);
let out = compile(&path).expect("compile");
assert_eq!(out.css, ".z {\n v: 1;\n m: 2;\n f: 3;\n}");
}
#[test]
fn forward_filter_sees_prefixed_names() {
let path = project(&[
("_lib.scss", "$x: 1;\n"),
("_fwd.scss", "@forward 'lib' as p-* show $p-x;\n"),
("main.scss", "@use 'fwd' as f;\n.z { v: f.$p-x }\n"),
]);
assert_eq!(compile(&path).expect("compile").css, ".z {\n v: 1;\n}");
let path = project(&[
("_lib.scss", "$x: 1;\n"),
("_fwd.scss", "@forward 'lib' as p-* show $x;\n"),
("main.scss", "@use 'fwd' as f;\n.z { v: f.$p-x }\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined variable.");
}
#[test]
fn prefix_does_not_revive_private_members() {
let path = project(&[
("_lib.scss", "$-p: 1;\n"),
("_fwd.scss", "@forward 'lib' as p-*;\n"),
("main.scss", "@use 'fwd' as f;\n.z { v: f.$p--p }\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined variable.");
}
#[test]
fn forward_with_default_is_overridable() {
let path = project(&[
("_base.scss", "$c: red !default;\n.b { c: $c }\n"),
(
"_theme.scss",
"@forward 'base' with ($c: green !default);\n",
),
(
"main.scss",
"@use 'theme' with ($c: purple);\n.z { c: theme.$c }\n",
),
]);
let out = compile(&path).expect("compile");
assert_eq!(out.css, ".b {\n c: purple;\n}\n\n.z {\n c: purple;\n}");
}
#[test]
fn forward_without_default_cannot_be_overridden() {
let path = project(&[
("_base.scss", "$c: red !default;\n"),
("_theme.scss", "@forward 'base' with ($c: green);\n"),
("main.scss", "@use 'theme' with ($c: purple);\n"),
]);
assert_eq!(
compile_err(&path).message,
"This variable was not declared with !default in the @used module."
);
}
#[test]
fn config_values_are_sass_expressions() {
let path = project(&[
("_lib.scss", "$w: 2px !default;\n.l { w: $w }\n"),
(
"main.scss",
"$base: 1px;\n@use 'lib' with ($w: $base * 3);\n.z { w: lib.$w }\n",
),
]);
let out = compile(&path).expect("compile");
assert_eq!(out.css, ".l {\n w: 3px;\n}\n\n.z {\n w: 3px;\n}");
}
#[test]
fn config_expressions_are_evaluated_eagerly() {
let path = project(&[
("_lib.scss", "$v: 1 !default;\n"),
("main.scss", "@use 'lib' with ($v: $undefined);\n"),
]);
assert_eq!(compile_err(&path).message, "Undefined variable.");
}
#[test]
fn config_falls_through_to_the_forwarding_modules_default() {
let path = project(&[
("_e.scss", "$y: 0;\n.a { y: $y }\n"),
(
"_fwd.scss",
"@forward 'e';\n$x: 9 !default;\n.f { x: $x }\n",
),
("main.scss", "@use 'fwd' with ($x: 2);\n.z { v: fwd.$x }\n"),
]);
let out = compile(&path).expect("compile");
assert_eq!(
out.css,
".a {\n y: 0;\n}\n\n.f {\n x: 2;\n}\n\n.z {\n v: 2;\n}"
);
}
#[test]
fn config_consumed_by_the_forwarded_module_does_not_reach_the_forwarder() {
let path = project(&[
("_a.scss", "$x: 1 !default;\n.a { x: $x }\n"),
(
"_fwd.scss",
"@forward 'a';\n$x: 9 !default;\n.f { x: $x }\n",
),
("main.scss", "@use 'fwd' with ($x: 2);\n.z { v: fwd.$x }\n"),
]);
let out = compile(&path).expect("compile");
assert_eq!(
out.css,
".a {\n x: 2;\n}\n\n.f {\n x: 9;\n}\n\n.z {\n v: 9;\n}"
);
}