From cd3ad28886978576b1be90108956c6a2e2675c61 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Thu, 9 Jul 2026 17:07:51 +0900 Subject: [PATCH 1/4] Fix testcase --- libs/braillify/src/rules/english_ueb/rule_11.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libs/braillify/src/rules/english_ueb/rule_11.rs b/libs/braillify/src/rules/english_ueb/rule_11.rs index 74bead5..df1df47 100644 --- a/libs/braillify/src/rules/english_ueb/rule_11.rs +++ b/libs/braillify/src/rules/english_ueb/rule_11.rs @@ -330,7 +330,7 @@ fn encode_expr_with_options( let (next, item) = if chars.get(i) == Some(&'{') { take_braced(chars, i)? } else { - (i + 1, &chars[i..i + 1]) + (i + 1, chars.get(i..i + 1)?) }; let need_grouping = item_needs_braille_grouping(item); if need_grouping { @@ -736,6 +736,13 @@ mod tests { assert_eq!(encode_technical(&"@".chars().collect::>()), None); } + #[rstest::rstest] + #[case::dangling_superscript('^')] + #[case::dangling_subscript('_')] + fn rejects_dangling_script_marker_without_panicking(#[case] marker: char) { + assert_eq!(encode_technical(&[marker]), None); + } + #[test] fn helper_primitives_reject_invalid_inputs() { let mut out = Vec::new(); From 7e0b00d06eb0f3d29ee1cb05ee625c54cf80ec6c Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 10 Jul 2026 09:56:29 +0900 Subject: [PATCH 2/4] Fix lint --- libs/braillify/src/fraction.rs | 9 +++------ libs/braillify/src/rules/english_ueb/engine.rs | 5 ++--- package.json | 5 ++++- py-test/pyproject.toml | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/libs/braillify/src/fraction.rs b/libs/braillify/src/fraction.rs index a6d71cd..78b8514 100644 --- a/libs/braillify/src/fraction.rs +++ b/libs/braillify/src/fraction.rs @@ -94,12 +94,9 @@ fn read_braced_content(iter: &mut std::iter::Peekable) -> Optio iter.next(); } _ => { - if let Some(digit) = normalize_digit(*c) { - content.push(digit); - iter.next(); - } else { - return None; - } + let digit = normalize_digit(*c)?; + content.push(digit); + iter.next(); } } } diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index d8a700c..809e833 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -4244,10 +4244,9 @@ fn push_spatial_char(out: &mut Vec, c: char) -> Option<()> { out.extend([CAPITAL, decode_unicode('⠜')]); } else if c == '<' { out.extend([CAPITAL, decode_unicode('⠣')]); - } else if let Some(cells) = super::rule_16::spatial_symbol(c) { - out.extend(cells); } else { - return None; + let cells = super::rule_16::spatial_symbol(c)?; + out.extend(cells); } Some(()) } diff --git a/package.json b/package.json index 01704bf..02c8b4e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "scripts": { "lint": "oxlint . && cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings", "lint:fix": "oxlint . --fix && cargo fmt --all && cargo clippy --workspace --all-targets --fix --allow-dirty --allow-staged -- -D warnings && cargo clippy --workspace --all-targets -- -D warnings", - "test": "cargo tarpaulin --engine llvm --out xml --out stdout -- --skip test_by_testcase && bun test --coverage && cd py-test && uv run pytest", + "test": "bun run test:rust && bun run test:node && bun run test:python", + "test:rust": "cargo tarpaulin --engine llvm --out xml --out stdout -- --skip test_by_testcase", + "test:node": "bun test --coverage", + "test:python": "cd py-test && uv run --locked pytest", "test:testcase": "cargo test -p braillify test_by_testcase -- --nocapture", "preinstall": "uv sync && (cargo install wasm-pack || node -e \"process.exit(0)\") && (pip install maturin || \"process.exit(0)\")", "build": "cargo build --release -p braillify && bun -F braillify build && cd packages/python && maturin build --release --out dist", diff --git a/py-test/pyproject.toml b/py-test/pyproject.toml index 25816da..9d003b4 100644 --- a/py-test/pyproject.toml +++ b/py-test/pyproject.toml @@ -4,7 +4,7 @@ version = "0.1.0" description = "" authors = [{ name = "owjs3901", email = "owjs3901@gmail.com" }] readme = "README.md" -requires-python = ">=3.13" +requires-python = ">=3.11" dependencies = ["braillify"] [tool.uv.sources] diff --git a/pyproject.toml b/pyproject.toml index af6934b..7328d22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.1.0" description = "" authors = [{ name = "owjs3901", email = "owjs3901@gmail.com" }] readme = "README.md" -requires-python = ">=3.13" +requires-python = ">=3.11" [tool.uv.workspace] members = ["packages/python", "py-test"] diff --git a/uv.lock b/uv.lock index 2ba0b4a..447865b 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.13" +requires-python = ">=3.11" [manifest] members = [ From 0e9f85e8dd222447f820b951832cd265228dfb02 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 10 Jul 2026 11:07:37 +0900 Subject: [PATCH 3/4] Fix lint --- libs/braillify/src/rules/english_ueb/engine.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index 809e833..6012464 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -10629,6 +10629,16 @@ mod tests { assert!(straight_single_quote_exchanged(&inner_double_close, 4)); } + #[test] + fn inner_double_quote_close_requires_an_opening_quote() { + let terminal_punctuation = [EnglishToken::Symbol('!'), EnglishToken::Symbol(',')]; + + assert!(!straight_single_quote_closes_after_inner_double( + &terminal_punctuation, + terminal_punctuation.len(), + )); + } + #[test] fn encode_rare_document_level_symbol_paths() { let engine = EnglishUebEngine::new(); From df6d2abd4f5cb838c1adff7bb4807f33be79b08c Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Fri, 10 Jul 2026 16:08:17 +0900 Subject: [PATCH 4/4] Fix lint --- libs/braillify/src/lib.rs | 26 +++++++++++++++++++ .../english_ueb/pronunciation/aligner.rs | 11 ++++++++ .../src/rules/english_ueb/rule_10_7_pron.rs | 20 ++++++++++++++ .../braillify/src/rules/english_ueb/rule_4.rs | 19 ++++++++++++++ 4 files changed, 76 insertions(+) diff --git a/libs/braillify/src/lib.rs b/libs/braillify/src/lib.rs index ecd28d6..bc2b626 100644 --- a/libs/braillify/src/lib.rs +++ b/libs/braillify/src/lib.rs @@ -1728,6 +1728,20 @@ mod coverage_targeted_tests { assert_eq!(normalize_math_alphanumeric_char(input), expected); } + #[test] + fn normalize_math_alphanumeric_runtime_block_offset() { + let input = std::hint::black_box('\u{1D44F}'); + + assert_eq!(normalize_math_alphanumeric_char(input), 'b'); + } + + #[test] + fn normalize_math_alphanumeric_runtime_digit_offset() { + let input = std::hint::black_box('\u{1D7D9}'); + + assert_eq!(normalize_math_alphanumeric_char(input), '1'); + } + /// `normalize_math_alphanumeric_string` short-circuits when no trigger char /// is present (Cow::Borrowed) and otherwise allocates a new String (Cow::Owned). #[test] @@ -1744,6 +1758,18 @@ mod coverage_targeted_tests { assert_eq!(result.as_ref(), "X = A"); } + #[test] + fn encode_decomposes_runtime_accented_latin_when_triggered() { + let input = std::hint::black_box("café"); + + let encoded = encode_with_options(input, &EncodeOptions::default()).unwrap(); + + assert_eq!( + encoded, + encode_with_options("cafe\u{301}", &EncodeOptions::default()).unwrap() + ); + } + /// Korean 제68항 — compact uppercase + subscript digit is Korean/math-owned by /// default, not UEB §3.24. This protects both plain Unicode and LaTeX token /// forms from the English §9 styled-letter preflight. diff --git a/libs/braillify/src/rules/english_ueb/pronunciation/aligner.rs b/libs/braillify/src/rules/english_ueb/pronunciation/aligner.rs index d5643de..e238b85 100644 --- a/libs/braillify/src/rules/english_ueb/pronunciation/aligner.rs +++ b/libs/braillify/src/rules/english_ueb/pronunciation/aligner.rs @@ -503,6 +503,17 @@ mod tests { assert!(er_unstressed_in(&word, 0, &[phoneme])); } + #[rstest::rstest] + #[case::unstressed_er("ER0", true)] + #[case::secondary_er("ER2", false)] + #[case::bare_r("R", true)] + fn er_unstressed_runtime_stress_paths(#[case] token: &str, #[case] expected: bool) { + let word = [std::hint::black_box('e'), std::hint::black_box('r')]; + let phoneme = parse_phoneme(std::hint::black_box(token)); + + assert_eq!(er_unstressed_in(&word, 0, &[phoneme]), expected); + } + #[test] fn er_unstressed_rejects_secondary_stress_and_split_vowel() { assert!(!er_unstressed_in(&['e', 'r'], 0, &[ph("ER2")])); diff --git a/libs/braillify/src/rules/english_ueb/rule_10_7_pron.rs b/libs/braillify/src/rules/english_ueb/rule_10_7_pron.rs index 2e22a6b..4dee136 100644 --- a/libs/braillify/src/rules/english_ueb/rule_10_7_pron.rs +++ b/libs/braillify/src/rules/english_ueb/rule_10_7_pron.rs @@ -597,4 +597,24 @@ mod tests { let somewise: Vec = "somewise".chars().collect(); assert!(rule.morphology_recovers(&somewise, 0, "some", 4)); } + + #[rstest::rstest] + #[case::initial_some_suffix("somesuch", 0, "some", 4, true)] + #[case::one_compound_suffix("stonework", 2, "one", 5, true)] + #[case::final_time_component("teatime", 3, "time", 7, true)] + fn morphology_recovery_runtime_word_edge_paths( + #[case] word: &str, + #[case] pos: usize, + #[case] key: &str, + #[case] end: usize, + #[case] expected: bool, + ) { + let rule = rule(); + let chars: Vec = std::hint::black_box(word).chars().collect(); + + assert_eq!( + rule.morphology_recovers(&chars, pos, std::hint::black_box(key), end), + expected + ); + } } diff --git a/libs/braillify/src/rules/english_ueb/rule_4.rs b/libs/braillify/src/rules/english_ueb/rule_4.rs index bbe11e3..6124262 100644 --- a/libs/braillify/src/rules/english_ueb/rule_4.rs +++ b/libs/braillify/src/rules/english_ueb/rule_4.rs @@ -242,4 +242,23 @@ mod tests { ]) ); } + + #[test] + fn accent_cells_runtime_eszett_and_upper_ligature_paths() { + assert_eq!( + accent_cells(std::hint::black_box('ß')), + Some(vec![decode_unicode('⠨'), decode_unicode('⠮')]) + ); + assert_eq!( + accent_cells(std::hint::black_box('Æ')), + Some(vec![ + decode_unicode('⠠'), + decode_unicode('⠁'), + decode_unicode('⠠'), + decode_unicode('⠘'), + decode_unicode('⠖'), + decode_unicode('⠑') + ]) + ); + } }