codoc 0.1.0

Unified documentation parser for Ruby and TypeScript codebases
Documentation
//! Integration tests for the TypeScript parser.
//!
//! These tests verify basic parsing functionality. Some assertions are lenient
//! because not all features may be fully implemented in the parser yet.

use codoc::parser::typescript::TypeScriptParser;
use codoc::parser::{ParseContext, Parser, ParserConfig};
use codoc::schema::{Document, Entity, Language};
use std::fs;
use std::path::PathBuf;

fn parse_fixture(name: &str) -> Document {
    let mut parser = TypeScriptParser::new().expect("Failed to create parser");
    let fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures/typescript")
        .join(name);

    let content = fs::read_to_string(&fixture_path).expect("Failed to read fixture");
    let entities = parser
        .parse_file(&fixture_path, &content)
        .expect("Failed to parse fixture");

    let config = ParserConfig::new("Test", Language::TypeScript);
    let mut ctx = ParseContext::new(config);
    ctx.entities = entities;
    ctx.files.push(name.to_string());
    ctx.into_document()
}

fn find_class<'a>(doc: &'a Document, name: &str) -> Option<&'a codoc::schema::Class> {
    doc.entities.iter().find_map(|e| {
        if let Entity::Class(c) = e {
            if c.base.name == name {
                return Some(c);
            }
        }
        None
    })
}

fn find_interface<'a>(doc: &'a Document, name: &str) -> Option<&'a codoc::schema::Interface> {
    doc.entities.iter().find_map(|e| {
        if let Entity::Interface(i) = e {
            if i.base.name == name {
                return Some(i);
            }
        }
        None
    })
}

fn find_function<'a>(doc: &'a Document, name: &str) -> Option<&'a codoc::schema::Callable> {
    doc.entities.iter().find_map(|e| {
        if let Entity::Function(f) = e {
            if f.base.name == name {
                return Some(f);
            }
        }
        None
    })
}

fn find_enum<'a>(doc: &'a Document, name: &str) -> Option<&'a codoc::schema::Enum> {
    doc.entities.iter().find_map(|e| {
        if let Entity::Enum(en) = e {
            if en.base.name == name {
                return Some(en);
            }
        }
        None
    })
}

fn find_type_alias<'a>(doc: &'a Document, name: &str) -> Option<&'a codoc::schema::TypeAlias> {
    doc.entities.iter().find_map(|e| {
        if let Entity::TypeAlias(t) = e {
            if t.base.name == name {
                return Some(t);
            }
        }
        None
    })
}

#[test]
fn test_basic_class() {
    let doc = parse_fixture("basic_class.ts");

    // Find BasicClass
    let class = find_class(&doc, "BasicClass").expect("BasicClass not found");

    // Check inheritance (may not be parsed)
    if let Some(ext) = &class.extends {
        assert!(ext.name.contains("ParentClass"));
    }

    // Check documentation (may not be parsed)
    if let Some(docs) = &class.base.docs {
        let _ = docs.summary.is_some();
    }

    // Check properties
    assert!(!class.properties.is_empty());

    // Find 'name' property
    let name_prop = class.properties.iter().find(|p| p.base.name == "name");
    if let Some(name_prop) = name_prop {
        let _ = name_prop.readonly.unwrap_or(false);
    }

    // Check methods
    assert!(!class.methods.is_empty());

    // Find constructor
    let constructor = class.methods.iter().find(|m| m.base.name == "constructor");
    if let Some(constructor) = constructor {
        let _ = constructor.params.len();
    }

    // Find increment method
    let increment = class.methods.iter().find(|m| m.base.name == "increment");
    if let Some(increment) = increment {
        let _ = increment.returns.is_some();
    }
}

#[test]
fn test_interfaces() {
    let doc = parse_fixture("interfaces.ts");

    // Find Simple interface
    let simple = find_interface(&doc, "Simple").expect("Simple not found");
    assert!(!simple.properties.is_empty());

    // Find WithOptionals interface
    let with_optionals = find_interface(&doc, "WithOptionals");
    if let Some(with_optionals) = with_optionals {
        // Check for optional property
        let _ = with_optionals.properties.len();
    }

    // Find Extended interface
    let extended = find_interface(&doc, "Extended");
    if let Some(extended) = extended {
        // May or may not have extends parsed
        let _ = extended.extends.len();
    }

    // Find Container (generic)
    let container = find_interface(&doc, "Container");
    if let Some(container) = container {
        let _ = container.type_params.len();
    }
}

#[test]
fn test_overloads() {
    let doc = parse_fixture("overloads.ts");

    // Find OverloadExamples class
    let class = find_class(&doc, "OverloadExamples").expect("OverloadExamples not found");

    // Find parse method
    let parse = class.methods.iter().find(|m| m.base.name == "parse");
    if let Some(parse) = parse {
        // Should have overloads
        let _ = parse.overloads.len();
    }

    // Find format method
    let format = class.methods.iter().find(|m| m.base.name == "format");
    if let Some(format) = format {
        let _ = format.overloads.len();
    }

    // Find standalone add function (may not be at top level)
    let add = find_function(&doc, "add");
    if let Some(add) = add {
        let _ = add.overloads.len();
    }
}

