Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
add tests for symlinks
  • Loading branch information
RobinMalfait committed Mar 26, 2025
commit 4b892ac9bc7c53dfd3143c8324784db1f97b8ebc
142 changes: 141 additions & 1 deletion crates/oxide/tests/scanner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[cfg(test)]
mod scanner {
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::thread::sleep;
use std::time::Duration;
Expand All @@ -9,6 +9,16 @@ mod scanner {
use tailwindcss_oxide::*;
use tempfile::tempdir;

fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> std::io::Result<()> {
#[cfg(not(windows))]
let result = std::os::unix::fs::symlink(original, link);

#[cfg(windows)]
let result = std::os::windows::fs::symlink_dir(original, link);

result
}

fn public_source_entry_from_pattern(dir: PathBuf, pattern: &str) -> PublicSourceEntry {
let mut parts = pattern.split_whitespace();
let _ = parts.next().unwrap_or_default();
Expand Down Expand Up @@ -1611,4 +1621,134 @@ mod scanner {
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}"]);
assert_eq!(normalized_sources, vec!["**/*"]);
}

#[test]
fn test_glob_with_symlinks() {
// Create a temporary working directory
let dir = tempdir().unwrap().into_path();

// Create files
create_files_in(
&dir,
&[
(".gitignore", "node_modules\ndist"),
(
"node_modules/.pnpm/@org+my-ui-library/dist/index.ts",
"content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']",
),
// Make sure the `@org` does exist
("node_modules/@org/.gitkeep", ""),
],
);

// Symlink folder
let _ = symlink(
dir.join("node_modules/.pnpm/@org+my-ui-library"),
dir.join("node_modules/@org/my-ui-library"),
);

// Should work with just node_modules (because we skip symlinks, and go straight into the
// `.pnpm` folder)
let mut scanner = Scanner::new(vec![public_source_entry_from_pattern(
dir.clone(),
"@source 'node_modules'",
)]);
let candidates = scanner.scan();

assert_eq!(
candidates,
vec!["content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']"]
);

// Should work with the full folder name
let mut scanner = Scanner::new(vec![public_source_entry_from_pattern(
dir.clone(),
"@source 'node_modules/@org/my-ui-library'",
)]);
let candidates = scanner.scan();

assert_eq!(
candidates,
vec!["content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']"]
);

// This should work, but isn't
let mut scanner = Scanner::new(vec![public_source_entry_from_pattern(
dir.clone(),
"@source 'node_modules/@org'",
)]);
let candidates = scanner.scan();

assert_eq!(
candidates,
vec!["content-['node_modules/.pnpm/@org+my-ui-library/dist/index.ts']"]
);
}

#[test]
fn test_globs_with_recursive_symlinks() {
// Create a temporary working directory
let dir = tempdir().unwrap().into_path();

// Create files
create_files_in(
&dir,
&[
("b/index.html", "content-['b/index.html']"),
("z/index.html", "content-['z/index.html']"),
],
);

// Create recursive symlinks
let _ = symlink(dir.join("a"), dir.join("b"));
let _ = symlink(dir.join("b/c"), dir.join("c"));
let _ = symlink(dir.join("b/root"), &dir);
let _ = symlink(dir.join("c"), dir.join("a"));

let mut scanner = Scanner::new(vec![public_source_entry_from_pattern(
dir.clone(),
"@source '.'",
)]);
let candidates = scanner.scan();

assert_eq!(
candidates,
vec!["content-['b/index.html']", "content-['z/index.html']"]
);
}

#[test]
fn test_partial_globs_with_symlinks() {
// Create a temporary working directory
let dir = tempdir().unwrap().into_path();

// Create files
create_files_in(&dir, &[("abcd/xyz.html", "content-['abcd/xyz.html']")]);

// Symlink folder
let _ = symlink(dir.join("abcd"), dir.join("efgh"));

// No sources should find nothing
let mut scanner = Scanner::new(vec![]);
let candidates = scanner.scan();
assert!(candidates.is_empty());

// Full symlinked folder name, should find the file
let mut scanner = Scanner::new(vec![public_source_entry_from_pattern(
dir.clone(),
"@source 'efgh/*.html'",
)]);
let candidates = scanner.scan();

assert_eq!(candidates, vec!["content-['abcd/xyz.html']"]);

// Partially referencing the symlinked folder with a glob, should find the file
let mut scanner = Scanner::new(vec![public_source_entry_from_pattern(
dir.clone(),
"@source 'ef*/*.html'",
)]);
let candidates = scanner.scan();

assert_eq!(candidates, vec!["content-['abcd/xyz.html']"]);
}
}