use super::*;
use std::sync::mpsc::{channel, Sender};
use std::time::Instant;
use tempfile::TempDir;
use crate::builder::Builder;
use crate::change::Broadcaster;
use crate::css::{CssOptions, CssTool};
use crate::source::SourcePipeline;
const SETTLE: Duration = Duration::from_secs(5);
fn await_contents(path: &Path, expected: &str) {
let deadline = Instant::now() + SETTLE;
loop {
if let Ok(contents) = std::fs::read_to_string(path) {
if contents == expected {
return;
}
}
assert!(
Instant::now() < deadline,
"{} did not reach {expected:?} within {SETTLE:?} (currently {:?})",
path.display(),
std::fs::read_to_string(path).ok()
);
std::thread::sleep(Duration::from_millis(25));
}
}
fn watching_builder(src: &TempDir, out: &TempDir, options: CssOptions) -> Builder {
Builder::new(out.path())
.unwrap()
.source_folder(src.path())
.unwrap()
.css_tool(CssTool::TestEcho, options)
}
#[test]
fn an_edited_source_is_rebuilt_in_per_file_mode() {
let src = TempDir::new().unwrap();
let out = TempDir::new().unwrap();
std::fs::write(src.path().join("a.css"), "ORIGINAL").unwrap();
let watching = watching_builder(&src, &out, CssOptions::new())
.watch(|e| panic!("unexpected rebuild failure: {e}"))
.unwrap();
await_contents(&out.path().join("a.css"), "ORIGINAL");
std::fs::write(src.path().join("a.css"), "EDITED").unwrap();
await_contents(&out.path().join("a.css"), "EDITED");
watching.stop();
}
#[test]
fn an_edited_source_rebuilds_the_bundle_in_bundle_mode() {
let src = TempDir::new().unwrap();
let out = TempDir::new().unwrap();
std::fs::write(src.path().join("a.css"), "ORIGINAL").unwrap();
let watching = watching_builder(&src, &out, CssOptions::new().bundle(true))
.watch(|e| panic!("unexpected rebuild failure: {e}"))
.unwrap();
await_contents(&out.path().join("styles.css"), "ORIGINAL");
std::fs::write(src.path().join("a.css"), "EDITED").unwrap();
await_contents(&out.path().join("styles.css"), "EDITED");
watching.stop();
}
#[test]
fn a_newly_added_source_is_built() {
let src = TempDir::new().unwrap();
let out = TempDir::new().unwrap();
std::fs::write(src.path().join("a.css"), "A").unwrap();
let watching = watching_builder(&src, &out, CssOptions::new())
.watch(|e| panic!("unexpected rebuild failure: {e}"))
.unwrap();
await_contents(&out.path().join("a.css"), "A");
std::fs::write(src.path().join("b.css"), "B").unwrap();
await_contents(&out.path().join("b.css"), "B");
watching.stop();
}
#[test]
fn the_first_pass_does_not_report_pre_existing_files_as_changes() {
let src = TempDir::new().unwrap();
let out = TempDir::new().unwrap();
std::fs::write(src.path().join("a.css"), "A").unwrap();
let (tx, rx): (Sender<PathBuf>, _) = channel();
let pipeline = SourcePipeline::new(
vec![src.path().to_path_buf()],
Vec::new(),
Vec::new(),
out.path().to_path_buf(),
Some((CssTool::TestEcho, CssOptions::new())),
None,
false,
Broadcaster::new(),
);
let watching = crate::watch::start(pipeline, vec![src.path().to_path_buf()], move |e| {
let _ = tx.send(PathBuf::from(e.to_string()));
});
std::thread::sleep(Duration::from_millis(1200));
watching.stop();
assert!(
rx.try_recv().is_err(),
"an untouched tree must produce no rebuild activity at all"
);
}
#[test]
fn dropping_the_handle_stops_rebuilding() {
let src = TempDir::new().unwrap();
let out = TempDir::new().unwrap();
std::fs::write(src.path().join("a.css"), "ORIGINAL").unwrap();
let watching = watching_builder(&src, &out, CssOptions::new())
.watch(|e| panic!("unexpected rebuild failure: {e}"))
.unwrap();
await_contents(&out.path().join("a.css"), "ORIGINAL");
drop(watching);
std::fs::write(src.path().join("a.css"), "EDITED-AFTER-STOP").unwrap();
std::thread::sleep(Duration::from_millis(1200));
assert_eq!(
std::fs::read_to_string(out.path().join("a.css")).unwrap(),
"ORIGINAL",
"the watcher kept running after its handle was dropped"
);
}
#[test]
fn a_failing_rebuild_reports_through_on_error() {
let src = TempDir::new().unwrap();
let out = TempDir::new().unwrap();
std::fs::write(src.path().join("a.css"), "A").unwrap();
let (tx, rx) = channel();
let pipeline = SourcePipeline::new(
vec![src.path().to_path_buf()],
Vec::new(),
Vec::new(),
out.path().to_path_buf(),
Some((CssTool::TestMissing, CssOptions::new().bundle(true))),
None,
false,
Broadcaster::new(),
);
let watching = crate::watch::start(pipeline, vec![src.path().to_path_buf()], move |e| {
let _ = tx.send(e.to_string());
});
std::fs::write(src.path().join("a.css"), "EDITED").unwrap();
let reported = rx
.recv_timeout(SETTLE)
.expect("a failing rebuild must call on_error");
assert!(
reported.contains("build failed"),
"the reported error should say what happened, got: {reported}"
);
watching.stop();
}