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 { let file = assert_fs::NamedTempFile::new("test.css")?; file.write_str( r#" .foo { border: none; } "#, )?; Ok(file) } fn test_file2() -> Result { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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 ' 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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> { 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(()) }