diff --git a/CHANGELOG.md b/CHANGELOG.md index 7047dd1..4555d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,16 @@ # Changelog -# v3.1.0 +## v3.1.0 - Replace structopts with clap [#10](https://github.com/KevinGimbel/mktoc/issues/10) by [@oylenshpeegul](https://github.com/oylenshpeegul) - wrap ToC in details block [#8](https://github.com/KevinGimbel/mktoc/issues/8) by [@KevinGimbel](https://github.com/KevinGimbel) - Links in Headlines produce wrong output [#12](https://github.com/KevinGimbel/mktoc/issues/12) by [@KevinGimbel](https://github.com/KevinGimbel) -# v3.0.0 +### Misc + +- add more tests + +## v3.0.0 - Implement JSON settings - Add build for GitHub Binary releases \ No newline at end of file diff --git a/helpers/coverage.sh b/helpers/coverage.sh index 0c01356..74ce10a 100755 --- a/helpers/coverage.sh +++ b/helpers/coverage.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -export LLVM_PROFILE_FILE="fd-%p-%m.profraw" +export LLVM_PROFILE_FILE="mktoc-%p-%m.profraw" export RUSTFLAGS="-Cinstrument-coverage" # build project diff --git a/src/lib.rs b/src/lib.rs index 5286100..ee8aa64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,23 @@ impl Default for Config { } } +impl PartialEq for Config { + fn eq(&self, other: &Self) -> bool { + return self.max_depth == other.max_depth + && self.min_depth == other.min_depth + && self.wrap_in_details == other.wrap_in_details + && self.start_comment == other.start_comment; + + } + + fn ne(&self, other: &Self) -> bool { + return self.max_depth != other.max_depth + || self.min_depth != other.min_depth + || self.wrap_in_details != other.wrap_in_details + || self.start_comment != other.start_comment; + } +} + fn default_min_depth() -> i32 { 1 } @@ -84,8 +101,7 @@ pub fn generate_toc(original_content: String, config: Config) -> String { let mut new_toc = String::from(""); let re = regex::Regex::new(r"((#{1,6}\s))((.*))").unwrap(); for line in original_content.lines() { - let line_s: String = line.chars().take(3).collect(); - if line_s == *"```" { + if line.starts_with("```") { code_block_found = true; } @@ -147,8 +163,8 @@ pub fn generate_toc(original_content: String, config: Config) -> String { } fn cleanup_wrapped_toc(input: String) -> String { - // starting with an indention of 4 GitHub will render code. So we strip away all lines - // staring with 4 spaces. + // 4 spaces will render a code block if wrapped inside a HTML element. + // So we strip away all lines staring with 4 spaces. let re = Regex::new(r"(?m)^ {4}").unwrap(); let new_content = re.replace_all(&input, "").to_string(); @@ -225,6 +241,257 @@ pub fn make_toc

