11#[ cfg( test) ]
22mod scanner {
3- use std:: path:: PathBuf ;
3+ use std:: path:: { Path , PathBuf } ;
44 use std:: process:: Command ;
55 use std:: thread:: sleep;
66 use std:: time:: Duration ;
@@ -9,6 +9,16 @@ mod scanner {
99 use tailwindcss_oxide:: * ;
1010 use tempfile:: tempdir;
1111
12+ fn symlink < P : AsRef < Path > , Q : AsRef < Path > > ( original : P , link : Q ) -> std:: io:: Result < ( ) > {
13+ #[ cfg( not( windows) ) ]
14+ let result = std:: os:: unix:: fs:: symlink ( original, link) ;
15+
16+ #[ cfg( windows) ]
17+ let result = std:: os:: windows:: fs:: symlink_dir ( original, link) ;
18+
19+ result
20+ }
21+
1222 fn public_source_entry_from_pattern ( dir : PathBuf , pattern : & str ) -> PublicSourceEntry {
1323 let mut parts = pattern. split_whitespace ( ) ;
1424 let _ = parts. next ( ) . unwrap_or_default ( ) ;
@@ -646,8 +656,8 @@ mod scanner {
646656 assert_eq ! (
647657 candidates,
648658 vec![
649- "content-['project-a/index.html']" . to_owned ( ) ,
650- "content-['project-b/index.html']" . to_owned ( ) ,
659+ "content-['project-a/index.html']" ,
660+ "content-['project-b/index.html']"
651661 ]
652662 ) ;
653663 }
@@ -702,8 +712,8 @@ mod scanner {
702712 assert_eq ! (
703713 candidates,
704714 vec![
705- "content-['project-a/index.html']" . to_owned ( ) ,
706- "content-['project-b/index.html']" . to_owned ( ) ,
715+ "content-['project-a/index.html']" ,
716+ "content-['project-b/index.html']"
707717 ]
708718 ) ;
709719
@@ -726,10 +736,10 @@ mod scanner {
726736 assert_eq ! (
727737 candidates,
728738 vec![
729- "content-['project-a/index.html']" . to_owned ( ) ,
730- "content-['project-a/new.html']" . to_owned ( ) ,
731- "content-['project-b/index.html']" . to_owned ( ) ,
732- "content-['project-b/new.html']" . to_owned ( ) ,
739+ "content-['project-a/index.html']" ,
740+ "content-['project-a/new.html']" ,
741+ "content-['project-b/index.html']" ,
742+ "content-['project-b/new.html']"
733743 ]
734744 ) ;
735745
@@ -758,12 +768,12 @@ mod scanner {
758768 assert_eq ! (
759769 candidates,
760770 vec![
761- "content-['project-a/index.html']" . to_owned ( ) ,
762- "content-['project-a/new.html']" . to_owned ( ) ,
763- "content-['project-a/sub1/sub2/index.html']" . to_owned ( ) ,
764- "content-['project-b/index.html']" . to_owned ( ) ,
765- "content-['project-b/new.html']" . to_owned ( ) ,
766- "content-['project-b/sub1/sub2/index.html']" . to_owned ( ) ,
771+ "content-['project-a/index.html']" ,
772+ "content-['project-a/new.html']" ,
773+ "content-['project-a/sub1/sub2/index.html']" ,
774+ "content-['project-b/index.html']" ,
775+ "content-['project-b/new.html']" ,
776+ "content-['project-b/sub1/sub2/index.html']"
767777 ]
768778 ) ;
769779
@@ -792,14 +802,14 @@ mod scanner {
792802 assert_eq ! (
793803 candidates,
794804 vec![
795- "content-['project-a/index.html']" . to_owned ( ) ,
796- "content-['project-a/new.html']" . to_owned ( ) ,
797- "content-['project-a/sub1/sub2/index.html']" . to_owned ( ) ,
798- "content-['project-a/sub1/sub2/new.html']" . to_owned ( ) ,
799- "content-['project-b/index.html']" . to_owned ( ) ,
800- "content-['project-b/new.html']" . to_owned ( ) ,
801- "content-['project-b/sub1/sub2/index.html']" . to_owned ( ) ,
802- "content-['project-b/sub1/sub2/new.html']" . to_owned ( ) ,
805+ "content-['project-a/index.html']" ,
806+ "content-['project-a/new.html']" ,
807+ "content-['project-a/sub1/sub2/index.html']" ,
808+ "content-['project-a/sub1/sub2/new.html']" ,
809+ "content-['project-b/index.html']" ,
810+ "content-['project-b/new.html']" ,
811+ "content-['project-b/sub1/sub2/index.html']" ,
812+ "content-['project-b/sub1/sub2/new.html']"
803813 ]
804814 ) ;
805815 }
@@ -1611,4 +1621,117 @@ mod scanner {
16111621 assert_eq ! ( globs, vec![ "*" , "src/*/*.{aspx,astro,cjs,cts,eex,erb,gjs,gts,haml,handlebars,hbs,heex,html,jade,js,jsx,liquid,md,mdx,mjs,mts,mustache,njk,nunjucks,php,pug,py,razor,rb,rhtml,rs,slim,svelte,tpl,ts,tsx,twig,vue}" ] ) ;
16121622 assert_eq ! ( normalized_sources, vec![ "**/*" ] ) ;
16131623 }
1624+
1625+ #[ test]
1626+ fn test_glob_with_symlinks ( ) {
1627+ let dir = tempdir ( ) . unwrap ( ) . into_path ( ) ;
1628+ create_files_in (
1629+ & dir,
1630+ & [
1631+ ( ".gitignore" , "node_modules\n dist" ) ,
1632+ (
1633+ "node_modules/.pnpm/@org+my-ui-library/dist/index.ts" ,
1634+ "content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']" ,
1635+ ) ,
1636+ // Make sure the `@org` does exist
1637+ ( "node_modules/@org/.gitkeep" , "" ) ,
1638+ ] ,
1639+ ) ;
1640+ let _ = symlink (
1641+ dir. join ( "node_modules/.pnpm/@org+my-ui-library" ) ,
1642+ dir. join ( "node_modules/@org/my-ui-library" ) ,
1643+ ) ;
1644+
1645+ let mut scanner = Scanner :: new ( vec ! [ public_source_entry_from_pattern(
1646+ dir. clone( ) ,
1647+ "@source 'node_modules'" ,
1648+ ) ] ) ;
1649+ let candidates = scanner. scan ( ) ;
1650+
1651+ assert_eq ! (
1652+ candidates,
1653+ vec![ "content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']" ]
1654+ ) ;
1655+
1656+ let mut scanner = Scanner :: new ( vec ! [ public_source_entry_from_pattern(
1657+ dir. clone( ) ,
1658+ "@source 'node_modules/@org/my-ui-library'" ,
1659+ ) ] ) ;
1660+ let candidates = scanner. scan ( ) ;
1661+
1662+ assert_eq ! (
1663+ candidates,
1664+ vec![ "content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']" ]
1665+ ) ;
1666+
1667+ let mut scanner = Scanner :: new ( vec ! [ public_source_entry_from_pattern(
1668+ dir. clone( ) ,
1669+ "@source 'node_modules/@org'" ,
1670+ ) ] ) ;
1671+ let candidates = scanner. scan ( ) ;
1672+
1673+ assert_eq ! (
1674+ candidates,
1675+ vec![ "content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']" ]
1676+ ) ;
1677+ }
1678+
1679+ #[ test]
1680+ fn test_globs_with_recursive_symlinks ( ) {
1681+ let dir = tempdir ( ) . unwrap ( ) . into_path ( ) ;
1682+ create_files_in (
1683+ & dir,
1684+ & [
1685+ ( "b/index.html" , "content-['b/index.html']" ) ,
1686+ ( "z/index.html" , "content-['z/index.html']" ) ,
1687+ ] ,
1688+ ) ;
1689+
1690+ // Create recursive symlinks
1691+ let _ = symlink ( dir. join ( "a" ) , dir. join ( "b" ) ) ;
1692+ let _ = symlink ( dir. join ( "b/c" ) , dir. join ( "c" ) ) ;
1693+ let _ = symlink ( dir. join ( "b/root" ) , & dir) ;
1694+ let _ = symlink ( dir. join ( "c" ) , dir. join ( "a" ) ) ;
1695+
1696+ let mut scanner = Scanner :: new ( vec ! [ public_source_entry_from_pattern(
1697+ dir. clone( ) ,
1698+ "@source '.'" ,
1699+ ) ] ) ;
1700+ let candidates = scanner. scan ( ) ;
1701+
1702+ assert_eq ! (
1703+ candidates,
1704+ vec![ "content-['b/index.html']" , "content-['z/index.html']" ]
1705+ ) ;
1706+ }
1707+
1708+ #[ test]
1709+ fn test_partial_globs_with_symlinks ( ) {
1710+ let dir = tempdir ( ) . unwrap ( ) . into_path ( ) ;
1711+ create_files_in ( & dir, & [ ( "abcd/xyz.html" , "content-['abcd/xyz.html']" ) ] ) ;
1712+ let _ = symlink ( dir. join ( "abcd" ) , dir. join ( "efgh" ) ) ;
1713+
1714+ // No sources should find nothing
1715+ let mut scanner = Scanner :: new ( vec ! [ ] ) ;
1716+ let candidates = scanner. scan ( ) ;
1717+ assert ! ( candidates. is_empty( ) ) ;
1718+
1719+ // Full symlinked folder name, should find the file
1720+ let mut scanner = Scanner :: new ( vec ! [ public_source_entry_from_pattern(
1721+ dir. clone( ) ,
1722+ "@source 'efgh/*.html'" ,
1723+ ) ] ) ;
1724+ let candidates = scanner. scan ( ) ;
1725+
1726+ assert_eq ! ( candidates, vec![ "content-['abcd/xyz.html']" ] ) ;
1727+
1728+ // Partially referencing the symlinked folder with a glob, should find the file
1729+ let mut scanner = Scanner :: new ( vec ! [ public_source_entry_from_pattern(
1730+ dir. clone( ) ,
1731+ "@source 'ef*/*.html'" ,
1732+ ) ] ) ;
1733+ let candidates = scanner. scan ( ) ;
1734+
1735+ assert_eq ! ( candidates, vec![ "content-['abcd/xyz.html']" ] ) ;
1736+ }
16141737}
0 commit comments