mdbook-angular 0.5.0

mdbook renderer to run angular code samples
Documentation
use std::{
	collections::HashMap,
	fs,
	path::Path,
	process::{Command, Stdio},
};

use anyhow::Result;
use assert_cmd::cargo;
use copy_dir::copy_dir;
use select::{document::Document, predicate::*};
use tempfile::TempDir;

pub struct Fixture(TempDir);

#[inline]
fn symlink_dir<F: AsRef<Path>, T: AsRef<Path>>(from: F, to: T) -> Result<()> {
	#[cfg(windows)]
	std::os::windows::fs::symlink_dir(from, to)?;
	#[cfg(not(windows))]
	std::os::unix::fs::symlink(from, to)?;
	Ok(())
}

impl Fixture {
	pub fn run_without_build(env: Option<HashMap<String, String>>) -> Fixture {
		let mut map = env.unwrap_or_default();
		map.insert("MDBOOK_ANGULAR_SKIP_BUILD".to_owned(), "1".to_owned());
		Self::run(Some(map))
	}

	pub fn run(env: Option<HashMap<String, String>>) -> Fixture {
		let mut temp_dir = tempfile::Builder::new()
			.prefix("mdbook-angular-tests")
			.tempdir()
			.expect("failed to create temporary directory");
		println!("Fixture directory {:?}", temp_dir.path());
		temp_dir.disable_cleanup(true);

		let fixture_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixture");

		symlink_dir(
			Path::new(env!("CARGO_MANIFEST_DIR")).join("node_modules"),
			temp_dir.path().join("node_modules"),
		)
		.expect("Failed to link node_modules");

		copy_dir(fixture_dir.join("src"), temp_dir.path().join("src"))
			.expect("Failed to copy source");
		fs::copy(
			fixture_dir.join("book.toml"),
			temp_dir.path().join("book.toml"),
		)
		.expect("Failed to copy book.toml");

		let mut command = Command::new("yarn");
		command
			.arg("exec")
			.arg("mdbook")
			.arg("build")
			.env_remove("RUST_LOG")
			.env(
				"MDBOOK_OUTPUT__ANGULAR__COMMAND",
				cargo::cargo_bin!("mdbook-angular"),
			)
			.current_dir(&temp_dir)
			.stdin(Stdio::null())
			.stdout(Stdio::null())
			.stderr(Stdio::inherit());

		if let Some(env) = env {
			for (name, val) in env {
				command.env(name, val);
			}
		}

		let result = command.status().expect("failed to spawn mdbook");
		if !result.success() {
			panic!("mdbook build failed");
		}

		temp_dir.disable_cleanup(false);
		Fixture(temp_dir)
	}

	fn read_chapter(&self, path: &str) -> Result<Chapter> {
		let html = fs::read_to_string(self.0.path().join("book").join(path))?;

		Ok(Chapter(Document::from(html.as_str())))
	}

	pub fn chapter1(&self) -> Chapter {
		self.read_chapter("chapter-1.html")
			.expect("couldn't read chapter-1.html")
	}

	pub fn chapter2(&self) -> Chapter {
		self.read_chapter("chapter-2.html")
			.expect("couldn't read chapter-2.html")
	}

	pub fn chapter3(&self) -> Chapter {
		self.read_chapter("chapter-3.html")
			.expect("couldn't read chapter-3.html")
	}

	pub fn chapter4(&self) -> Chapter {
		self.read_chapter("chapter-4.html")
			.expect("couldn't read chapter-4.html")
	}

	pub fn chapter5(&self) -> Chapter {
		self.read_chapter("chapter-5.html")
			.expect("couldn't read chapter-5.html")
	}
}

pub struct Chapter(Document);

impl Chapter {
	pub fn assert_code_block_count(&self, count: usize) {
		assert_eq!(count, self.0.find(Name("pre").child(Name("code"))).count());
	}

	pub fn assert_collapsed(&self, value: bool) {
		for code in self.0.find(Name("pre").child(Name("code"))) {
			assert_eq!(
				value,
				code.parent().unwrap().parent().unwrap().is(Name("details")),
				"Parent name {} {} \"details\"? ({0} > {} > {})",
				code.parent().unwrap().parent().unwrap().name().unwrap(),
				if value { "==" } else { "!=" },
				code.parent().unwrap().name().unwrap(),
				code.name().unwrap(),
			);
		}
	}

	pub fn assert_is_default_insertion(&self, value: bool) {
		assert_eq!(1, self.0.find(Name("example-inline")).count());
		assert_eq!(
			!value,
			self.0
				.find(Name("example-inline"))
				.next()
				.unwrap()
				.is(Name("blockquote").descendant(Name("example-inline")))
		);

		assert_eq!(1, self.0.find(Name("example-component")).count());
		assert_eq!(
			!value,
			self.0
				.find(Name("example-component"))
				.next()
				.unwrap()
				.is(Name("blockquote").descendant(Name("example-component")))
		);
	}

	pub fn assert_has_playground(&self, has_playground: bool) {
		if !has_playground {
			assert_eq!(0, self.0.find(Class("mdbook-angular-inputs")).count());
			return;
		}

		assert_eq!(2, self.0.find(Class("mdbook-angular-inputs")).count());
		assert_eq!(
			2,
			self.0
				.find(Class("mdbook-angular-inputs").descendant(Name("mdbook-angular-input")))
				.count()
		);

		for (index, input) in self
			.0
			.find(Class("mdbook-angular-inputs").descendant(Name("mdbook-angular-input")))
			.enumerate()
		{
			assert_eq!(Some(index.to_string().as_str()), input.attr("index"));
			assert_eq!(
				r#"{"type":"string","default":"lorem ipsum"}"#,
				input.inner_html()
			);
		}
	}
}