From 0c441a55b5205b6ef7b2e367d7f579e1a2d18d5c Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Wed, 21 Feb 2024 15:43:12 +0000 Subject: [PATCH] feat(ssg): :art: Improve meta tag generation and unit tests Refactored tests into logical groupings, covered additional cases like duplicate keys, long values, empty inputs, whitespace handling, and further validated behaviour --- src/modules/metatags.rs | 114 +++++++++--------- tests/test_metatags.rs | 261 +++++++++++++++++++++++++++++++--------- 2 files changed, 261 insertions(+), 114 deletions(-) diff --git a/src/modules/metatags.rs b/src/modules/metatags.rs index c684106d..04670c58 100644 --- a/src/modules/metatags.rs +++ b/src/modules/metatags.rs @@ -1,10 +1,8 @@ // Copyright © 2024 Shokunin Static Site Generator. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT +use crate::macro_generate_tags_from_fields; use crate::models::data::{MetaTag, MetaTagGroups}; -use crate::{ - macro_generate_tags_from_fields, macro_generate_tags_from_list, -}; use std::collections::HashMap; // Type alias for better readability @@ -24,15 +22,10 @@ pub fn generate_custom_meta_tags( mapping .iter() .filter_map(|(key, value)| { - if let Some(val) = value.as_ref() { - if !val.is_empty() { - Some((key.clone(), val.clone())) - } else { - None - } - } else { - None - } + value + .as_ref() + .map(|val| (key.clone(), val.clone())) + .filter(|(_, val)| !val.is_empty()) }) .collect(); generate_metatags(&filtered_mapping) @@ -47,7 +40,7 @@ pub fn generate_custom_meta_tags( /// A `String` containing the HTML code for the meta tags. pub fn generate_metatags(meta: &[(String, String)]) -> String { meta.iter() - .map(|(key, value)| format_meta_tag(key, value)) + .map(|(key, value)| format_meta_tag(key, value.trim())) .collect::>() .join("\n") } @@ -66,7 +59,10 @@ pub fn load_metatags( ) -> String { let mut result = String::new(); for &name in tag_names { - let value = metadata.get(name).cloned().unwrap_or_default(); + let value = metadata + .get(name) + .cloned() + .unwrap_or_else(|| String::new()); result.push_str( &MetaTag::new(name.to_string(), value).generate(), ); @@ -83,7 +79,9 @@ pub fn load_metatags( /// # Returns /// A `String` containing the HTML representation of the meta tag. pub fn format_meta_tag(key: &str, value: &str) -> String { - format!("", key, value) + // Sanitize the value by replacing newline characters with spaces + let sanitized_value = value.replace("\n", " "); + format!("", key, &sanitized_value) } /// Generates HTML meta tags for Apple-specific settings. @@ -95,17 +93,17 @@ pub fn format_meta_tag(key: &str, value: &str) -> String { /// A `String` containing the HTML code for the meta tags. /// pub fn generate_apple_meta_tags(metadata: &MetaDataMap) -> String { - let tag_names = - [ - "apple_mobile_web_app_orientations", - "apple_touch_icon_sizes", - "apple-mobile-web-app-capable", - "apple-mobile-web-app-status-bar-inset", - "apple-mobile-web-app-status-bar-style", - "apple-mobile-web-app-title", - "apple-touch-fullscreen", - ]; - macro_generate_tags_from_list!(&tag_names, metadata) + macro_generate_tags_from_fields!( + tag_names, + metadata, + "apple_mobile_web_app_orientations" => apple_mobile_web_app_orientations, + "apple_touch_icon_sizes" => apple_touch_icon_sizes, + "apple-mobile-web-app-capable" => apple_mobile_web_app_capable, + "apple-mobile-web-app-status-bar-inset" => apple_mobile_web_app_status_bar_inset, + "apple-mobile-web-app-status-bar-style" => apple_mobile_web_app_status_bar_style, + "apple-mobile-web-app-title" => apple_mobile_web_app_title, + "apple-touch-fullscreen" => apple_touch_fullscreen + ) } /// Generates HTML meta tags for primary settings like author, description, etc. @@ -117,23 +115,24 @@ pub fn generate_apple_meta_tags(metadata: &MetaDataMap) -> String { /// A `String` containing the HTML code for the meta tags. /// pub fn generate_primary_meta_tags(metadata: &MetaDataMap) -> String { - let tag_names = [ - "author", - "description", - "format-detection", - "generator", - "keywords", - "language", - "permalink", - "rating", - "referrer", - "revisit-after", - "robots", - "theme-color", - "title", - "viewport", - ]; - macro_generate_tags_from_list!(&tag_names, metadata) + macro_generate_tags_from_fields!( + tag_names, + metadata, + "author" => author, + "description" => description, + "format-detection" => format_detection, + "generator" => generator, + "keywords" => keywords, + "language" => language, + "permalink" => permalink, + "rating" => rating, + "referrer" => referrer, + "revisit-after" => revisit_after, + "robots" => robots, + "theme-color" => theme_color, + "title" => title, + "viewport" => viewport + ) } /// Generates HTML meta tags for Open Graph settings, primarily for social media. @@ -159,7 +158,7 @@ pub fn generate_primary_meta_tags(metadata: &MetaDataMap) -> String { /// pub fn generate_og_meta_tags(metadata: &MetaDataMap) -> String { macro_generate_tags_from_fields!( - generate_og_meta_tags, + tag_names, metadata, "og:description" => description, "og:image" => image, @@ -170,7 +169,7 @@ pub fn generate_og_meta_tags(metadata: &MetaDataMap) -> String { "og:site_name" => site_name, "og:title" => title, "og:type" => type, - "og:url" => permalink + "og:url" => url ) } @@ -183,8 +182,11 @@ pub fn generate_og_meta_tags(metadata: &MetaDataMap) -> String { /// A `String` containing the HTML code for the meta tags. /// pub fn generate_ms_meta_tags(metadata: &MetaDataMap) -> String { - let tag_names = ["msapplication-navbutton-color"]; - macro_generate_tags_from_list!(&tag_names, metadata) + macro_generate_tags_from_fields!( + tag_names, + metadata, + "msapplication-navbutton-color" => msapplication_navbutton_color + ) } /// Generates HTML meta tags for Twitter-specific settings. @@ -209,18 +211,18 @@ pub fn generate_ms_meta_tags(metadata: &MetaDataMap) -> String { /// pub fn generate_twitter_meta_tags(metadata: &MetaDataMap) -> String { macro_generate_tags_from_fields!( - generate_twitter_meta_tags, + tag_names, metadata, "twitter:card" => twitter_card, "twitter:creator" => twitter_creator, - "twitter:description" => description, - "twitter:image" => image, - "twitter:image:alt" => image_alt, - "twitter:image:height" => image_height, - "twitter:image:width" => image_width, - "twitter:site" => url, - "twitter:title" => title, - "twitter:url" => url + "twitter:description" => twitter_description, + "twitter:image" => twitter_image, + "twitter:image:alt" => twitter_image_alt, + "twitter:image:height" => twitter_image_height, + "twitter:image:width" => twitter_image_width, + "twitter:site" => twitter_site, + "twitter:title" => twitter_title, + "twitter:url" => twitter_url ) } diff --git a/tests/test_metatags.rs b/tests/test_metatags.rs index bddb3072..f933f4d3 100644 --- a/tests/test_metatags.rs +++ b/tests/test_metatags.rs @@ -1,113 +1,258 @@ #[cfg(test)] mod tests { + use regex::Regex; use ssg::modules::metatags::generate_metatags; + // Test general functionality of the generate_metatags function #[test] - fn test_generate_metatags() { - let meta = vec![ + fn test_generate_metatags_general() { + let test_cases = vec![ + ( + vec![ + ("description".to_string(), "A blog about Rust programming.".to_string()), + ("keywords".to_string(), "Rust, programming".to_string()), + ], + "\n", + ), + ( + vec![], + "", + ), + ( + vec![("description".to_string(), "My site description".to_string())], + "", + ), + ( + vec![ + ("description".to_string(), "My site description".to_string()), + ("keywords".to_string(), "rust,web,ssg".to_string()), + ("author".to_string(), "John Doe".to_string()), + ], + "\n\n", + ), + ( + vec![ + ("description".to_string(), "My site description".to_string()), + ("keywords".to_string(), "rust,web,ssg rust,web,ssg".to_string()), + ("author".to_string(), "John Doe".to_string()), + ], + "\n\n", + ), + ]; + + for (input_metadata, expected_output) in test_cases.iter() { + let result = generate_metatags(&input_metadata); + assert_eq!( + result, *expected_output, + "Mismatch in generated meta tags for general tests" + ); + } + } + + // Test for ensuring meta tag order stability + #[test] + fn test_generate_metatags_order_stability() { + let input_metadata = vec![ + ("author".to_string(), "John Doe".to_string()), ( "description".to_string(), "A blog about Rust programming.".to_string(), ), ("keywords".to_string(), "Rust, programming".to_string()), ]; - let result = generate_metatags(&meta); - let expected = "\n"; - assert_eq!(result, expected); + let expected_output = "\n\n"; + + let result = generate_metatags(&input_metadata); + + assert_eq!(result, expected_output, "Generated meta tags do not maintain the input order for order stability tests"); + + let regex = + Regex::new(r#""#) + .unwrap(); + let found_tags = regex + .captures_iter(&result) + .map(|cap| cap[1].to_string()) + .collect::>(); + let expected_tags = input_metadata + .iter() + .map(|(name, _)| name.clone()) + .collect::>(); + + assert_eq!( + found_tags, expected_tags, + "Meta tags are not in the expected order" + ); } + // Test basic functionality with a single metadata entry #[test] - fn test_generate_metatags_empty() { - let meta: &[(String, String)] = &[]; - let result = generate_metatags(meta); - assert_eq!(result, ""); + fn test_generate_metatags_basic() { + let test_cases = vec![ + ( + vec![("description".to_string(), "A blog about Rust programming.".to_string())], + "", + ), + // Add more basic cases as needed + ]; + + for (input_metadata, expected_output) in test_cases { + let result = generate_metatags(&input_metadata); + assert_eq!( + result, expected_output, + "Failed on basic metadata input" + ); + } } + // Test handling of empty and null cases #[test] - fn test_generate_metatags_single() { - let meta = &[( - "description".to_string(), - "My site description".to_string(), - )]; - let result = generate_metatags(meta); - assert_eq!( - result, - "" - ); + fn test_generate_metatags_edge_cases() { + let test_cases = vec![ + (vec![], ""), + ( + vec![("".to_string(), "".to_string())], + "", + ), + // Add more edge cases as needed + ]; + + for (input_metadata, expected_output) in test_cases { + let result = generate_metatags(&input_metadata); + assert_eq!( + result, expected_output, + "Failed on edge case metadata input" + ); + } } + // Test meta tag order stability and tag order verification #[test] - fn test_generate_metatags_multiple() { - let meta = &[ + fn test_generate_metatags_order_stability_order_check() { + let input_metadata = vec![ + ("author".to_string(), "John Doe".to_string()), ( "description".to_string(), - "My site description".to_string(), + "A blog about Rust programming.".to_string(), ), - ("keywords".to_string(), "rust,web,ssg".to_string()), - ("author".to_string(), "John Doe".to_string()), + ("keywords".to_string(), "Rust, programming".to_string()), ]; - let result = generate_metatags(meta); + let expected_order = vec!["author", "description", "keywords"]; + + let result = generate_metatags(&input_metadata); + let regex = + Regex::new(r#""#) + .unwrap(); + let matches = regex + .captures_iter(&result) + .map(|cap| cap[1].to_string()) + .collect::>(); + + // Ensure all expected tags are present in the result assert_eq!( - result, - "\n\n" + matches.len(), + expected_order.len(), + "Mismatch in number of generated meta tags" ); + + // Verify the order of generated meta tags matches the input order + for (expected, actual) in + expected_order.iter().zip(matches.iter()) + { + assert_eq!( + expected, actual, + "Meta tag order does not match expected order" + ); + } } + // Test behaviour with duplicate keys #[test] - fn test_generate_metatags_multiple_lines() { - let meta = &[ + fn test_generate_metatags_duplicate_keys() { + let input_metadata = vec![ ( "description".to_string(), - "My site description".to_string(), + "First description".to_string(), ), + ("keywords".to_string(), "Rust, programming".to_string()), ( - "keywords".to_string(), - "rust,web,ssg\nrust,web,ssg".to_string(), + "description".to_string(), + "Second description".to_string(), ), - ("author".to_string(), "John Doe".to_string()), ]; - let result = generate_metatags(meta); + let expected_output = "\n\n"; + + let result = generate_metatags(&input_metadata); + assert_eq!( - result, - "\n\n" + result, expected_output, + "Generated meta tags should handle duplicate keys" ); } + // Test behaviour with long input values #[test] - fn test_generate_metatags_empty_lines() { - let meta = &[ + fn test_generate_metatags_long_input_values() { + let long_description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".repeat(100); + let input_metadata = vec![ + ("description".to_string(), long_description), + ("keywords".to_string(), "Rust, programming".to_string()), + ]; + let result = generate_metatags(&input_metadata); + + assert!(!result.is_empty(), "Generated meta tags should not be empty for long input values"); + } + // Test behaviour with empty keys + #[test] + fn test_generate_metatags_empty_keys() { + let input_metadata = vec![ ( "description".to_string(), - "My site description".to_string(), + "A blog about Rust programming.".to_string(), ), - ("keywords".to_string(), "".to_string()), - ("author".to_string(), "John Doe".to_string()), + ("".to_string(), "Empty key".to_string()), + ("keywords".to_string(), "Rust, programming".to_string()), ]; - let result = generate_metatags(meta); + let expected_output = "\n\n"; + + let result = generate_metatags(&input_metadata); + assert_eq!( - result, - "\n\n" + result, expected_output, + "Generated meta tags should handle empty keys" ); - let meta: &[(String, String)] = &[]; - let result = generate_metatags(meta); - assert_eq!(result, ""); } + // Test behaviour with empty values #[test] - fn test_generate_metatags_multiple_lines_empty() { - let meta = &[ + fn test_generate_metatags_empty_values() { + let input_metadata = vec![ ( "description".to_string(), - "My site description".to_string(), - ), - ( - "keywords".to_string(), - "rust,web,ssg\nrust,web,ssg".to_string(), + "A blog about Rust programming.".to_string(), ), - ("author".to_string(), "".to_string()), + ("keywords".to_string(), "".to_string()), ]; - let result = generate_metatags(meta); + let expected_output = "\n"; + + let result = generate_metatags(&input_metadata); + assert_eq!( - result, - "\n\n" + result, expected_output, + "Generated meta tags should handle empty values" ); } + // Test behaviour with whitespace handling + #[test] + fn test_generate_metatags_whitespace_handling() { + let input_metadata = vec![ + ( + "description".to_string(), + " A blog about Rust programming. ".to_string(), + ), + ("keywords".to_string(), " Rust, programming ".to_string()), + ]; + let expected_output = "\n"; + + let result = generate_metatags(&input_metadata); + + assert_eq!(result, expected_output, "Generated meta tags should handle whitespace appropriately"); + } }