#[path = "helpers.rs"] mod helpers; use helpers::{exe_path, Sandbox, ToUrl}; use sass_embedded::{ Options, OptionsBuilder, OutputStyle, Sass, StringOptions, StringOptionsBuilder, Syntax, Url, }; use serde_json::json; mod compile_string { use super::*; mod success { use super::*; mod input { use super::*; #[test] fn compiles_scss_by_default() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string("$a: b; c {d: $a}", StringOptions::default()) .unwrap(); assert_eq!(res.css, "c {\n d: b;\n}"); } #[test] fn compiles_scss_with_explicit_syntax() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "$a: b; c {d: $a}", StringOptionsBuilder::default().syntax(Syntax::Scss).build(), ) .unwrap(); assert_eq!(res.css, "c {\n d: b;\n}"); } #[test] fn compiles_indented_syntax_with_explicit_syntax() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a\n b: c", StringOptionsBuilder::default() .syntax(Syntax::Indented) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}"); } #[test] fn compiles_plain_css_with_explicit_syntax() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a {b: c}", StringOptionsBuilder::default().syntax(Syntax::Css).build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}"); } #[test] fn does_not_take_its_syntax_from_the_url_s_extension() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a {b: c}", StringOptionsBuilder::default() .url(Url::parse("file:///foo.sass").unwrap()) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}"); } } mod loaded_urls { use super::*; #[test] fn is_empty_with_no_url() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string("a {b: c}", StringOptions::default()) .unwrap(); assert!(res.loaded_urls.is_empty()); } #[test] fn contains_the_url_if_one_is_passed() { let url = Url::parse("file:///foo.scss").unwrap(); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a {b: c}", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap(); assert_eq!(res.loaded_urls, vec![url]); } #[test] fn contains_an_immediate_dependency() { let sandbox = Sandbox::default(); let url = sandbox.path().join("input.scss").to_url(); sandbox.write(sandbox.path().join("_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"other\"", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap(); assert_eq!( res.loaded_urls, vec![url, sandbox.path().join("_other.scss").to_url(),] ); } #[test] fn contains_a_transitive_dependency() { let sandbox = Sandbox::default(); let url = sandbox.path().join("input.scss").to_url(); sandbox .write(sandbox.path().join("_midstream.scss"), "@use \"upstream\"") .write(sandbox.path().join("_upstream.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"midstream\"", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap(); assert_eq!( res.loaded_urls, vec![ url, sandbox.path().join("_midstream.scss").to_url(), sandbox.path().join("_upstream.scss").to_url(), ] ); } mod contains_a_dependency_only_once { use super::*; #[test] fn for_at_use() { let sandbox = Sandbox::default(); let url = sandbox.path().join("input.scss").to_url(); sandbox .write(sandbox.path().join("_left.scss"), "@use \"upstream\"") .write(sandbox.path().join("_right.scss"), "@use \"upstream\"") .write(sandbox.path().join("_upstream.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"left\"; @use \"right\"", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap(); assert_eq!( res.loaded_urls, vec![ url, sandbox.path().join("_left.scss").to_url(), sandbox.path().join("_upstream.scss").to_url(), sandbox.path().join("_right.scss").to_url(), ] ); } #[test] fn for_at_import() { let sandbox = Sandbox::default(); let url = sandbox.path().join("input.scss").to_url(); sandbox .write(sandbox.path().join("_left.scss"), "@use \"upstream\"") .write(sandbox.path().join("_right.scss"), "@use \"upstream\"") .write(sandbox.path().join("_upstream.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@import \"left\"; @import \"right\"", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap(); assert_eq!( res.loaded_urls, vec![ url, sandbox.path().join("_left.scss").to_url(), sandbox.path().join("_upstream.scss").to_url(), sandbox.path().join("_right.scss").to_url(), ] ); } } } #[test] fn file_url_is_used_to_resolve_relative_loads() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("foo/bar/_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"other\";", StringOptionsBuilder::default() .url(sandbox.path().join("foo/bar/style.scss").to_url()) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}",); } mod load_paths { use super::*; #[test] fn is_used_to_resolve_loads() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("foo/bar/_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"other\";", StringOptionsBuilder::default() .load_path(sandbox.path().join("foo/bar")) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}",); } #[test] fn resolves_relative_paths() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("foo/bar/_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"bar/other\";", StringOptionsBuilder::default() .load_path(sandbox.path().join("foo")) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}",); } #[test] fn resolves_loads_using_later_paths_if_earlier_ones_do_not_match() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("bar/_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"other\";", StringOptionsBuilder::default() .load_path(sandbox.path().join("foo")) .load_path(sandbox.path().join("bar")) .load_path(sandbox.path().join("baz")) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}",); } #[test] fn does_not_take_precedence_over_loads_relative_to_the_url() { let sandbox = Sandbox::default(); sandbox .write(sandbox.path().join("url/_other.scss"), "a {b: url}") .write( sandbox.path().join("load-path/_other.scss"), "a {b: load path}", ); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"other\";", StringOptionsBuilder::default() .load_path(sandbox.path().join("load-path")) .url(sandbox.path().join("url/input.scss").to_url()) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: url;\n}",); } #[test] fn uses_earlier_paths_in_preference_to_later_ones() { let sandbox = Sandbox::default(); sandbox .write(sandbox.path().join("earlier/_other.scss"), "a {b: earlier}") .write(sandbox.path().join("later/_other.scss"), "a {b: later}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "@use \"other\";", StringOptionsBuilder::default() .load_path(sandbox.path().join("earlier")) .load_path(sandbox.path().join("later")) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: earlier;\n}",); } } #[test] fn recognizes_the_expanded_output_style() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a {b: c}", StringOptionsBuilder::default() .style(OutputStyle::Expanded) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}",); } mod source_map { use super::*; #[test] fn does_not_include_one_by_default() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string("a {b: c}", StringOptions::default()) .unwrap(); assert!(res.source_map.is_none()); } #[test] fn includes_one_if_source_map_is_true() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a {b: c}", StringOptionsBuilder::default().source_map(true).build(), ) .unwrap(); assert!(res.source_map.is_some()); let source_map: serde_json::Value = serde_json::from_str(&res.source_map.unwrap()).unwrap(); assert_eq!(source_map["version"], json!(3)); assert!(source_map["sources"].is_array()); assert!(source_map["names"].is_array()); assert!(source_map["mappings"].is_string()); } #[test] fn includes_one_with_source_content_if_source_map_include_sources_is_true( ) { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a {b: c}", StringOptionsBuilder::default() .source_map(true) .source_map_include_sources(true) .build(), ) .unwrap(); assert!(res.source_map.is_some()); let source_map: serde_json::Value = serde_json::from_str(&res.source_map.unwrap()).unwrap(); assert!(source_map.get("sourcesContent").is_some()); assert!(source_map["sourcesContent"].is_array()); assert!(!source_map["sourcesContent"].as_array().unwrap().is_empty()); } } mod charset { use super::*; #[test] fn emits_at_charset_utf_8_or_bom_for_non_ascii_css_by_default() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string("a {b: あ;}", StringOptions::default()) .unwrap(); assert_eq!(res.css, "@charset \"UTF-8\";\na {\n b: あ;\n}"); } #[test] fn does_not_emit_at_charset_or_bom_if_charset_is_false() { let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile_string( "a {b: あ;}", StringOptionsBuilder::default().charset(false).build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: あ;\n}"); } } } mod error { use super::*; #[test] fn requires_plain_css_with_explicit_syntax() { let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile_string( "$a: b; c {d: $a}", StringOptionsBuilder::default().syntax(Syntax::Css).build(), ) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert!(err.span().unwrap().url.is_none()); } #[test] fn relative_loads_fail_without_a_url() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile_string("@use \"./other\"", StringOptions::default()) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert!(err.span().unwrap().url.is_none()); } #[test] fn relative_loads_fail_with_a_non_file_url() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile_string( "@use \"./other\"", StringOptionsBuilder::default() .url(Url::parse("unknown:style.scss").unwrap()) .build(), ) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert_eq!( err.span().unwrap().url.as_ref().unwrap(), &Url::parse("unknown:style.scss").unwrap(), ); } mod includes_source_span_information { use super::*; #[test] fn in_syntax_errors() { let sandbox = Sandbox::default(); let url = sandbox.path().join("foo.scss").to_url(); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile_string( "a {b:", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert_eq!(err.span().unwrap().url.as_ref().unwrap(), &url); } #[test] fn in_runtime_errors() { let sandbox = Sandbox::default(); let url = sandbox.path().join("foo.scss").to_url(); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile_string( "@error \"oh no\"", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert_eq!(err.span().unwrap().url.as_ref().unwrap(), &url); } #[test] fn with_multi_span_errors() { let sandbox = Sandbox::default(); let url = sandbox.path().join("foo.scss").to_url(); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile_string( "@use \"sass:math\"; @use \"sass:math\"", StringOptionsBuilder::default().url(url.clone()).build(), ) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert_eq!(err.span().unwrap().url.as_ref().unwrap(), &url); } } } } mod compile { use super::*; mod success { use super::*; #[test] fn compiles_scss_for_a_scss_file() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.scss"), "$a: b; c {d: $a}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile(sandbox.path().join("input.scss"), Options::default()) .unwrap(); assert_eq!(res.css, "c {\n d: b;\n}"); } #[test] fn compiles_scss_for_a_file_with_an_unknown_extension() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.asdf"), "$a: b; c {d: $a}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile(sandbox.path().join("input.asdf"), Options::default()) .unwrap(); assert_eq!(res.css, "c {\n d: b;\n}"); } #[test] fn compiles_indented_syntax_for_a_sass_file() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.sass"), "a\n b: c"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile(sandbox.path().join("input.sass"), Options::default()) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}"); } #[test] fn compiles_plain_css_for_a_css_file() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.css"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile(sandbox.path().join("input.css"), Options::default()) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}"); } mod loaded_urls { use super::*; #[test] fn includes_a_relative_path_s_url() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile(sandbox.path().join("input.scss"), Options::default()) .unwrap(); assert_eq!( res.loaded_urls, vec![sandbox.path().join("input.scss").to_url()] ); } #[test] fn includes_an_absolute_path_s_url() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile(sandbox.path().join("input.scss"), Options::default()) .unwrap(); assert_eq!( res.loaded_urls, vec![sandbox.path().join("input.scss").to_url()] ); } #[test] fn contains_a_dependency() { let sandbox = Sandbox::default(); sandbox .write(sandbox.path().join("input.scss"), "@use \"other\"") .write(sandbox.path().join("_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile(sandbox.path().join("input.scss"), Options::default()) .unwrap(); assert_eq!( res.loaded_urls, vec![ sandbox.path().join("input.scss").to_url(), sandbox.path().join("_other.scss").to_url(), ] ); } } #[test] fn the_path_is_used_to_resolve_relative_loads() { let sandbox = Sandbox::default(); sandbox .write(sandbox.path().join("foo/bar/input.scss"), "@use \"other\"") .write(sandbox.path().join("foo/bar/_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile( sandbox.path().join("foo/bar/input.scss"), Options::default(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}"); } mod load_paths { use super::*; #[test] fn is_used_to_resolve_loads() { let sandbox = Sandbox::default(); sandbox .write(sandbox.path().join("input.scss"), "@use \"other\"") .write(sandbox.path().join("foo/bar/_other.scss"), "a {b: c}"); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile( sandbox.path().join("input.scss"), OptionsBuilder::default() .load_path(sandbox.path().join("foo/bar")) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: c;\n}"); } #[test] fn does_not_take_precedence_over_loads_relative_to_the_entrypoint() { let sandbox = Sandbox::default(); sandbox .write(sandbox.path().join("url/input.scss"), "@use \"other\"") .write(sandbox.path().join("url/_other.scss"), "a {b: url}") .write( sandbox.path().join("load-path/_other.scss"), "a {b: load path}", ); let mut sass = Sass::new(exe_path()).unwrap(); let res = sass .compile( sandbox.path().join("url/input.scss"), OptionsBuilder::default() .load_path(sandbox.path().join("load-path")) .build(), ) .unwrap(); assert_eq!(res.css, "a {\n b: url;\n}"); } } } mod error { use super::*; #[test] fn requires_plain_css_for_a_css_file() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.css"), "$a: b; c {d: $a}"); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile(sandbox.path().join("input.css"), Options::default()) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert_eq!( err.span().unwrap().url.as_ref().unwrap(), &sandbox.path().join("input.css").to_url(), ); } mod includes_the_path_s_url { use super::*; #[test] fn in_syntax_errors() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.css"), "a {b:"); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile(sandbox.path().join("input.css"), Options::default()) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert_eq!( err.span().unwrap().url.as_ref().unwrap(), &sandbox.path().join("input.css").to_url(), ); } #[test] fn in_runtime_errors() { let sandbox = Sandbox::default(); sandbox.write(sandbox.path().join("input.css"), "@error \"oh no\""); let mut sass = Sass::new(exe_path()).unwrap(); let err = sass .compile(sandbox.path().join("input.css"), Options::default()) .unwrap_err(); assert_eq!(err.span().unwrap().start.line, 0); assert_eq!( err.span().unwrap().url.as_ref().unwrap(), &sandbox.path().join("input.css").to_url(), ); } } } }