5e8020aa38
Editor previews (colour swatches, arrows, embeds), the 18 obsidian-basics tasks (find and replace with regex, back/forward, auto-pair, footnotes, folding, search operators, split view, PDF/audio/ video embeds, spelling, drag and drop, pinned tabs, settings) and round 2: ```query blocks, stacked tabs, pop-out windows, tasks ticked inside embeds, text around an embed, embeds that follow edits, block menu shortcut. New crates: notescraft-regex (linear-time engine) and notescraft-syntax. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
147 lines
6.3 KiB
Rust
147 lines
6.3 KiB
Rust
//! Table-driven tests of [`notescraft_blocks::detect_language`]: every rule positive, near-miss
|
|
//! negatives, CRLF input, empty and hostile bodies, and the 4 KiB / 40 line bounds.
|
|
|
|
use notescraft_blocks::detect_language;
|
|
|
|
/// (body, expected label) rows: a rule positive or a near miss that must stay `None`.
|
|
#[test]
|
|
fn the_table() {
|
|
let rows: Vec<(&str, Option<&str>)> = vec![
|
|
// shebang
|
|
("#!/usr/bin/env python\nprint('hi')\n", Some("python")),
|
|
("#!/usr/bin/python\nimport os\n", Some("python")),
|
|
("#!/usr/bin/env bash\necho hi\n", Some("bash")),
|
|
("#!/bin/bash\necho hi\n", Some("bash")),
|
|
("#!/bin/sh\necho hi\n", Some("bash")),
|
|
("#!/usr/bin/env zsh\necho hi\n", Some("bash")),
|
|
("#!/usr/bin/env node\nconsole.log(1)\n", Some("javascript")),
|
|
("#!/usr/bin/env ruby\nputs 'no'\n", None),
|
|
("# not a shebang\n", None),
|
|
// php, html, xml by opener
|
|
("<?php\necho 1;\n", Some("php")),
|
|
("<?PHP\necho 1;\n", None),
|
|
("<!DOCTYPE html>\n<html><body></body></html>\n", Some("html")),
|
|
("<!doctype html>\n<p>hi</p>\n", Some("html")),
|
|
("<HTML>\n</HTML>\n", Some("html")),
|
|
("<div>no label for fragments</div>\n", None),
|
|
("<?xml version=\"1.0\"?>\n<a/>\n", Some("xml")),
|
|
// json
|
|
("{\"a\": 1}\n", Some("json")),
|
|
("[1, 2, 3]\n", Some("json")),
|
|
("`{\"a\":1}`\n", Some("json")),
|
|
("{\"a\": 1}\r\n", Some("json")),
|
|
("{a: 1}\nb: 2\n", None),
|
|
("42\n", None),
|
|
// yaml
|
|
("name: NotesCraft\nversion: 1.0\nitems:\n - one\n - two\n", Some("yaml")),
|
|
("title: a\n# a comment\n- one\nkey: value\n", Some("yaml")),
|
|
("a: {x}\nb: 1\nc: 2\n", None),
|
|
("a: b\nc: d\n", None),
|
|
("x: 1; y\nz: 2\nw: 3\n", None),
|
|
// diff
|
|
("diff --git a/x b/x\nindex 1..2 100644\n", Some("diff")),
|
|
("--- a/x\n+++ b/x\n@@ -1 +1 @@\n-old\n+new\n", Some("diff")),
|
|
("+++ b/x\n--- a/x\n", None),
|
|
("- removed\n+ added\n", None),
|
|
// shell transcripts
|
|
("$ cargo build\n$ cargo test\n", Some("bash")),
|
|
("$ ls\nfile1 file2\n$ pwd\n/home\n", None),
|
|
("$ make\n", None),
|
|
// rust
|
|
("fn main() {\n let x = 1;\n}\n", Some("rust")),
|
|
("impl Foo {\n fn bar(&self) -> u32 {\n 0\n }\n}\n", Some("rust")),
|
|
("use std::collections::HashMap;\n", Some("rust")),
|
|
("pub fn id(x: u32) -> u32 {\n x\n}\n", Some("rust")),
|
|
("let x = 1;\n", None),
|
|
("fn main() {}\n", None),
|
|
("use foo::bar;\n", None),
|
|
// python
|
|
("def add(a, b):\n return a + b\n", Some("python")),
|
|
("import os\nprint(os.getcwd())\n", Some("python")),
|
|
("from os import path\nprint(path)\n", Some("python")),
|
|
("class A:\n def x(self):\n pass\n", Some("python")),
|
|
("def f(): pass\n", None),
|
|
("import os\n", None),
|
|
("from x y\n", None),
|
|
// sql
|
|
("SELECT * FROM users;\n", Some("sql")),
|
|
("select name\nfrom t\nwhere id = 1\n", Some("sql")),
|
|
("INSERT INTO t VALUES (1);\n", Some("sql")),
|
|
("CREATE TABLE t (id INT);\n", Some("sql")),
|
|
("DELETE FROM t WHERE id = 1;\n", Some("sql")),
|
|
("UPDATE t SET a = 1 WHERE b = 2;\n", Some("sql")),
|
|
("UPDATE t SET a = 1\n", None),
|
|
("SELECT 1\n", None),
|
|
("insert into\n", None),
|
|
// toml
|
|
("[package]\nname = \"x\"\n", Some("toml")),
|
|
("a = 1\nb = 2\n", Some("toml")),
|
|
("a = 1\n", None),
|
|
("a = 1\nb = 2;\n", None),
|
|
("[\"a\",]\n", None),
|
|
("[section]\n", Some("toml")),
|
|
// prose, lists, one-liners, hostile
|
|
("The plan is simple: we ship it.\nAnother line here: no code.\nMostly prose without any shape at all.\n", None),
|
|
("Note: this\nWarning: that\n", None),
|
|
("- one\n- two\n- three\n", None),
|
|
("x = 1", None),
|
|
("a: b", None),
|
|
("just a sentence\n", None),
|
|
("1 + 1\n2 + 2\n", None),
|
|
("hello: world\nfoo: bar\n", None),
|
|
("\u{0}\u{0}\u{fffd}\n", None),
|
|
("caf\u{e9}: no\n", None),
|
|
// crlf versions of the keyword rules
|
|
("fn main() {\r\n let x = 1;\r\n}\r\n", Some("rust")),
|
|
("def add(a, b):\r\n return a + b\r\n", Some("python")),
|
|
("$ cargo build\r\n$ cargo test\r\n", Some("bash")),
|
|
("[package]\r\nname = \"x\"\r\n", Some("toml")),
|
|
];
|
|
for (body, expected) in rows {
|
|
assert_eq!(detect_language(body), expected, "body {body:?}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn empty_and_blank_bodies_are_none() {
|
|
assert_eq!(detect_language(""), None);
|
|
assert_eq!(detect_language(" \n\n \n"), None);
|
|
assert_eq!(detect_language("\r\n\r\n"), None);
|
|
}
|
|
|
|
#[test]
|
|
fn only_the_first_4_kib_and_40_non_blank_lines_are_examined() {
|
|
// 1500 `$ x` lines (6000 bytes) push the bad line past the 4 KiB window.
|
|
let beyond_bytes = format!("{}not a prompt\n", "$ x\n".repeat(1500));
|
|
assert_eq!(detect_language(&beyond_bytes), Some("bash"));
|
|
// 40 prompt lines: the 41st is never examined.
|
|
let beyond_lines = format!("{}nope\n", "$ x\n".repeat(40));
|
|
assert_eq!(detect_language(&beyond_lines), Some("bash"));
|
|
// 39 prompt lines plus the bad one: the rule fails.
|
|
let within = format!("{}nope\n", "$ x\n".repeat(39));
|
|
assert_eq!(detect_language(&within), None);
|
|
}
|
|
|
|
#[test]
|
|
fn a_mib_body_stays_bounded_in_time() {
|
|
let began = std::time::Instant::now();
|
|
let big = "x".repeat(1024 * 1024);
|
|
assert_eq!(detect_language(&big), None);
|
|
let mut pythonish = String::from("def add(a, b):\n return a + b\n");
|
|
pythonish.push_str(&"x".repeat(1024 * 1024));
|
|
assert_eq!(detect_language(&pythonish), Some("python"));
|
|
let mut multibyte = String::from("fn main() {\n let x = \"\u{e9}\";\n}\n");
|
|
multibyte.push_str(&"\u{e9}".repeat(700 * 1024));
|
|
assert_eq!(detect_language(&multibyte), Some("rust"));
|
|
assert!(began.elapsed().as_secs_f64() < 1.0, "took {}s", began.elapsed().as_secs_f64());
|
|
}
|
|
|
|
#[test]
|
|
fn hostile_bytes_never_panic_and_stay_none() {
|
|
for hostile in ["\u{0}", "\u{0}\n\u{0}", "\u{7f}\u{8}", "\u{fffd}".repeat(5000).as_str(), "#!/usr/bin/env\u{0}python\n", "<<??php", "[]", "{}", "[\u{0}]"] {
|
|
let _ = detect_language(hostile);
|
|
}
|
|
assert_eq!(detect_language("[\u{0}]"), None);
|
|
assert_eq!(detect_language("#!/usr/bin/env python\u{0}"), None);
|
|
}
|