From 40b4427595125012876af7745d17b73096bf7eb0 Mon Sep 17 00:00:00 2001 From: supersurviveur Date: Fri, 31 May 2024 12:28:06 +0200 Subject: [PATCH] Add tests --- README.md | 3 +- typst-math-rust/Cargo.toml | 1 + typst-math-rust/coverage.sh | 12 +- typst-math-rust/src/interface.rs | 20 ++- typst-math-rust/src/lib.rs | 54 +++++-- typst-math-rust/src/parser/parser.rs | 35 +++-- typst-math-rust/src/parser/utils.rs | 113 +++++++++++++- typst-math-rust/src/utils/symbols.rs | 30 +++- typst-math-rust/tests/test.rs | 214 ++++++++++++++++++++++++++- typst-math-rust/tests/web.rs | 14 ++ 10 files changed, 454 insertions(+), 42 deletions(-) create mode 100644 typst-math-rust/tests/web.rs diff --git a/README.md b/README.md index b888f2c..4f84533 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,5 @@ If you encounter any issues, please report them on the [GitHub repository](https Feel free to contribute to the project ! See the [CONTRIBUTING.md](./CONTRIBUTING.md) file for instructions on how to build the project. # Acknowledgements -- Thanks to [Enter-tainer](https://github.com/Enter-tainer) for his advices \ No newline at end of file +- Thanks to [Enter-tainer](https://github.com/Enter-tainer) for his advices +- Thanks to [Le-Foucheur](https://github.com/Le-Foucheur) for testing \ No newline at end of file diff --git a/typst-math-rust/Cargo.toml b/typst-math-rust/Cargo.toml index 95e6b1e..9b385e4 100644 --- a/typst-math-rust/Cargo.toml +++ b/typst-math-rust/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib", "rlib"] [features] default = ["console_error_panic_hook"] +coverage = [] [dependencies] wasm-bindgen = "0.2.84" diff --git a/typst-math-rust/coverage.sh b/typst-math-rust/coverage.sh index 609ed12..b14fbc3 100755 --- a/typst-math-rust/coverage.sh +++ b/typst-math-rust/coverage.sh @@ -1,7 +1,9 @@ #!/bin/sh +TEST="" + # Generate profile files -RUSTFLAGS="-C instrument-coverage -Z coverage-options=branch" cargo +nightly test --tests +RUSTFLAGS="-C instrument-coverage -Z coverage-options=branch" cargo +nightly test --tests --features coverage $TEST cargo profdata -- merge -sparse default_*.profraw -o typst-math.profdata # Get binary names @@ -9,7 +11,7 @@ FILES=$( \ for file in \ $( \ RUSTFLAGS="-C instrument-coverage -Z coverage-options=branch" \ - cargo +nightly test --tests --no-run --message-format=json \ + cargo +nightly test --tests --features coverage $TEST --no-run --message-format=json \ | jq -r "select(.profile.test == true) | .filenames[]" \ | grep -v dSYM - \ ); \ @@ -21,16 +23,16 @@ FILES=$( \ # Generate report cargo cov -- report \ $FILES \ - --use-color --ignore-filename-regex='/.cargo/registry' \ + --use-color --ignore-filename-regex='/.cargo/registry|main.rs|rustc/' \ --instr-profile=typst-math.profdata # Show where code isn't covered # cargo cov -- show \ # $FILES \ -# --use-color --ignore-filename-regex='/.cargo/registry' \ +# --use-color --ignore-filename-regex='/.cargo/registry|main.rs|rustc/' \ # --instr-profile=typst-math.profdata --summary-only \ # --show-instantiations --show-line-counts-or-regions \ -# --Xdemangler=rustfilt | less -R +# --Xdemangler=rustfilt --show-branches=percent | less -R # Clean files rm -f default_*.profraw diff --git a/typst-math-rust/src/interface.rs b/typst-math-rust/src/interface.rs index 80df76f..ca58c63 100644 --- a/typst-math-rust/src/interface.rs +++ b/typst-math-rust/src/interface.rs @@ -9,7 +9,7 @@ use crate::utils::symbols::Color; /// - rust side: in the decoraions hasmap /// - js side: in the decorations array, to avoid generating the same decoration multiple times (Expensive) #[derive(Debug, Clone)] -#[wasm_bindgen(getter_with_clone)] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen(getter_with_clone))] pub struct Decoration { pub uuid: String, pub symbol: String, @@ -20,7 +20,7 @@ pub struct Decoration { /// Represents a symbol position in the document #[derive(Debug, Clone)] -#[wasm_bindgen] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen)] pub struct Position { pub start: usize, pub end: usize, @@ -35,9 +35,21 @@ pub struct Options { pub custom_symbols: HashMap, } +impl Default for Options { + fn default() -> Self { + Options { + rendering_mode: 3, + render_outside_math: true, + render_spaces: false, + blacklisted_symbols: vec![], + custom_symbols: HashMap::new(), + } + } +} + /// Represents a user defined symbol that can be used trough WASM #[derive(Debug)] -#[wasm_bindgen(getter_with_clone)] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen(getter_with_clone))] pub struct CustomSymbol { pub name: String, pub symbol: String, @@ -45,7 +57,7 @@ pub struct CustomSymbol { } /// Represents the result of the parsing function -#[wasm_bindgen(getter_with_clone)] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen(getter_with_clone))] pub struct Parsed { pub decorations: Vec, pub edit_start_line: usize, diff --git a/typst-math-rust/src/lib.rs b/typst-math-rust/src/lib.rs index 67c78ef..a6ce23f 100644 --- a/typst-math-rust/src/lib.rs +++ b/typst-math-rust/src/lib.rs @@ -13,7 +13,7 @@ use utils::hook::set_panic_hook; use wasm_bindgen::prelude::*; /// Initialize the WASM library -#[wasm_bindgen] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen)] pub fn init_lib() { set_panic_hook(); } @@ -34,7 +34,7 @@ pub fn find_node<'a>( } /// Parse a document and return the decorations to apply -#[wasm_bindgen] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen)] pub fn parse_document( content: &str, edited_line_start: i32, @@ -86,18 +86,13 @@ pub fn parse_document( // Find all nodes in this range find_node(range.clone(), root.clone(), &mut nodes); - if nodes.is_empty() { - // If no nodes were found, parse the entire document - nodes.push(root); - } else { - // Get the range of part which will be reparsed - let first = source.find(nodes.first().unwrap().span()).unwrap().range(); - let last = source.find(nodes.last().unwrap().span()).unwrap().range(); - edit_start_line = source.byte_to_line(first.start).unwrap(); - edit_end_line = source.byte_to_line(last.end).unwrap(); - edit_start_column = source.byte_to_column(first.start).unwrap(); - edit_end_column = source.byte_to_column(last.end).unwrap(); - } + // Get the range of part which will be reparsed + let first = source.find(nodes.first().unwrap().span()).unwrap().range(); + let last = source.find(nodes.last().unwrap().span()).unwrap().range(); + edit_start_line = source.byte_to_line(first.start).unwrap(); + edit_end_line = source.byte_to_line(last.end).unwrap(); + edit_start_column = source.byte_to_column(first.start).unwrap(); + edit_end_column = source.byte_to_column(last.end).unwrap(); } else { // Parse the entire document let root = source.find(source.root().span()).unwrap(); @@ -149,7 +144,7 @@ pub fn parse_document( } /// Generate a custom symbol struct easily from JS -#[wasm_bindgen] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen)] pub fn generate_custom_symbol(name: String, symbol: String, category: String) -> CustomSymbol { return CustomSymbol { name, @@ -157,3 +152,32 @@ pub fn generate_custom_symbol(name: String, symbol: String, category: String) -> category, }; } + +#[cfg(test)] +mod tests { + use crate::{generate_custom_symbol, init_lib, parse_document}; + + #[test] + fn test_initialization() { + init_lib(); + } + + #[test] + fn test_custom_symbols() { + let parsed = parse_document( + "$alpha symbol$", + -1, + -1, + 3, + true, + true, + vec![], + vec![generate_custom_symbol( + "symbol".to_string(), + "symbol".to_string(), + "operator".to_string(), + )], + ); + assert_eq!(parsed.decorations.len(), 2); + } +} diff --git a/typst-math-rust/src/parser/parser.rs b/typst-math-rust/src/parser/parser.rs index d80381c..41d6394 100644 --- a/typst-math-rust/src/parser/parser.rs +++ b/typst-math-rust/src/parser/parser.rs @@ -14,6 +14,15 @@ pub struct State { pub is_attachment: bool, } +impl Default for State { + fn default() -> Self { + State { + is_base: false, + is_attachment: false, + } + } +} + /// Use a recursive DFS to traverse the entire AST and apply style \ /// Most complex part of the code, match the current expression and then, /// compute the appropriate style and/or if we need to continue over children @@ -164,26 +173,32 @@ fn math_attach_block(parser: &mut InnerParser) { if parser.options.rendering_mode > 1 { parser.offset = (1, 0); } - let top_decor = if parser.options.rendering_mode > 1 { - "font-size: 0.8em; transform: translateY(-30%); display: inline-block;" + let (top_decor, top_uuid) = if parser.options.rendering_mode > 1 { + ( + "font-size: 0.8em; transform: translateY(-30%); display: inline-block;", + "top-", + ) } else { - "" + ("", "") }; - let bottom_decor = if parser.options.rendering_mode > 1 { - "font-size: 0.8em; transform: translateY(20%); display: inline-block;" + let (bottom_decor, bottom_uuid) = if parser.options.rendering_mode > 1 { + ( + "font-size: 0.8em; transform: translateY(20%); display: inline-block;", + "bottom-", + ) } else { - "" + ("", "") }; // Set state for top and bottom attachment parser.state.is_base = false; parser.state.is_attachment = parser.options.rendering_mode > 1; if let Some(top) = attachment.top() { let top = parser.expr.find(top.span()).unwrap(); - ast_dfs(parser, &top, "top-", top_decor, parser.offset) + ast_dfs(parser, &top, top_uuid, top_decor, parser.offset) } if let Some(bottom) = attachment.bottom() { let bottom = parser.expr.find(bottom.span()).unwrap(); - ast_dfs(parser, &bottom, "bottom-", bottom_decor, parser.offset) + ast_dfs(parser, &bottom, bottom_uuid, bottom_decor, parser.offset) } // Restore the state parser.state.is_base = state.is_base; @@ -198,7 +213,7 @@ fn math_block(parser: &mut InnerParser) { && children[1].kind() == SyntaxKind::Math && children[2].kind() == SyntaxKind::RightParen { - // This serie of check aims to verify that the block inside paren is 'simple', wich means that we can propagate style (So top and bottom attachment) + // This serie of checks aims to verify that the block inside paren is 'simple', wich means that we can propagate style (So top and bottom attachment) let mut propagate_style = false; let sub_children: Vec = children[1].children().collect(); @@ -259,6 +274,8 @@ fn math_block(parser: &mut InnerParser) { return; } } + // Style isn't propagated, reset state + parser.state.is_attachment = false; for child in parser.expr.children() { ast_dfs(parser, &child, "", "", (0, 0)); // Propagate the function } diff --git a/typst-math-rust/src/parser/utils.rs b/typst-math-rust/src/parser/utils.rs index 8639b90..a5bb74f 100644 --- a/typst-math-rust/src/parser/utils.rs +++ b/typst-math-rust/src/parser/utils.rs @@ -9,8 +9,8 @@ use crate::{ }, }; use std::{collections::HashMap, ops::Range}; -use typst_syntax::{ast::AstNode, LinkedNode, Source}; use typst_syntax::SyntaxNode; +use typst_syntax::{ast::AstNode, LinkedNode, Source}; /// Get symbol from it's name pub fn get_symbol(content: String, options: &Options) -> Option<(Category, String)> { @@ -49,7 +49,6 @@ fn len_utf16(string: &str) -> usize { string.chars().map(char::len_utf16).sum() } - /// Return the index range of the UTF-16 code unit at the byte index range. \ /// Faster than calling `byte_to_utf16` over start and end. fn byte_range_to_utf16(source: &Source, range: &Range) -> Option> { @@ -207,3 +206,113 @@ impl<'a> InnerParser<'a> { ) } } + +#[cfg(test)] +mod tests { + use typst_syntax::SyntaxNode; + + use crate::{interface::Options, parser::parser::State}; + + #[test] + fn test_inner_parser() { + let source = typst_syntax::Source::detached("α"); + let mut result = std::collections::HashMap::new(); + let mut state = State::default(); + let options = Options::default(); + let node = SyntaxNode::leaf(typst_syntax::SyntaxKind::Ident, "alpha"); + let expr = typst_syntax::LinkedNode::new(&node); + let mut parser = super::InnerParser::new(&source, &expr, &mut result, &mut state, &options); + parser.insert_result_symbol( + 0..2, + "alpha".to_string(), + "alpha".to_string(), + "", + (0, 0), + ("", ""), + ); + + let mut parser = super::InnerParser::from(&mut parser, &expr, "alpha", "", (0, 0)); + parser.insert_result_symbol( + 0..2, + "alpha".to_string(), + "alpha".to_string(), + "", + (0, 0), + ("", ""), + ); + assert_eq!(parser.result.len(), 1); + assert_eq!(parser.result.get("alpha").unwrap().symbol, "α"); + } + + #[test] + fn test_inner_parser_spaces() { + let source = typst_syntax::Source::detached("zwnj"); + let mut result = std::collections::HashMap::new(); + let mut state = State::default(); + let mut options = Options::default(); + let node = SyntaxNode::leaf(typst_syntax::SyntaxKind::MathIdent, "zwnj"); + let expr = typst_syntax::LinkedNode::new(&node); + let mut parser = super::InnerParser::new(&source, &expr, &mut result, &mut state, &options); + parser.insert_result_symbol( + 0..4, + "zwnj".to_string(), + "zwnj".to_string(), + "", + (0, 0), + ("", ""), + ); + assert_eq!(parser.result.len(), 0); + + options.render_spaces = true; + let mut parser = super::InnerParser::new(&source, &expr, &mut result, &mut state, &options); + parser.insert_result_symbol( + 0..4, + "zwnj".to_string(), + "zwnj".to_string(), + "", + (0, 0), + ("", ""), + ); + assert_eq!(parser.result.len(), 1); + } + + #[test] + fn test_inner_parser_not_found() { + let source = typst_syntax::Source::detached(""); + let mut result = std::collections::HashMap::new(); + let mut state = State::default(); + let options = Options::default(); + let node = SyntaxNode::leaf(typst_syntax::SyntaxKind::Ident, "alpha"); + let expr = typst_syntax::LinkedNode::new(&node); + let mut parser = super::InnerParser::new(&source, &expr, &mut result, &mut state, &options); + parser.insert_result_symbol( + 0..5, + "doesn't exist".to_string(), + "doesn't exist".to_string(), + "", + (0, 0), + ("", ""), + ); + } + + #[test] + fn test_inner_parser_blacklist() { + let source = typst_syntax::Source::detached("alpha"); + let mut result = std::collections::HashMap::new(); + let mut state = State::default(); + let mut options = Options::default(); + options.blacklisted_symbols.push("alpha".to_string()); + let node = SyntaxNode::leaf(typst_syntax::SyntaxKind::Ident, "alpha"); + let expr = typst_syntax::LinkedNode::new(&node); + let mut parser = super::InnerParser::new(&source, &expr, &mut result, &mut state, &options); + parser.insert_result_symbol( + 0..5, + "alpha".to_string(), + "alpha".to_string(), + "", + (0, 0), + ("", ""), + ); + assert_eq!(parser.result.len(), 0); + } +} diff --git a/typst-math-rust/src/utils/symbols.rs b/typst-math-rust/src/utils/symbols.rs index fa5d1e7..a5b5675 100644 --- a/typst-math-rust/src/utils/symbols.rs +++ b/typst-math-rust/src/utils/symbols.rs @@ -2,22 +2,24 @@ //! Stealed from https://github.com/typst/typst/blob/main/crates/typst/src/symbols/symbol.rs //! and edited to be used in the frontend. + use phf::phf_map; use std::fmt::Debug; use typst_math_macros::symbols; -use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::prelude::*; /// Represents a symbol with a given category. #[derive(Debug, Clone)] -#[wasm_bindgen(getter_with_clone)] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen(getter_with_clone))] pub struct Symbol { pub symbol: char, pub category: Category, } /// Represents a symbol category, used for styling. + #[derive(Debug, Clone, Copy, PartialEq)] -#[wasm_bindgen] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen)] pub enum Category { Keyword, Comparison, @@ -45,8 +47,8 @@ pub fn get_category_by_name(name: &String) -> Category { } /// Represents a symbol color, passed to the frontend for styling. -#[derive(Debug, Clone, Copy)] -#[wasm_bindgen] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(not(feature = "coverage"), wasm_bindgen)] pub enum Color { Keyword, Comparison, @@ -1127,3 +1129,21 @@ pub const BLACKBOLD_LETTERS: phf::Map = phf_map! { '8' => '𝟠', '9' => '𝟡', }; + + +#[cfg(test)] +mod tests { + use crate::utils::symbols::{get_category_by_name, Category}; + + #[test] + fn test_get_category_by_name() { + assert_eq!(get_category_by_name(&"leTter".to_string()), Category::Letter); + assert_eq!(get_category_by_name(&"coMparison".to_string()), Category::Comparison); + assert_eq!(get_category_by_name(&"keyword".to_string()), Category::Keyword); + assert_eq!(get_category_by_name(&"Set".to_string()), Category::Set); + assert_eq!(get_category_by_name(&"bigletter".to_string()), Category::BigLetter); + assert_eq!(get_category_by_name(&"Number".to_string()), Category::Number); + assert_eq!(get_category_by_name(&"space".to_string()), Category::Space); + assert_eq!(get_category_by_name(&"doesn't exists".to_string()), Category::Default); + } +} \ No newline at end of file diff --git a/typst-math-rust/tests/test.rs b/typst-math-rust/tests/test.rs index 5a46fa3..51c3d84 100644 --- a/typst-math-rust/tests/test.rs +++ b/typst-math-rust/tests/test.rs @@ -2,6 +2,7 @@ mod tests { use typst_math_rust::parse_document; + #[test] fn basic_symbol() { let parsed = parse_document("$alpha$", -1, -1, 3, true, true, vec![], vec![]); @@ -12,7 +13,16 @@ mod tests { #[test] fn symbol_repetition() { - let parsed = parse_document("$alpha alpha alpha alpha alpha$", -1, -1, 3, true, true, vec![], vec![]); + let parsed = parse_document( + "$alpha alpha alpha alpha alpha$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); assert_eq!(parsed.decorations.len(), 1); assert_eq!(parsed.decorations[0].symbol, "α"); assert_eq!(parsed.decorations[0].uuid, "alpha"); @@ -29,5 +39,207 @@ mod tests { assert_eq!(parsed.decorations[0].uuid, "top-alpha"); let parsed = parse_document("$x_alpha$", -1, -1, 3, true, true, vec![], vec![]); assert_eq!(parsed.decorations[0].uuid, "bottom-alpha"); + + let parsed = parse_document("$x_alpha_alpha^alpha^alpha$", -1, -1, 3, true, true, vec![], vec![]); + assert_eq!(parsed.decorations.len(), 3); + let parsed = parse_document("$x_alpha_alpha^alpha^alpha$", -1, -1, 0, true, true, vec![], vec![]); + assert_eq!(parsed.decorations.len(), 1); + } + + #[test] + fn test_edited_line() { + let parsed = parse_document( + "$zeta^2$\n#sym.arrow\n$(alpha)$", + 2, + 3, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 2); + let parsed = parse_document( + "\n\nnothing on this line\n$zeta^2$\n#sym.arrow\n$(alpha)$", + 0, + 0, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 0); + } + #[test] + fn test_functions() { + let parsed = parse_document( + "$arrow(x)$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 2); + + // Check that not too many decorations are added + let parsed = parse_document( + "$abs(x) x^abs(x) x_abs(x) arrow(abs(x))$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 6); + let parsed = parse_document( + "$bb(\"hello\") cal(\"world!\") frak(!)$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 3); + let parsed = parse_document( + "$dot(x) dot.double(x) tilde(x) norm(x) sqrt(2) sqrt(2^2)$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 9); + } + #[test] + fn test_field_access() { + let parsed = parse_document( + "$beta.alt$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 1); + assert_eq!(parsed.decorations[0].symbol, "ϐ"); + assert_eq!(parsed.decorations[0].uuid, "beta.alt"); + let parsed = parse_document( + "$triangle.filled.b$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 1); + assert_eq!(parsed.decorations[0].symbol, "▼"); + assert_eq!(parsed.decorations[0].uuid, "triangle.filled.b"); + } + #[test] + fn test_text() { + let parsed = parse_document( + "$x^a x_a$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 2); + assert_eq!(parsed.decorations[0].symbol, "a"); + + let parsed = parse_document( + "$x^\"text\" x_\"text\"$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 2); + assert_eq!(parsed.decorations[0].symbol, "text"); + } + #[test] + fn test_linebreak() { + let parsed = parse_document( + "$x$ \\ \\ \\ x", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 1); + assert_eq!(parsed.decorations[0].symbol, "⮰"); + } + #[test] + fn test_math_block() { + let parsed = parse_document( + "$x^(5+3-2)=6$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 3); + let parsed = parse_document( + "$x^(alpha)$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 2); + let parsed = parse_document( + "$x^(\"alpha\") x^(-\"alpha\") x^(-alpha)$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 4); + } + #[test] + fn test_shortands() { + let parsed = parse_document( + "$=> + - * |--> [ |]$", + -1, + -1, + 3, + true, + true, + vec![], + vec![], + ); + assert_eq!(parsed.decorations.len(), 7); } } diff --git a/typst-math-rust/tests/web.rs b/typst-math-rust/tests/web.rs new file mode 100644 index 0000000..03bdfc0 --- /dev/null +++ b/typst-math-rust/tests/web.rs @@ -0,0 +1,14 @@ +//! Test suite for the Web and headless browsers. + +#![cfg(target_arch = "wasm32")] + +extern crate wasm_bindgen_test; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn pass() { + assert_eq!(1 + 1, 2); + +}