use kcode_k1_transaction_id::TxId;
use kcode_k1_web_code_document::{CodeDocument, Language};
use kcode_k1_web_package::{AuthorityId, SourceFile, SourcePackage, WebFamily, WebId};
use semver::Version;
fn id() -> WebId {
WebId::new(
WebFamily::new(AuthorityId::new(TxId::from_bytes([1; 12])), "alpha").unwrap(),
Version::new(1, 2, 3),
)
.unwrap()
}
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(id(), docs("[]"), language, code.as_bytes().to_vec()).unwrap()
}
fn files(source: &SourcePackage) -> Vec<&str> {
source.files().iter().map(|f| f.path()).collect()
}
fn joined(value: &CodeDocument) -> Vec<u8> {
value
.chunk_ranges()
.into_iter()
.flat_map(|r| value.code()[r].iter().copied())
.collect()
}
#[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().unwrap();
assert_eq!(files(&source), paths);
assert_eq!(source.entry(), entry);
assert_eq!(
source
.files()
.iter()
.find(|f| f.path() == language.code_path())
.unwrap()
.bytes(),
code.as_bytes()
);
assert_eq!(CodeDocument::from_source_package(&source).unwrap(), value);
}
}
#[test]
fn header_is_strict() {
let authority = AuthorityId::new(TxId::from_bytes([2; 12])).to_string();
let dep = format!(r#"[{{"authority":"{authority}","name":"beta","selector":"1"}}]"#);
assert_eq!(
CodeDocument::new(id(), docs(&dep), Language::Css, b"a{}".to_vec())
.unwrap()
.dependencies()[0]
.name(),
"beta"
);
let invalid = [
" {}",
"{\"dependencies\":[],\"extra\":0}",
"{\"dependencies\":[{\"authority\":\"\",\"name\":\"a\",\"selector\":\"*\"}]}",
"{\"dependencies\":[{\"authority\":\"bad\",\"name\":\"a\",\"selector\":\"*\"}]}",
];
for json in invalid {
let text = format!("<!-- k1-web/v1\n{json}\n-->\n");
assert!(
CodeDocument::new(id(), text.into_bytes(), Language::Css, b"a{}".to_vec()).is_err(),
"{json}"
);
}
let duplicate = format!("[{0},{0}]", &dep[1..dep.len() - 1]);
assert!(CodeDocument::new(id(), docs(&duplicate), Language::Css, b"a{{}}".to_vec()).is_err());
}
#[test]
fn recovery_rejects_extra_or_changed_projection() {
let value = document(Language::Html, "<p>ok</p>");
let source = value.to_source_package().unwrap();
let mut extra = source.files().to_vec();
extra.push(SourceFile::new("extra.bin", vec![1]));
let extra = SourcePackage::new(id(), extra).unwrap();
assert!(CodeDocument::from_source_package(&extra).is_err());
let changed = source
.files()
.iter()
.map(|f| {
if f.path() == "k1-entry.js" {
SourceFile::new(f.path(), b"export {};".to_vec())
} else {
f.clone()
}
})
.collect();
let changed = SourcePackage::new(id(), changed).unwrap();
assert!(CodeDocument::from_source_package(&changed).is_err());
}
#[test]
fn chunks_preserve_bom_crlf_and_merge_short_tail() {
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);
let ranges = value.chunk_ranges();
assert_eq!(joined(&value), code.as_bytes());
assert_eq!(ranges.len(), 2);
assert!(ranges[1].clone().count() > 800);
assert_eq!(value.code().last(), Some(&b'\n'));
}
#[test]
fn javascript_safety_and_large_construct() {
let unit = "/** docs */\nexport class A{m(){const x=`a${/x[;}]/.test('x') ? `{}` : '//'};return x;} n=1;}\n";
let value = document(Language::JavaScript, &unit.repeat(30));
assert_eq!(joined(&value), value.code());
assert!(value.chunk_ranges().len() > 1);
let large = document(
Language::JavaScript,
&format!("const value=\"{}\";", "x".repeat(2000)),
);
assert_eq!(large.chunk_ranges().len(), 1);
assert_eq!(
document(Language::JavaScript, "const x='unterminated")
.chunk_ranges()
.len(),
1
);
}
#[test]
fn css_and_html_safety() {
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='>&'><p>text & more</p><script>const x=`<b>${/x/.test('x')}</b>`;</script><style>.a{background:url('x;y')}</style></section>".repeat(20);
let value = document(Language::Html, &html);
assert_eq!(joined(&value), value.code());
assert!(value.chunk_ranges().len() > 1);
for bad in ["a{", "<div>"] {
let language = if bad.starts_with('<') {
Language::Html
} else {
Language::Css
};
assert_eq!(document(language, bad).chunk_ranges().len(), 1);
}
}
#[test]
fn empty_and_small_are_single_ranges() {
let empty = document(Language::Css, "").chunk_ranges();
assert_eq!(empty.len(), 1);
assert_eq!(empty[0], 0..0);
let small = document(Language::Html, "<p>small</p>").chunk_ranges();
assert_eq!(small.len(), 1);
assert_eq!(small[0], 0..12);
}