shadertoy-cli 2.2.5

Agent-friendly ShaderToy project, rendering, debugging, and live-preview CLI
use super::*;

#[test]
fn checked_in_schema_matches_manifest_types() {
    let expected = crate::include_file!("schema/shadertoy.schema.json").trim_end();
    let generated = schema_json().expect("schema generation should succeed");
    assert_eq!(expected, generated);
}

#[test]
fn multipass_template_validates() {
    Manifest::multipass("feedback")
        .validate_structure()
        .expect("multipass template should remain valid");
}

#[test]
fn manifest_rejects_paths_outside_project() {
    for source in ["../outside.frag", "/tmp/outside.frag", r"C:\outside.frag"] {
        let mut manifest = Manifest::minimal("demo");
        manifest.passes[0].source = source.into();
        assert!(
            manifest.validate_structure().is_err(),
            "source should be rejected: {source}"
        );
    }
}

#[test]
fn manifest_rejects_reserved_source_names() {
    for name in ["keyboard", "music", "webcam"] {
        let mut manifest = Manifest::minimal("demo");
        manifest.passes[0].name = name.into();
        assert!(
            manifest.validate_structure().is_err(),
            "pass name should be rejected: {name}"
        );
    }
}

#[test]
fn built_in_input_kind_requires_canonical_source_name() {
    let mut manifest = Manifest::minimal("demo");
    manifest.passes[0].inputs.push(Input {
        channel: 0,
        source: "keys".into(),
        kind: Some(InputKind::Keyboard),
        output: 0,
        frame: FrameRef::Current,
        filter: Filter::Linear,
        wrap: Wrap::Clamp,
    });
    assert!(manifest.validate_structure().is_err());
}

#[test]
fn fixed_dimensions_require_complete_buffer_pair() {
    let mut manifest = Manifest::multipass("fixed");
    manifest.passes[0].width = Some(256);
    assert!(manifest.validate_structure().is_err());

    manifest.passes[0].height = Some(256);
    manifest
        .validate_structure()
        .expect("buffer pass should accept fixed dimensions");

    manifest.passes[1].width = Some(256);
    manifest.passes[1].height = Some(256);
    assert!(manifest.validate_structure().is_err());
}

#[test]
fn multiple_outputs_validate_only_for_offscreen_2d_passes() {
    let mut manifest = Manifest::multipass("mrt");
    manifest.passes[0].extra_outputs = vec![RenderFormat::Rg32f, RenderFormat::Rgba16f];
    manifest
        .validate_structure()
        .expect("buffer MRT should validate");

    manifest.passes[1].extra_outputs = vec![RenderFormat::Rg32f];
    assert!(
        manifest.validate_structure().is_err(),
        "final image may not expose extra outputs"
    );
}

#[test]
fn input_output_index_must_exist_and_previous_feedback_uses_primary_output() {
    let mut manifest = Manifest::multipass("mrt-input");
    manifest.passes[0].extra_outputs = vec![RenderFormat::Rg32f];
    manifest.passes[1].inputs[0].output = 1;
    manifest
        .validate_structure()
        .expect("current-frame output 1 should validate");

    manifest.passes[1].inputs[0].output = 2;
    assert!(manifest.validate_structure().is_err());

    manifest.passes[1].inputs[0].output = 1;
    manifest.passes[1].inputs[0].frame = FrameRef::Previous;
    assert!(
        manifest.validate_structure().is_err(),
        "non-primary MRT feedback is intentionally not resumable"
    );
}

#[test]
fn compute_local_size_rejects_multiple_z_lanes() {
    let mut manifest = Manifest::minimal("compute-z");
    manifest.passes.insert(
        0,
        Pass {
            name: "simulation".into(),
            kind: PassKind::Compute,
            source: "shaders/simulation.comp".into(),
            width: Some(1),
            height: Some(1),
            format: RenderFormat::Rgba32f,
            extra_outputs: Vec::new(),
            iterations: 1,
            local_size: Some([1, 1, 2]),
            storage: Vec::new(),
            inputs: Vec::new(),
        },
    );

    let error = manifest
        .validate_structure()
        .expect_err("2D compute entrypoints must reject multiple local Z lanes");
    assert!(error.to_string().contains("z"), "{error}");
    assert!(error.to_string().contains("1"), "{error}");
}

#[test]
fn visual_reference_tests_reject_frame_resolution_matrices() {
    let source = r#"format = 1
[project]
name = "matrix-reference"

[[pass]]
name = "image"
kind = "image"
source = "shaders/image.frag"

[[test]]
name = "ambiguous-reference"
frames = [0, 1]
resolutions = [[16, 16], [32, 32]]
reference = "tests/reference.png"
"#;
    let manifest: Manifest = toml::from_str(source).expect("manifest should parse");
    let error = manifest
        .validate_structure()
        .expect_err("one reference path cannot describe a frame/resolution matrix");
    assert!(
        error.to_string().contains("single reference image"),
        "{error}"
    );
}