use assert_cmd::prelude::*;
use serde_json::Value;
use std::fs;
use std::process::Command;
use tempfile::TempDir;
fn tldr_cmd() -> Command {
Command::new(assert_cmd::cargo::cargo_bin!("tldr"))
}
const FIXTURE: &str = "defmodule Foo do\n def bar(x) do\n x + 1\n end\n\n defp baz do\n :ok\n end\nend\n";
fn run_structure(dir: &TempDir) -> Value {
let mut cmd = tldr_cmd();
cmd.args([
"structure",
dir.path().to_str().unwrap(),
"--lang",
"elixir",
"-q",
]);
let out = cmd.assert().success().get_output().stdout.clone();
serde_json::from_slice(&out).expect("structure output is JSON")
}
#[test]
fn test_structure_elixir_method_infos_populated() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("foo.ex");
fs::write(&path, FIXTURE).unwrap();
let v = run_structure(&temp);
let files = v
.get("files")
.and_then(Value::as_array)
.expect("structure.files present");
assert_eq!(files.len(), 1, "exactly one elixir file expected");
let f0 = &files[0];
let methods = f0
.get("methods")
.and_then(Value::as_array)
.expect("methods array present");
let method_names: Vec<&str> = methods.iter().filter_map(Value::as_str).collect();
assert!(
method_names.contains(&"bar"),
"legacy methods[] must contain `bar`, got {:?}",
method_names
);
assert!(
method_names.contains(&"baz"),
"legacy methods[] must contain `baz`, got {:?}",
method_names
);
let mi = f0
.get("method_infos")
.and_then(Value::as_array)
.expect("method_infos array present");
assert_eq!(
mi.len(),
2,
"method_infos must have exactly 2 entries (bar + baz), got {}: {:?}",
mi.len(),
mi
);
let mi_names: Vec<&str> = mi
.iter()
.filter_map(|m| m.get("name").and_then(Value::as_str))
.collect();
assert!(
mi_names.contains(&"bar"),
"method_infos must contain `bar`, got {:?}",
mi_names
);
assert!(
mi_names.contains(&"baz"),
"method_infos must contain `baz`, got {:?}",
mi_names
);
for entry in mi {
let name = entry.get("name").and_then(Value::as_str).unwrap_or("");
let line = entry.get("line").and_then(Value::as_u64).unwrap_or(0);
let sig = entry.get("signature").and_then(Value::as_str).unwrap_or("");
assert!(line > 0, "method_infos[{}].line must be 1-indexed positive, got {}", name, line);
assert!(
!sig.is_empty(),
"method_infos[{}].signature must be non-empty",
name
);
assert!(
sig.starts_with("def ") || sig.starts_with("defp "),
"method_infos[{}].signature must start with `def ` / `defp `, got {:?}",
name,
sig
);
}
}
#[test]
fn test_structure_elixir_method_infos_count_matches_methods() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("foo.ex");
fs::write(&path, FIXTURE).unwrap();
let v = run_structure(&temp);
let f0 = &v.get("files").and_then(Value::as_array).unwrap()[0];
let methods_len = f0.get("methods").and_then(Value::as_array).unwrap().len();
let mi_len = f0
.get("method_infos")
.and_then(Value::as_array)
.unwrap()
.len();
assert_eq!(
methods_len, mi_len,
"elixir-method-infos-v1: methods.len() ({}) must equal method_infos.len() ({}); they describe the same defmodule-scoped declarations",
methods_len, mi_len
);
}