kcode-k1-web-code-document 0.2.0

Represent one versionless authored K1 Web code document
Documentation
use kcode_k1_transaction_id::TxId;
use kcode_k1_web_code_document::{CodeDocument, Language};
use kcode_k1_web_package::{AuthorityId, SourceFile, SourcePackage, WebFamily};
use semver::Version;

fn family() -> WebFamily {
    WebFamily::new(AuthorityId::new(TxId::from_bytes([1; 12])), "alpha").unwrap()
}
fn version() -> Version {
    Version::new(1, 2, 3)
}
fn docs(dependencies: &str) -> Vec<u8> {
    format!("<!-- k1-web/v1\n{{\"dependencies\":{dependencies}}}\n-->\nBody\r\n").into_bytes()
}
fn document(language: Language, code: &str) -> CodeDocument {
    CodeDocument::new(family(), docs("[]"), language, code.as_bytes().to_vec()).unwrap()
}
fn files(source: &SourcePackage) -> Vec<&str> {
    source.files().iter().map(|file| file.path()).collect()
}
fn joined(value: &CodeDocument) -> Vec<u8> {
    value
        .chunk_ranges()
        .into_iter()
        .flat_map(|range| value.code()[range].iter().copied())
        .collect()
}

#[test]
fn version_is_injected_only_into_runtime_projection() {
    let value = document(Language::JavaScript, "export async function runTests(){}");
    assert_eq!(value.family(), &family());
    let source = value.to_source_package(version()).unwrap();
    assert_eq!(source.id().version(), &version());
    assert_eq!(CodeDocument::from_source_package(&source).unwrap(), value);
}

#[test]
fn exact_runtime_shapes() {
    let cases = [
        (
            Language::JavaScript,
            "export async function runTests(){}",
            vec!["Code.js", "Documentation.md", "k1-web.json"],
            "Code.js",
        ),
        (
            Language::Html,
            "<!doctype html><script>globalThis.runTests=async()=>{};</script>",
            vec![
                "Code.html",
                "Documentation.md",
                "k1-entry.js",
                "k1-tests.js",
                "k1-web.json",
            ],
            "k1-entry.js",
        ),
        (
            Language::Css,
            "body{color:red}",
            vec![
                "Code.css",
                "Documentation.md",
                "k1-entry.js",
                "k1-tests.js",
                "k1-web.json",
            ],
            "k1-entry.js",
        ),
    ];
    for (language, code, paths, entry) in cases {
        let value = document(language, code);
        let source = value.to_source_package(version()).unwrap();
        assert_eq!(files(&source), paths);
        assert_eq!(source.entry(), entry);
        assert_eq!(
            source
                .files()
                .iter()
                .find(|file| file.path() == language.code_path())
                .unwrap()
                .bytes(),
            code.as_bytes()
        );
    }
}

#[test]
fn header_is_strict_and_source_validation_is_reused() {
    let authority = AuthorityId::new(TxId::from_bytes([2; 12])).to_string();
    let dependency = format!(r#"[{{"authority":"{authority}","name":"beta","selector":"1"}}]"#);
    assert_eq!(
        CodeDocument::new(family(), docs(&dependency), Language::Css, b"a{}".to_vec())
            .unwrap()
            .dependencies()[0]
            .name(),
        "beta"
    );
    for json in [
        " {}",
        "{\"dependencies\":[],\"extra\":0}",
        "{\"dependencies\":[{\"authority\":\"\",\"name\":\"a\",\"selector\":\"*\"}]}",
        "{\"dependencies\":[{\"authority\":\"bad\",\"name\":\"a\",\"selector\":\"*\"}]}",
    ] {
        let text = format!("<!-- k1-web/v1\n{json}\n-->\n");
        assert!(
            CodeDocument::new(family(), text.into_bytes(), Language::Css, b"a{}".to_vec()).is_err(),
            "{json}"
        );
    }
}

#[test]
fn recovery_rejects_extra_or_changed_projection() {
    let value = document(Language::Html, "<p>ok</p>");
    let source = value.to_source_package(version()).unwrap();
    let mut extra = source.files().to_vec();
    extra.push(SourceFile::new("extra.bin", vec![1]));
    let extra = SourcePackage::new(source.id().clone(), extra).unwrap();
    assert!(CodeDocument::from_source_package(&extra).is_err());
    let changed = source
        .files()
        .iter()
        .map(|file| {
            if file.path() == "k1-entry.js" {
                SourceFile::new(file.path(), b"export {};".to_vec())
            } else {
                file.clone()
            }
        })
        .collect();
    let changed = SourcePackage::new(source.id().clone(), changed).unwrap();
    assert!(CodeDocument::from_source_package(&changed).is_err());
}

#[test]
fn chunks_preserve_bytes_and_safety() {
    let code = format!(
        "\u{feff}const a=`{}`;\r\nconst b=/{}/;\r\nconst c='{}';\r\n",
        "x".repeat(850),
        "y".repeat(850),
        "z".repeat(100)
    );
    let value = document(Language::JavaScript, &code);
    assert_eq!(joined(&value), code.as_bytes());
    assert_eq!(value.chunk_ranges().len(), 2);
    let js = "/** docs */\nexport class A{m(){const x=`a${/x[;}]/.test('x') ? `{}` : '//'};return x;} n=1;}\n".repeat(30);
    let value = document(Language::JavaScript, &js);
    assert_eq!(joined(&value), value.code());
    assert!(value.chunk_ranges().len() > 1);
    let css = "@media screen{.a{background:url(\"data:x;y\");/* } */color:red}.b{content:'}';}}\n"
        .repeat(30);
    let value = document(Language::Css, &css);
    assert_eq!(joined(&value), value.code());
    assert!(value.chunk_ranges().len() > 1);
    let html = "<!--x--><section data-x='>&amp;'><p>text</p><script>const x=`<b>${/x/.test('x')}</b>`;</script><style>.a{color:red}</style></section>".repeat(20);
    let value = document(Language::Html, &html);
    assert_eq!(joined(&value), value.code());
    assert!(value.chunk_ranges().len() > 1);
    assert_eq!(
        document(Language::JavaScript, "const x='unterminated")
            .chunk_ranges()
            .len(),
        1
    );
    assert_eq!(document(Language::Css, "").chunk_ranges()[0], 0..0);
}