blob: 943beaa71516569ce02cacd0a55c5790bf53a345 [file] [log] [blame]
use assert_cmd::prelude::*;
use assert_fs::fixture::FixtureError;
use assert_fs::prelude::*;
use indoc::indoc;
use lightningcss::css_modules::CssModuleExport;
use predicates::prelude::*;
use std::collections::HashMap;
use std::fs;
use std::process::Command;
fn test_file() -> Result<assert_fs::NamedTempFile, FixtureError> {
let file = assert_fs::NamedTempFile::new("test.css")?;
file.write_str(
r#"
.foo {
border: none;
}
"#,
)?;
Ok(file)
}
fn test_file2() -> Result<assert_fs::NamedTempFile, FixtureError> {
let file = assert_fs::NamedTempFile::new("test2.css")?;
file.write_str(
r#"
.foo {
color: yellow;
}
"#,
)?;
Ok(file)
}
fn css_module_test_vals() -> (String, String, String) {
let exports: HashMap<&str, CssModuleExport> = HashMap::from([
(
"fade",
CssModuleExport {
name: "EgL3uq_fade".into(),
composes: vec![],
is_referenced: false,
},
),
(
"foo",
CssModuleExport {
name: "EgL3uq_foo".into(),
composes: vec![],
is_referenced: false,
},
),
(
"circles",
CssModuleExport {
name: "EgL3uq_circles".into(),
composes: vec![],
is_referenced: true,
},
),
(
"id",
CssModuleExport {
name: "EgL3uq_id".into(),
composes: vec![],
is_referenced: false,
},
),
(
"test",
CssModuleExport {
name: "EgL3uq_test".into(),
composes: vec![],
is_referenced: true,
},
),
]);
(
r#"
.foo {
color: red;
}
#id {
animation: 2s test;
}
@keyframes test {
from { color: red }
to { color: yellow }
}
@counter-style circles {
symbols: Ⓒ;
}
ul {
list-style: circles;
}
@keyframes fade {
from { opacity: 0 }
to { opacity: 1 }
}
"#
.into(),
indoc! {r#"
.EgL3uq_foo {
color: red;
}
#EgL3uq_id {
animation: 2s EgL3uq_test;
}
@keyframes EgL3uq_test {
from {
color: red;
}
to {
color: #ff0;
}
}
@counter-style EgL3uq_circles {
symbols: Ⓒ;
}
ul {
list-style: EgL3uq_circles;
}
@keyframes EgL3uq_fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
"#}
.into(),
serde_json::to_string(&exports).unwrap(),
)
}
#[test]
fn valid_input_file() -> Result<(), Box<dyn std::error::Error>> {
let file = assert_fs::NamedTempFile::new("test.css")?;
file.write_str(
r#"
.foo {
border: none;
}
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
.foo {
border: none;
}"#}));
Ok(())
}
#[test]
fn empty_input_file() -> Result<(), Box<dyn std::error::Error>> {
let file = assert_fs::NamedTempFile::new("test.css")?;
file.write_str("")?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(file.path());
cmd.assert().success();
Ok(())
}
#[test]
fn output_file_option() -> Result<(), Box<dyn std::error::Error>> {
let infile = test_file()?;
let outfile = assert_fs::NamedTempFile::new("test.out")?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg("--output-file").arg(outfile.path());
cmd.assert().success();
outfile.assert(predicate::str::contains(indoc! {r#"
.foo {
border: none;
}"#}));
Ok(())
}
#[test]
fn output_file_option_create_missing_directories() -> Result<(), Box<dyn std::error::Error>> {
let infile = test_file()?;
let outdir = assert_fs::TempDir::new()?;
let outfile = outdir.child("out.css");
outdir.close()?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg("--output-file").arg(outfile.path());
cmd.assert().success();
outfile.assert(predicate::str::contains(indoc! {
r#"
.foo {
border: none;
}
"#
}));
fs::remove_dir_all(outfile.parent().unwrap())?;
Ok(())
}
#[test]
fn multiple_input_files() -> Result<(), Box<dyn std::error::Error>> {
let infile = test_file()?;
let infile2 = test_file2()?;
let outdir = assert_fs::TempDir::new()?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg(infile2.path());
cmd.arg("--output-dir").arg(outdir.path());
cmd.assert().success();
outdir
.child(infile.file_name().unwrap())
.assert(predicate::str::contains(indoc! {r#"
.foo {
border: none;
}"#}));
outdir
.child(infile2.file_name().unwrap())
.assert(predicate::str::contains(indoc! {r#"
.foo {
color: #ff0;
}"#}));
Ok(())
}
#[test]
fn multiple_input_files_out_file() -> Result<(), Box<dyn std::error::Error>> {
let infile = test_file()?;
let infile2 = test_file2()?;
let outdir = assert_fs::TempDir::new()?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg(infile2.path());
cmd.arg("--output-file").arg(outdir.path());
cmd.assert().failure();
Ok(())
}
#[test]
fn multiple_input_files_stdout() -> Result<(), Box<dyn std::error::Error>> {
let infile = test_file()?;
let infile2 = test_file2()?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg(infile2.path());
cmd.assert().failure();
Ok(())
}
#[test]
fn minify_option() -> Result<(), Box<dyn std::error::Error>> {
let infile = test_file()?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg("--minify");
cmd
.assert()
.success()
.stdout(predicate::str::contains(indoc! {r#".foo{border:none}"#}));
Ok(())
}
#[test]
fn nesting_option() -> Result<(), Box<dyn std::error::Error>> {
let infile = assert_fs::NamedTempFile::new("test.css")?;
infile.write_str(
r#"
.foo {
color: blue;
& > .bar { color: red; }
}
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg("--targets=defaults");
cmd.arg("--nesting");
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
.foo {
color: #00f;
}
.foo > .bar {
color: red;
}
"#}));
Ok(())
}
#[test]
fn css_modules_infer_output_file() -> Result<(), Box<dyn std::error::Error>> {
let (input, _, exports) = css_module_test_vals();
let infile = assert_fs::NamedTempFile::new("test.css")?;
let outfile = assert_fs::NamedTempFile::new("out.css")?;
infile.write_str(&input)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(infile.path().parent().unwrap());
cmd.arg(infile.path());
cmd.arg("--css-modules");
cmd.arg("-o").arg(outfile.path());
cmd.assert().success();
let expected: serde_json::Value = serde_json::from_str(&exports)?;
let actual: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(outfile.path().with_extension("json"))?)?;
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn css_modules_output_target_option() -> Result<(), Box<dyn std::error::Error>> {
let (input, _, exports) = css_module_test_vals();
let infile = assert_fs::NamedTempFile::new("test.css")?;
let outfile = assert_fs::NamedTempFile::new("out.css")?;
let modules_file = assert_fs::NamedTempFile::new("module.json")?;
infile.write_str(&input)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(infile.path().parent().unwrap());
cmd.arg(infile.path());
cmd.arg("-o").arg(outfile.path());
cmd.arg("--css-modules").arg(modules_file.path());
cmd.assert().success();
let expected: serde_json::Value = serde_json::from_str(&exports)?;
let actual: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(modules_file.path())?)?;
assert_eq!(expected, actual);
Ok(())
}
#[test]
fn css_modules_stdout() -> Result<(), Box<dyn std::error::Error>> {
let (input, out_code, exports) = css_module_test_vals();
let infile = assert_fs::NamedTempFile::new("test.css")?;
infile.write_str(&input)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(infile.path().parent().unwrap());
cmd.arg(infile.path());
cmd.arg("--css-modules");
let assert = cmd.assert().success();
let output = assert.get_output();
let expected: serde_json::Value = serde_json::from_str(&exports)?;
let actual: serde_json::Value = serde_json::from_slice(&output.stdout)?;
assert_eq!(out_code, actual.pointer("/code").unwrap().as_str().unwrap());
assert_eq!(&expected, actual.pointer("/exports").unwrap());
Ok(())
}
#[test]
fn css_modules_pattern() -> Result<(), Box<dyn std::error::Error>> {
let (input, _, _) = css_module_test_vals();
let infile = assert_fs::NamedTempFile::new("test.css")?;
infile.write_str(&input)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(infile.path().parent().unwrap());
cmd.arg(infile.path());
cmd.arg("--css-modules");
cmd.arg("--css-modules-pattern").arg("[name]-[hash]-[local]");
cmd.assert().success().stdout(predicate::str::contains("test-EgL3uq-foo"));
Ok(())
}
#[test]
fn css_modules_next_64299() -> Result<(), Box<dyn std::error::Error>> {
let file = assert_fs::NamedTempFile::new("test.css")?;
file.write_str(
"
.blue {
background: blue;
:global {
.red {
background: red;
}
}
&:global {
&.green {
background: green;
}
}
}
",
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(file.path());
cmd.arg("--css-modules");
cmd.assert().failure();
Ok(())
}
#[test]
fn sourcemap() -> Result<(), Box<dyn std::error::Error>> {
let (input, _, _) = css_module_test_vals();
let infile = assert_fs::NamedTempFile::new("test.css")?;
let outdir = assert_fs::TempDir::new()?;
let outfile = outdir.child("out.css");
infile.write_str(&input)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(infile.path().parent().unwrap());
cmd.arg(infile.path());
cmd.arg("-o").arg(outfile.path());
cmd.arg("--sourcemap");
cmd.assert().success();
outfile.assert(predicate::str::contains(&format!(
"/*# sourceMappingURL={}.map */",
outfile.path().to_str().unwrap()
)));
let mapfile = outdir.child("out.css.map");
mapfile.assert(predicate::str::contains(r#""version":3"#));
mapfile.assert(predicate::str::contains(r#""sources":["test.css"]"#));
mapfile.assert(predicate::str::contains(
r#""mappings":"AACM;;;;AAIA;;;;AAIA;;;;;;;;;;AAKA;;;;AAIA;;;;AAIA""#,
));
Ok(())
}
#[test]
fn targets() -> Result<(), Box<dyn std::error::Error>> {
let file = assert_fs::NamedTempFile::new("test.css")?;
file.write_str(
r#"
@custom-media --foo print;
@media (--foo) {
.a { color: red }
}
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(file.path());
cmd.arg("--custom-media");
cmd.arg("--targets").arg("last 1 Chrome version");
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
@media print {
.a {
color: red;
}
}"#}));
Ok(())
}
#[test]
fn preserve_custom_media() -> Result<(), Box<dyn std::error::Error>> {
let file = assert_fs::NamedTempFile::new("test.css")?;
file.write_str(
r#"
@custom-media --foo print;
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(file.path());
cmd.arg("--custom-media");
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
@custom-media --foo print;
"#}));
Ok(())
}
#[test]
/// Test command line argument parsing failing when `--targets` is used at the same time as `--browserslist`.
/// The two options are mutually exclusive.
fn browserslist_targets_exclusive() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg("--targets").arg("defaults");
cmd.arg("--browserslist");
cmd.assert().failure().stderr(predicate::str::contains(indoc! {r#"
error: The argument '--targets <TARGETS>' cannot be used with '--browserslist'
"#}));
Ok(())
}
#[test]
/// Test browserslist defaults being applied when no configuration is provided or discovered.
///
/// Note: This test might fail in unhygienic environments and should ideally run inside a chroot.
/// We have no control over the contents of our temp dir's parent dir (e.g. `/tmp`).
/// If this parent dir or its ancestors contain a `browserslist`, `.browserslistrc` or `package.json`
/// file, then configuration will be read from there, instead of applying defaults.
fn browserslist_defaults() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
/// Test browserslist configuration being read from the `BROWSERSLIST` environment variable.
fn browserslist_env_config() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
border-radius: 1rem;
}
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.env("BROWSERSLIST", "safari 4");
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
/// Test browserslist configuration being read from the file configured
/// by setting the `BROWSERSLIST_CONFIG` environment variable.
fn browserslist_env_config_file() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
border-radius: 1rem;
}
"#,
)?;
let config = dir.child("config");
config.write_str(
r#"
safari 4
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.env("BROWSERSLIST_CONFIG", config.path());
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
/// Test `browserslist` configuration file being read.
fn browserslist_config_discovery() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
border-radius: 1rem;
}
"#,
)?;
let config = dir.child("browserslist");
config.write_str(
r#"
safari 4
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
/// Test `.browserslistrc` configuration file being read.
fn browserslist_rc_discovery() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
border-radius: 1rem;
}
"#,
)?;
let config = dir.child(".browserslistrc");
config.write_str(
r#"
safari 4
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
/// Test `package.json` configuration section being read.
fn browserslist_package_discovery() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
border-radius: 1rem;
}
"#,
)?;
let config = dir.child("package.json");
config.write_str(
r#"
{
"browserslist": "safari 4"
}
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
/// Test environment targets being applied from the `NODE_ENV` environment variable.
fn browserslist_environment_from_node_env() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
border-radius: 1rem;
}
"#,
)?;
let config = dir.child("browserslist");
config.write_str(
r#"
last 1 Chrome version
[legacy]
safari 4
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.env("NODE_ENV", "legacy");
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
/// Test environment targets being applied from the `BROWSERSLIST_ENV` environment variable.
fn browserslist_environment_from_browserslist_env() -> Result<(), Box<dyn std::error::Error>> {
let dir = assert_fs::TempDir::new()?;
let file = dir.child("test.css");
file.write_str(
r#"
* {
border-radius: 1rem;
}
"#,
)?;
let config = dir.child("browserslist");
config.write_str(
r#"
last 1 Chrome version
[legacy]
safari 4
"#,
)?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.current_dir(dir.path());
cmd.env_clear();
cmd.env("BROWSERSLIST_ENV", "legacy");
cmd.arg("--browserslist");
cmd.arg(file.path());
cmd.assert().success().stdout(predicate::str::contains(indoc! {r#"
* {
-webkit-border-radius: 1rem;
border-radius: 1rem;
}
"#}));
Ok(())
}
#[test]
fn next_66191() -> Result<(), Box<dyn std::error::Error>> {
let infile = assert_fs::NamedTempFile::new("test.css")?;
infile.write_str(
r#"
.cb:is(input:checked) {
margin: 3rem;
}
"#,
)?;
let outfile = assert_fs::NamedTempFile::new("test.out")?;
let mut cmd = Command::cargo_bin("lightningcss")?;
cmd.arg(infile.path());
cmd.arg("--output-file").arg(outfile.path());
cmd.assert().success();
outfile.assert(predicate::str::contains(indoc! {r#".cb:is(input:checked)"#}));
Ok(())
}