findnerd 0.1.0

Semantic search for Nerd Font glyphs
use std::process::{Command, Output};

use serde_json::Value;

fn findnerd(arguments: &[&str]) -> Output {
	Command::new(env!("CARGO_BIN_EXE_findnerd"))
		.args(arguments)
		.output()
		.expect("findnerd should execute")
}

#[test]
fn positional_query_prints_ranked_glyphs() {
	let output = findnerd(&["blank", "--limit", "3", "--no-color"]);

	assert!(output.status.success());
	let stdout = String::from_utf8(output.stdout).expect("output should be UTF-8");
	assert!(stdout.contains("cod-blank"));
	assert!(stdout.contains(''));
	assert!(!stdout.contains("\x1b["), "redirected output must not contain ANSI escapes");
}

#[test]
fn json_mode_combines_query_category_filter_and_strategy() {
	let output = findnerd(&[
		"--query",
		"database",
		"--json",
		"--semantic",
		"--category",
		"cod",
		"--filter",
		"!name:remote",
		"--limit",
		"4",
	]);

	assert!(output.status.success());
	let document: Value = serde_json::from_slice(&output.stdout).expect("output should be JSON");
	assert_eq!(document["query"], "database");
	assert_eq!(document["mode"], "semantic");
	assert_eq!(document["categories"][0], "cod");
	assert_eq!(document["filters"][0], "!name:remote");
	let results = document["results"]
		.as_array()
		.expect("results should be an array");
	assert_eq!(results.len(), 4);
	assert!(results.iter().all(|result| {
		result["category"] == "cod"
			&& result["name"]
				.as_str()
				.is_some_and(|name| !name.contains("remote"))
			&& result["glyph"]
				.as_str()
				.is_some_and(|glyph| !glyph.is_empty())
	}));
}

#[test]
fn each_explicit_ranker_is_available_to_machine_callers() {
	for (flag, expected) in [("--bm25", "bm25"), ("--semantic", "semantic"), ("--match", "match")] {
		let output = findnerd(&["--query", "git branch", flag, "--json", "--limit", "1"]);
		assert!(output.status.success(), "{flag} should succeed");
		let document: Value =
			serde_json::from_slice(&output.stdout).expect("ranker output should be JSON");
		assert_eq!(document["mode"], expected);
		assert_eq!(document["count"], 1);
		assert!(document["results"][0]["name"].is_string());
	}
}

#[test]
fn exact_codepoint_filter_supports_empty_queries() {
	let output = findnerd(&["--query", "", "--filter", "code:eb99", "--json"]);

	assert!(output.status.success());
	let document: Value = serde_json::from_slice(&output.stdout).expect("output should be JSON");
	assert_eq!(document["total"], 1);
	assert_eq!(document["results"][0]["name"], "cod-account");
	assert_eq!(document["results"][0]["codepoint"], "U+EB99");
}

#[test]
fn positive_category_switches_opt_into_only_those_collections() {
	let output = findnerd(&["--query", "view", "--cod", "--json", "--limit", "10"]);

	assert!(output.status.success());
	let document: Value = serde_json::from_slice(&output.stdout).expect("output should be JSON");
	assert_eq!(document["categories"], serde_json::json!(["cod"]));
	assert!(document["results"].as_array().is_some_and(|results| {
		!results.is_empty() && results.iter().all(|result| result["category"] == "cod")
	}));
}

#[test]
fn negative_category_switches_exclude_from_the_full_catalog() {
	let output = findnerd(&["--query", "eye", "--no-fa", "--json", "--limit", "10"]);

	assert!(output.status.success());
	let document: Value = serde_json::from_slice(&output.stdout).expect("output should be JSON");
	assert_eq!(document["categories"], serde_json::json!([]));
	assert_eq!(document["excluded_categories"], serde_json::json!(["fa"]));
	assert!(document["results"].as_array().is_some_and(|results| {
		!results.is_empty() && results.iter().all(|result| result["category"] != "fa")
	}));
}

#[test]
fn no_results_are_valid_json_with_a_distinct_exit_status() {
	let output =
		findnerd(&["--query", "anything", "--filter", "name:this-icon-does-not-exist", "--json"]);

	assert_eq!(output.status.code(), Some(1));
	let document: Value = serde_json::from_slice(&output.stdout).expect("output should be JSON");
	assert_eq!(document["count"], 0);
	assert_eq!(document["results"], Value::Array(Vec::new()));
}