( #[cfg(test)] mod tests { use super::*; + #[test] + fn test_read_file() { + struct TestCase<'a> { + name: &'a str, + input: &'a str, + expect_error: bool, + } + + let tests = [ + TestCase{ + name: "File exists and can be read", + input: "tests/files/README_01.md", + expect_error: false + }, + TestCase{ + name: "File does not exist", + input: "tests/files/doesnt-exists.md", + expect_error: true + }, + TestCase{ + name: "Directory does not exist", + input: "anywhere/but/here/README.md", + expect_error: true + } + ]; + + for test in tests { + dbg!(test.name); + match read_file(test.input) { + Ok(_content) => { + assert_eq!(false, test.expect_error) + } + Err(_err) => { + assert_eq!(true, test.expect_error) + } + } + } + } + + #[test] + fn test_generate_toc() { + struct TestCase<'a> { + name: &'a str, + input: &'a str, + expected: &'a str, + } + + let tests = [ + TestCase{ + name: "Can parse simple input to ToC", + input: r#" +# Test + + +## Hello +### World"#, + expected: r#" + +- [Test](#test) +- [Hello](#hello) + - [World](#world) +"# + }, + TestCase{ + name: "Can find all heading levels", + input: r#" +# Test 1 + + +## Test 2 +### Test 3 +#### Test 4 +##### Test 5 +###### Test 6"#, + expected: r#" + +- [Test 1](#test-1) +- [Test 2](#test-2) + - [Test 3](#test-3) + - [Test 4](#test-4) + - [Test 5](#test-5) + - [Test 6](#test-6) +"# + }, + TestCase{ + name: "Can parse with code in headings", + input: r#" +# Test + + +## `Hello` +### World"#, + expected: r#" + +- [Test](#test) +- [`Hello`](#hello) + - [World](#world) +"# + }, + TestCase{ + name: "Can parse headings with emojis", + input: r#" +# Test + + +## Hello 🥳 +### World"#, + expected: r#" + +- [Test](#test) +- [Hello 🥳](#hello-🥳) + - [World](#world) +"# + }, + TestCase{ + name: "Can exclude code blocks", + input: r#" +# Test + + +## Hello + +Lorem Ipsum Dolor... + +``` +# inline comment +fn some_func() -> bool {} +``` +"#, + expected: r#" + +- [Test](#test) +- [Hello](#hello) +"# + } + ]; + + // (original_content: String, config: Config) + + for test in tests { + dbg!(test.name); + let new_toc = generate_toc(test.input.to_string(), Config::default()); + assert_eq!(new_toc, test.expected.to_string()); + } + } + + #[test] + fn test_generate_toc_wrap_details() { + struct TestCase<'a> { + name: &'a str, + input: &'a str, + expected: &'a str, + } + + let tests = [ + TestCase{ + name: "Can wrap ToC in details", + input: r#" +# Test + + +## Hello +### World"#, + expected: r#" +

Table of Contents + +- [Test](#test) +- [Hello](#hello) + - [World](#world) + +
+ +"# + }, + ]; + + for test in tests { + dbg!(test.name); + let new_toc = generate_toc(test.input.to_string(), Config{wrap_in_details: true, ..Config::default()}); + assert_eq!(new_toc, test.expected.to_string()); + } + } + + #[test] + fn test_parse_json_config_or_use_provided() { + struct TestCase<'a> { + name: &'a str, + input: &'a str, + input_cnf: Config, + expected: Config, + } + + let tests = [ + TestCase{ + name: "Default config used if empty input", + input: "", + input_cnf: Config{..Default::default()}, + expected: Config{..Default::default()}, + }, + TestCase{ + name: "", + input: "", + input_cnf: Config{wrap_in_details: true, ..Default::default()}, + expected: Config{wrap_in_details: false, start_comment: String::from(""), ..Default::default()}, + } + ]; + + for test in tests { + dbg!(test.name); + let cnf = parse_json_config_or_use_provided(test.input, test.input_cnf); + assert_eq!(cnf, test.expected); + } + } + + #[test] + fn test_config_eq() { + let cnf1 = Config{..Default::default()}; + let cnf2 = Config{..Default::default()}; + + assert_eq!(cnf1, cnf2); + } + + #[test] + fn test_config_ne() { + struct TestCase { + cnf1: Config, + cnf2: Config, + } + + let tests = [ + TestCase{ + cnf1: Config{min_depth: 1, ..Default::default()}, + cnf2: Config{min_depth: 2, ..Default::default()}, + },TestCase{ + cnf1: Config{max_depth: 3, ..Default::default()}, + cnf2: Config{max_depth: 4, ..Default::default()}, + }, + TestCase{ + cnf1: Config{wrap_in_details: false, ..Default::default()}, + cnf2: Config{wrap_in_details: true, ..Default::default()}, + }, + TestCase{ + cnf1: Config{start_comment: String::from(""), ..Default::default()}, + cnf2: Config{start_comment: String::from("Different"), ..Default::default()}, + }, + ]; + + for test in tests { + assert!(test.cnf1 != test.cnf2); + } + } #[test] fn test_text_to_url() { @@ -234,7 +501,7 @@ mod tests { expected: String, } - let tests = vec![ + let tests = [ TestCase{ name: "Case 01: My heading", input: "My heading", @@ -276,7 +543,7 @@ mod tests { expected: Config, } - let tests = vec![ + let tests = [ TestCase{ name: "only min_depth set", input: "", @@ -315,6 +582,22 @@ mod tests { ..Default::default() } }, + TestCase{ + name: "invalid min_depth set results in default min_depth being used", + input: "", + expected: Config{ + max_depth: 6, + ..Default::default() + } + }, + TestCase{ + name: "invalid config used, fallback to default", + input: "", + expected: Config{ + max_depth: 6, + ..Default::default() + } + }, ]; for test in tests { @@ -360,4 +643,10 @@ mod tests { assert_eq!(strip_markdown_links(test_case.input), test_case.expected); } } + + // TODO: implement this test + // #[test] + // fn test_make_toc() { + // todo!() + // } } \ No newline at end of file diff --git a/tests/files/README_07_rust-code.md b/tests/files/README_07_rust-code.md index 4684963..df160ee 100644 --- a/tests/files/README_07_rust-code.md +++ b/tests/files/README_07_rust-code.md @@ -33,4 +33,11 @@ let a = MyStruct{name: "a_struct", ..Default::default()}; assert_eq("a_struct", a.name); assert_eq(String::from("1234-4321-9817"), a.id); +``` + +### No lang tag code block + +``` +## this is some text +just some text :) ``` \ No newline at end of file