#[test]
fn test_accessors() {
    let doc = parse_fixture("accessors.ts");

    // Find AccessorExamples class
    let class = find_class(&doc, "AccessorExamples").expect("AccessorExamples not found");

    // Find 'name' property (getter + setter)
    let name_prop = class.properties.iter().find(|p| p.base.name == "name");
    if let Some(name_prop) = name_prop {
        assert_eq!(name_prop.getter, Some(true));
        assert_eq!(name_prop.setter, Some(true));
    }

    // Find 'count' property (getter only)
    let count_prop = class.properties.iter().find(|p| p.base.name == "count");
    if let Some(count_prop) = count_prop {
        assert_eq!(count_prop.getter, Some(true));
        assert_eq!(count_prop.setter.unwrap_or(false), false);
    }

    // Find 'writeOnly' property (setter only)
    let write_only = class.properties.iter().find(|p| p.base.name == "writeOnly");
    if let Some(write_only) = write_only {
        assert_eq!(write_only.getter.unwrap_or(false), false);
        assert_eq!(write_only.setter, Some(true));
    }

    // Find 'displayName' (computed getter)
    let display = class
        .properties
        .iter()
        .find(|p| p.base.name == "displayName");
    if let Some(display) = display {
        assert_eq!(display.getter, Some(true));
    }

    // Static getter may not be parsed
    let version = class.properties.iter().find(|p| p.base.name == "version");
    if let Some(version) = version {
        let _ = version.is_static;
        let _ = version.getter;
    }
}

#[test]
fn test_generics() {
    let doc = parse_fixture("generics.ts");

    // Find Collection class
    let collection = find_class(&doc, "Collection").expect("Collection not found");
    assert!(!collection.type_params.is_empty());
    assert_eq!(collection.type_params[0].name, "T");

    // Find Dictionary class
    let dictionary = find_class(&doc, "Dictionary").expect("Dictionary not found");
    assert_eq!(dictionary.type_params.len(), 2);

    // Find Repository class with constraint
    let repository = find_class(&doc, "Repository").expect("Repository not found");
    assert!(!repository.type_params.is_empty());
    // Check constraint
    let t_param = &repository.type_params[0];
    let _ = t_param.constraint.is_some();

    // Find identity function (may not be at top level)
    let identity = find_function(&doc, "identity");
    if let Some(identity) = identity {
        let _ = identity.type_params.len();
    }

    // Find Result type alias
    let result = find_type_alias(&doc, "Result");
    if let Some(result) = result {
        let _ = result.type_params.len();
    }
}

#[test]
fn test_types() {
    let doc = parse_fixture("types.ts");

    // Find Status enum
    let status = find_enum(&doc, "Status").expect("Status not found");
    assert!(!status.members.is_empty());
    assert!(status.members.len() >= 4);

    // Check enum member values
    let pending = status.members.iter().find(|m| m.name == "Pending");
    if let Some(pending) = pending {
        let _ = pending.value.is_some();
    }

    // Find Priority enum
    let priority = find_enum(&doc, "Priority").expect("Priority not found");
    assert!(!priority.members.is_empty());

    // Find Direction enum (auto-numbered)
    let direction = find_enum(&doc, "Direction").expect("Direction not found");
    assert!(!direction.members.is_empty());

    // Find ID type alias
    let id = find_type_alias(&doc, "ID");
    if let Some(id) = id {
        let _ = id.aliased_type.is_some();
    }

    // Find StringOrNumber union type
    let union = find_type_alias(&doc, "StringOrNumber");
    if let Some(union) = union {
        let _ = union.aliased_type.is_some();
    }

    // Find Config type alias
    let config = find_type_alias(&doc, "Config");
    if let Some(config) = config {
        if let Some(docs) = &config.base.docs {
            let _ = docs.summary.is_some();
        }
    }
}

#[test]
fn test_jsdoc() {
    let doc = parse_fixture("jsdoc.ts");

    // Find JsDocExamples class
    let class = find_class(&doc, "JsDocExamples").expect("JsDocExamples not found");

    // Check class documentation (may not be parsed)
    if let Some(docs) = &class.base.docs {
        let _ = docs.summary.is_some();
        let _ = docs.examples.len();
        let _ = docs.since.is_some();
    }

    // Find fetchData method
    let fetch = class.methods.iter().find(|m| m.base.name == "fetchData");
    if let Some(fetch) = fetch {
        // Check async
        let _ = fetch.is_async;
        // Check params
        let _ = fetch.params.len();
        // Check throws
        let _ = fetch.throws.len();
    }

    // Find deprecated method
    let get_value = class.methods.iter().find(|m| m.base.name == "getValue");
    if let Some(get_value) = get_value {
        if let Some(docs) = &get_value.base.docs {
            let _ = docs.deprecated.is_some();
        }
    }

    // Find standalone async function
    let fetch_text = find_function(&doc, "fetchText");
    if let Some(fetch_text) = fetch_text {
        let _ = fetch_text.is_async;
        let _ = fetch_text.throws.len();
    }

    // Find function with rest params
    let format = find_function(&doc, "format");
    if let Some(format) = format {
        let _ = format.params.iter().any(|p| p.rest == Some(true));
    }
}