skif-cli 0.1.0

Command-line interface for skif, a dependency and package-manager linter for JS/TS monorepos.
Documentation
//! End-to-end tests of `skif explain-usage`.

#![allow(clippy::unwrap_used, clippy::expect_used)]

mod support;

use std::fs;
use std::path::Path;

use support::{redact, run, skif};
use tempfile::TempDir;

fn put(root: &Path, relative: &str, content: &str) {
    let path = root.join(relative);
    fs::create_dir_all(path.parent().unwrap()).unwrap();
    fs::write(path, content).unwrap();
}

fn project() -> TempDir {
    let dir = tempfile::tempdir().unwrap();
    fs::create_dir_all(dir.path().join(".git")).unwrap();
    put(
        dir.path(),
        "package.json",
        r#"{"name":"app","version":"1.0.0","private":true,"main":"index.ts","devDependencies":{"vitest":"3.0.0"}}"#,
    );
    put(
        dir.path(),
        "index.ts",
        "import { go } from './barrel';\nimport './nested/leaf';\nexport const main = go();\n",
    );
    put(dir.path(), "barrel.ts", "export { run as go } from './lib';\n");
    put(
        dir.path(),
        "lib.ts",
        "export function run() {}\nexport function idle() {}\n",
    );
    put(dir.path(), "nested/leaf.ts", "export const leaf = 1;\n");
    put(dir.path(), "orphan.ts", "export const lonely = 1;\n");
    put(dir.path(), "a.test.ts", "import './lib';\n");
    dir
}

#[test]
fn a_used_export_is_explained_in_human_output() {
    let dir = project();
    let result = run(skif().current_dir(dir.path()).args(["explain-usage", "lib.ts#run"]));
    assert_eq!(result.code, 0, "{}{}", result.stdout, result.stderr);
    insta::assert_snapshot!("explain_usage_human", redact(&result.stdout, dir.path()));
}

#[test]
fn an_unused_export_is_explained_in_json() {
    let dir = project();
    let result = run(skif()
        .current_dir(dir.path())
        .args(["explain-usage", "lib.ts#idle", "--format", "json"]));
    assert_eq!(result.code, 0, "{}{}", result.stdout, result.stderr);
    let document = result.json();
    assert_eq!(document["report"]["export"]["verdict"], "unused");
    insta::assert_snapshot!("explain_usage_json", redact(&result.stdout, dir.path()));
}

#[test]
fn an_entry_exports_are_public_and_the_verdict_agrees() {
    let dir = project();
    let result = run(skif()
        .current_dir(dir.path())
        .args(["explain-usage", "index.ts#main", "--format", "json"]));
    assert_eq!(result.code, 0, "{}{}", result.stdout, result.stderr);
    let document = result.json();
    assert!(document["report"]["entry"].is_string(), "{document}");
    assert_eq!(document["report"]["export"]["entry-public"], true);
    assert_eq!(document["report"]["export"]["verdict"], "public");
}

#[test]
fn a_file_outside_the_graph_exits_two_with_the_reason() {
    let dir = project();
    let human = run(skif().current_dir(dir.path()).args(["explain-usage", "orphan.ts"]));
    assert_eq!(human.code, 2);
    assert_eq!(human.stdout, "");
    assert!(human.stderr.contains("unused-files"), "{}", human.stderr);

    let json = run(skif()
        .current_dir(dir.path())
        .args(["explain-usage", "orphan.ts", "--format", "json"]));
    assert_eq!(json.code, 2);
    let document = json.json();
    assert_eq!(document["error"]["kind"], "usage");
    assert_eq!(document["report"]["absence"], "unused-file");
}

#[test]
fn a_missing_file_exits_two() {
    let dir = project();
    let result = run(skif().current_dir(dir.path()).args(["explain-usage", "nope.ts"]));
    assert_eq!(result.code, 2);
    assert!(result.stderr.starts_with("error: "), "{}", result.stderr);
}

#[test]
fn the_file_is_resolved_from_the_working_directory() {
    let dir = project();
    let nested = dir.path().join("nested");
    let result = run(skif()
        .current_dir(&nested)
        .args(["explain-usage", "leaf.ts", "..", "--format", "json"]));
    assert_eq!(result.code, 0, "{}{}", result.stdout, result.stderr);
    assert_eq!(result.json()["report"]["file"], "nested/leaf.ts");

    let absolute = run(skif()
        .current_dir(&nested)
        .arg("explain-usage")
        .arg(dir.path().join("lib.ts"))
        .args(["--format", "json"]));
    assert_eq!(absolute.code, 0, "{}{}", absolute.stdout, absolute.stderr);
    assert_eq!(absolute.json()["report"]["file"], "lib.ts");
}

#[test]
fn production_removes_test_entries() {
    let dir = project();
    let default = run(skif().current_dir(dir.path()).args(["explain-usage", "a.test.ts"]));
    assert_eq!(default.code, 0, "{}{}", default.stdout, default.stderr);
    let production = run(skif()
        .current_dir(dir.path())
        .args(["explain-usage", "a.test.ts", "--production"]));
    assert_eq!(production.code, 2, "{}{}", production.stdout, production.stderr);
}