auto_content.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #[cfg(test)]
  2. mod auto_content {
  3. use std::fs;
  4. use std::process::Command;
  5. use tailwindcss_oxide::*;
  6. use tempfile::tempdir;
  7. fn scan(paths_with_content: &[(&str, Option<&str>)]) -> (Vec<String>, Vec<String>) {
  8. // Create a temporary working directory
  9. let dir = tempdir().unwrap().into_path();
  10. // Initialize this directory as a git repository
  11. let _ = Command::new("git").arg("init").current_dir(&dir).output();
  12. // Create the necessary files
  13. for (path, contents) in paths_with_content {
  14. let path = dir.join(path);
  15. let parent = path.parent().unwrap();
  16. if !parent.exists() {
  17. fs::create_dir_all(parent).unwrap();
  18. }
  19. match contents {
  20. Some(contents) => fs::write(path, contents).unwrap(),
  21. None => fs::write(path, "").unwrap(),
  22. }
  23. }
  24. let base = format!("{}", dir.display());
  25. // Resolve all content paths for the (temporary) current working directory
  26. let result = scan_dir(ScanOptions {
  27. base: base.clone(),
  28. globs: true,
  29. });
  30. let mut paths: Vec<_> = result
  31. .files
  32. .into_iter()
  33. .map(|x| x.replace(&format!("{}/", &base), ""))
  34. .collect();
  35. for glob in result.globs {
  36. paths.push(format!("{}/{}", glob.base, glob.glob));
  37. }
  38. paths = paths
  39. .into_iter()
  40. .map(|x| {
  41. let parent_dir = format!("{}/", &base.to_string());
  42. x.replace(&parent_dir, "")
  43. })
  44. .collect();
  45. // Sort the output for easier comparison (depending on internal datastructure the order
  46. // _could_ be random)
  47. paths.sort();
  48. (paths, result.candidates)
  49. }
  50. fn test(paths_with_content: &[(&str, Option<&str>)]) -> Vec<String> {
  51. scan(paths_with_content).0
  52. }
  53. #[test]
  54. fn it_should_work_with_a_set_of_root_files() {
  55. let globs = test(&[
  56. ("index.html", None),
  57. ("a.html", None),
  58. ("b.html", None),
  59. ("c.html", None),
  60. ]);
  61. assert_eq!(globs, vec!["a.html", "b.html", "c.html", "index.html"]);
  62. }
  63. #[test]
  64. fn it_should_work_with_a_set_of_root_files_and_ignore_ignored_files() {
  65. let globs = test(&[
  66. (".gitignore", Some("b.html")),
  67. ("index.html", None),
  68. ("a.html", None),
  69. ("b.html", None),
  70. ("c.html", None),
  71. ]);
  72. assert_eq!(globs, vec!["a.html", "c.html", "index.html"]);
  73. }
  74. #[test]
  75. fn it_should_list_all_files_in_the_public_folder_explicitly() {
  76. let globs = test(&[
  77. ("index.html", None),
  78. ("public/a.html", None),
  79. ("public/b.html", None),
  80. ("public/c.html", None),
  81. ]);
  82. assert_eq!(
  83. globs,
  84. vec![
  85. "index.html",
  86. "public/a.html",
  87. "public/b.html",
  88. "public/c.html",
  89. ]
  90. );
  91. }
  92. #[test]
  93. fn it_should_list_nested_folders_explicitly_in_the_public_folder() {
  94. let globs = test(&[
  95. ("index.html", None),
  96. ("public/a.html", None),
  97. ("public/b.html", None),
  98. ("public/c.html", None),
  99. ("public/nested/a.html", None),
  100. ("public/nested/b.html", None),
  101. ("public/nested/c.html", None),
  102. ("public/nested/again/a.html", None),
  103. ("public/very/deeply/nested/a.html", None),
  104. ]);
  105. assert_eq!(
  106. globs,
  107. vec![
  108. "index.html",
  109. "public/a.html",
  110. "public/b.html",
  111. "public/c.html",
  112. "public/nested/a.html",
  113. "public/nested/again/a.html",
  114. "public/nested/b.html",
  115. "public/nested/c.html",
  116. "public/very/deeply/nested/a.html",
  117. ]
  118. );
  119. }
  120. #[test]
  121. fn it_should_list_all_files_in_the_public_folder_explicitly_except_ignored_files() {
  122. let globs = test(&[
  123. (".gitignore", Some("public/b.html\na.html")),
  124. ("index.html", None),
  125. ("public/a.html", None),
  126. ("public/b.html", None),
  127. ("public/c.html", None),
  128. ]);
  129. assert_eq!(globs, vec!["index.html", "public/c.html",]);
  130. }
  131. #[test]
  132. fn it_should_use_a_glob_for_top_level_folders() {
  133. let globs = test(&[
  134. ("index.html", None),
  135. ("src/a.html", None),
  136. ("src/b.html", None),
  137. ("src/c.html", None),
  138. ]);
  139. assert_eq!(globs, vec![
  140. "index.html",
  141. "src/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  142. "src/a.html",
  143. "src/b.html",
  144. "src/c.html"
  145. ]);
  146. }
  147. #[test]
  148. fn it_should_ignore_binary_files() {
  149. let globs = test(&[
  150. ("index.html", None),
  151. ("a.mp4", None),
  152. ("b.png", None),
  153. ("c.lock", None),
  154. ]);
  155. assert_eq!(globs, vec!["index.html"]);
  156. }
  157. #[test]
  158. fn it_should_ignore_known_extensions() {
  159. let globs = test(&[
  160. ("index.html", None),
  161. ("a.css", None),
  162. ("b.sass", None),
  163. ("c.less", None),
  164. ]);
  165. assert_eq!(globs, vec!["index.html"]);
  166. }
  167. #[test]
  168. fn it_should_ignore_known_files() {
  169. let globs = test(&[
  170. ("index.html", None),
  171. ("package-lock.json", None),
  172. ("yarn.lock", None),
  173. ]);
  174. assert_eq!(globs, vec!["index.html"]);
  175. }
  176. #[test]
  177. fn it_should_ignore_and_expand_nested_ignored_folders() {
  178. let globs = test(&[
  179. // Explicitly listed root files
  180. ("foo.html", None),
  181. ("bar.html", None),
  182. ("baz.html", None),
  183. // Nested folder A, using glob
  184. ("nested-a/foo.html", None),
  185. ("nested-a/bar.html", None),
  186. ("nested-a/baz.html", None),
  187. // Nested folder B, with deeply nested files, using glob
  188. ("nested-b/deeply-nested/foo.html", None),
  189. ("nested-b/deeply-nested/bar.html", None),
  190. ("nested-b/deeply-nested/baz.html", None),
  191. // Nested folder C, with ignored sub-folder
  192. ("nested-c/foo.html", None),
  193. ("nested-c/bar.html", None),
  194. ("nested-c/baz.html", None),
  195. // Ignored folder
  196. ("nested-c/.gitignore", Some("ignored-folder/")),
  197. ("nested-c/ignored-folder/foo.html", None),
  198. ("nested-c/ignored-folder/bar.html", None),
  199. ("nested-c/ignored-folder/baz.html", None),
  200. // Deeply nested, without issues
  201. ("nested-c/sibling-folder/foo.html", None),
  202. ("nested-c/sibling-folder/bar.html", None),
  203. ("nested-c/sibling-folder/baz.html", None),
  204. // Nested folder D, with deeply nested ignored folder
  205. ("nested-d/foo.html", None),
  206. ("nested-d/bar.html", None),
  207. ("nested-d/baz.html", None),
  208. ("nested-d/.gitignore", Some("deep/")),
  209. ("nested-d/very/deeply/nested/deep/foo.html", None),
  210. ("nested-d/very/deeply/nested/deep/bar.html", None),
  211. ("nested-d/very/deeply/nested/deep/baz.html", None),
  212. ("nested-d/very/deeply/nested/foo.html", None),
  213. ("nested-d/very/deeply/nested/bar.html", None),
  214. ("nested-d/very/deeply/nested/baz.html", None),
  215. ("nested-d/very/deeply/nested/directory/foo.html", None),
  216. ("nested-d/very/deeply/nested/directory/bar.html", None),
  217. ("nested-d/very/deeply/nested/directory/baz.html", None),
  218. ("nested-d/very/deeply/nested/directory/again/foo.html", None),
  219. ]);
  220. assert_eq!(
  221. globs,
  222. vec![
  223. "bar.html",
  224. "baz.html",
  225. "foo.html",
  226. "nested-a/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  227. "nested-a/bar.html",
  228. "nested-a/baz.html",
  229. "nested-a/foo.html",
  230. "nested-b/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  231. "nested-b/deeply-nested/bar.html",
  232. "nested-b/deeply-nested/baz.html",
  233. "nested-b/deeply-nested/foo.html",
  234. "nested-c/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  235. "nested-c/bar.html",
  236. "nested-c/baz.html",
  237. "nested-c/foo.html",
  238. "nested-c/sibling-folder/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  239. "nested-c/sibling-folder/bar.html",
  240. "nested-c/sibling-folder/baz.html",
  241. "nested-c/sibling-folder/foo.html",
  242. "nested-d/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  243. "nested-d/bar.html",
  244. "nested-d/baz.html",
  245. "nested-d/foo.html",
  246. "nested-d/very/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  247. "nested-d/very/deeply/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  248. "nested-d/very/deeply/nested/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  249. "nested-d/very/deeply/nested/bar.html",
  250. "nested-d/very/deeply/nested/baz.html",
  251. "nested-d/very/deeply/nested/directory/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
  252. "nested-d/very/deeply/nested/directory/again/foo.html",
  253. "nested-d/very/deeply/nested/directory/bar.html",
  254. "nested-d/very/deeply/nested/directory/baz.html",
  255. "nested-d/very/deeply/nested/directory/foo.html",
  256. "nested-d/very/deeply/nested/foo.html"
  257. ]
  258. );
  259. }
  260. #[test]
  261. fn it_should_scan_for_utilities() {
  262. let mut ignores = String::new();
  263. ignores.push_str("# md:font-bold\n");
  264. ignores.push_str("foo.html\n");
  265. let candidates = scan(&[
  266. // The gitignore file is used to filter out files but not scanned for candidates
  267. (".gitignore", Some(&ignores)),
  268. // A file that should definitely be scanned
  269. ("index.html", Some("font-bold md:flex")),
  270. // A file that should definitely not be scanned
  271. ("foo.jpg", Some("xl:font-bold")),
  272. // A file that is ignored
  273. ("foo.html", Some("lg:font-bold")),
  274. // A svelte file with `class:foo="bar"` syntax
  275. ("index.svelte", Some("<div class:px-4='condition'></div>")),
  276. ])
  277. .1;
  278. assert_eq!(
  279. candidates,
  280. vec!["condition", "div", "font-bold", "md:flex", "px-4"]
  281. );
  282. }
  283. }