#[cfg(test)]
mod tests {
use crate::config::{ConfigSource, SourcedValue};
use std::collections::HashSet;
fn make_sourced_vec(values: &[&str], source: ConfigSource) -> SourcedValue<Vec<String>> {
SourcedValue::new(values.iter().map(std::string::ToString::to_string).collect(), source)
}
fn assert_vec_eq(actual: &[String], expected: &[&str]) {
let actual_set: HashSet<_> = actual.iter().collect();
let expected_set: HashSet<_> = expected.iter().map(std::string::ToString::to_string).collect();
assert_eq!(
actual_set.len(),
expected_set.len(),
"Sets have different sizes. Actual: {actual:?}, Expected: {expected:?}"
);
for item in &expected_set {
assert!(
actual_set.contains(&item),
"Expected item {item:?} not found in {actual:?}"
);
}
}
#[test]
fn test_merge_union_combines_arrays() {
let mut user_disable = make_sourced_vec(&["MD013"], ConfigSource::UserConfig);
user_disable.merge_union(vec!["MD041".to_string()], ConfigSource::PyprojectToml, None);
assert_vec_eq(&user_disable.value, &["MD013", "MD041"]);
assert_eq!(user_disable.source, ConfigSource::PyprojectToml);
}
#[test]
fn test_merge_union_deduplicates() {
let mut user_disable = make_sourced_vec(&["MD013", "MD041"], ConfigSource::UserConfig);
user_disable.merge_union(
vec!["MD013".to_string(), "MD047".to_string()],
ConfigSource::PyprojectToml,
None,
);
assert_vec_eq(&user_disable.value, &["MD013", "MD041", "MD047"]);
assert_eq!(user_disable.value.len(), 3, "Should not have duplicates");
}
#[test]
fn test_merge_union_with_empty_new_value() {
let mut user_disable = make_sourced_vec(&["MD013"], ConfigSource::UserConfig);
user_disable.merge_union(vec![], ConfigSource::PyprojectToml, None);
assert_vec_eq(&user_disable.value, &["MD013"]);
}
#[test]
fn test_merge_union_respects_precedence() {
let mut user_disable = make_sourced_vec(&["MD013"], ConfigSource::UserConfig);
user_disable.merge_union(vec!["MD041".to_string()], ConfigSource::Default, None);
assert_vec_eq(&user_disable.value, &["MD013"]);
assert_eq!(user_disable.source, ConfigSource::UserConfig);
}
#[test]
fn test_merge_union_equal_precedence_merges() {
let mut disable = make_sourced_vec(&["MD013"], ConfigSource::UserConfig);
disable.merge_union(vec!["MD041".to_string()], ConfigSource::UserConfig, None);
assert_vec_eq(&disable.value, &["MD013", "MD041"]);
}
#[test]
fn test_merge_replace_replaces_arrays() {
let mut user_enable = make_sourced_vec(&["MD001", "MD002"], ConfigSource::UserConfig);
user_enable.merge_override(vec!["MD003".to_string()], ConfigSource::PyprojectToml, None);
assert_vec_eq(&user_enable.value, &["MD003"]);
assert_eq!(user_enable.source, ConfigSource::PyprojectToml);
}
#[test]
fn test_merge_replace_respects_precedence() {
let mut enable = make_sourced_vec(&["MD001"], ConfigSource::PyprojectToml);
enable.merge_override(vec!["MD002".to_string()], ConfigSource::UserConfig, None);
assert_vec_eq(&enable.value, &["MD001"]);
assert_eq!(enable.source, ConfigSource::PyprojectToml);
}
#[test]
fn test_scalar_merge_replaces_with_higher_precedence() {
let mut line_length = SourcedValue::new(120u64, ConfigSource::UserConfig);
line_length.merge_override(80u64, ConfigSource::PyprojectToml, None);
assert_eq!(line_length.value, 80);
assert_eq!(line_length.source, ConfigSource::PyprojectToml);
}
#[test]
fn test_scalar_merge_preserves_with_lower_precedence() {
let mut line_length = SourcedValue::new(80u64, ConfigSource::PyprojectToml);
line_length.merge_override(120u64, ConfigSource::UserConfig, None);
assert_eq!(line_length.value, 80);
assert_eq!(line_length.source, ConfigSource::PyprojectToml);
}
#[test]
fn test_enable_disables_remove_conflicts() {
let mut disable_list = vec!["MD013".to_string(), "MD041".to_string()];
let enable_list = ["MD013".to_string()];
disable_list.retain(|rule| !enable_list.contains(rule));
assert_vec_eq(&disable_list, &["MD041"]);
}
#[test]
fn test_integration_user_and_project_config_merge() {
use crate::config::{SourcedConfig, SourcedConfigFragment};
let mut config = SourcedConfig::default();
config.global.disable = make_sourced_vec(&["MD013", "MD041"], ConfigSource::UserConfig);
config.global.enable = make_sourced_vec(&[], ConfigSource::UserConfig);
let mut project_fragment = SourcedConfigFragment::default();
project_fragment.global.disable = make_sourced_vec(&["MD047"], ConfigSource::PyprojectToml);
project_fragment.global.enable = make_sourced_vec(&["MD001"], ConfigSource::PyprojectToml);
config.merge(project_fragment);
assert_vec_eq(&config.global.disable.value, &["MD047"]);
assert_vec_eq(&config.global.enable.value, &["MD001"]);
}
#[test]
fn test_integration_enable_overrides_disable() {
use crate::config::{SourcedConfig, SourcedConfigFragment};
let mut config = SourcedConfig::default();
config.global.disable = make_sourced_vec(&["MD013"], ConfigSource::UserConfig);
config.global.enable = make_sourced_vec(&[], ConfigSource::UserConfig);
let mut project_fragment = SourcedConfigFragment::default();
project_fragment.global.enable = make_sourced_vec(&["MD013"], ConfigSource::PyprojectToml);
config.merge(project_fragment);
assert_vec_eq(&config.global.enable.value, &["MD013"]);
assert!(
!config.global.disable.value.contains(&"MD013".to_string()),
"MD013 should be removed from disable when enabled by project config"
);
}
#[test]
fn test_cli_has_highest_precedence() {
let mut enable = make_sourced_vec(&["MD001"], ConfigSource::PyprojectToml);
enable.merge_override(vec!["MD002".to_string()], ConfigSource::Cli, None);
assert_vec_eq(&enable.value, &["MD002"]);
assert_eq!(enable.source, ConfigSource::Cli);
}
#[test]
fn test_precedence_order() {
use crate::config::{ConfigSource, SourcedValue};
const ASCENDING: &[ConfigSource] = &[
ConfigSource::Default,
ConfigSource::EditorConfig,
ConfigSource::UserConfig,
ConfigSource::PyprojectToml,
ConfigSource::ProjectConfig,
ConfigSource::Cli,
];
for (rank, &weaker) in ASCENDING.iter().enumerate() {
for &stronger in &ASCENDING[rank + 1..] {
let mut value = SourcedValue::new("weaker", stronger);
value.merge_override("stronger", weaker, None);
assert_eq!(
value.value, "weaker",
"{weaker:?} must not override the higher-precedence {stronger:?}"
);
let mut value = SourcedValue::new("weaker", weaker);
value.merge_override("stronger", stronger, None);
assert_eq!(value.value, "stronger", "{stronger:?} must override {weaker:?}");
}
}
}
#[test]
fn test_empty_enable_doesnt_clear_disable() {
use crate::config::{SourcedConfig, SourcedConfigFragment};
let mut config = SourcedConfig::default();
config.global.disable = make_sourced_vec(&["MD013", "MD041"], ConfigSource::UserConfig);
let project_fragment = SourcedConfigFragment::default();
config.merge(project_fragment);
assert_vec_eq(&config.global.disable.value, &["MD013", "MD041"]);
}
#[test]
fn test_multiple_merges_accumulate_disable() {
let mut disable = make_sourced_vec(&["MD013"], ConfigSource::Default);
disable.merge_union(vec!["MD041".to_string()], ConfigSource::UserConfig, None);
assert_vec_eq(&disable.value, &["MD013", "MD041"]);
disable.merge_union(vec!["MD047".to_string()], ConfigSource::PyprojectToml, None);
assert_vec_eq(&disable.value, &["MD013", "MD041", "MD047"]);
disable.merge_union(vec!["MD001".to_string()], ConfigSource::Cli, None);
assert_vec_eq(&disable.value, &["MD013", "MD041", "MD047", "MD001"]);
}
#[test]
fn test_origin_tracking() {
let mut disable = make_sourced_vec(&["MD013"], ConfigSource::UserConfig);
assert_eq!(disable.origin, None, "values without a file have no origin");
disable.merge_union(
vec!["MD041".to_string()],
ConfigSource::PyprojectToml,
Some("project.toml".to_string()),
);
assert_eq!(disable.source, ConfigSource::PyprojectToml);
assert_eq!(disable.origin, Some("project.toml".to_string()));
disable.merge_override(
vec!["MD001".to_string()],
ConfigSource::UserConfig,
Some("user.toml".to_string()),
);
assert_eq!(disable.origin, Some("project.toml".to_string()));
}
#[test]
fn test_origin_survives_extends_style_merge() {
use crate::config::{SourcedConfig, SourcedConfigFragment};
let mut config = SourcedConfig::default();
let mut base = SourcedConfigFragment::default();
base.global.exclude.push_override(
vec!["drafts".to_string()],
ConfigSource::ProjectConfig,
Some("base.toml".to_string()),
);
config.merge(base);
let mut child = SourcedConfigFragment::default();
child.global.enable.push_override(
vec!["MD001".to_string()],
ConfigSource::ProjectConfig,
Some(".rumdl.toml".to_string()),
);
config.merge(child);
assert_eq!(config.global.exclude.origin, Some("base.toml".to_string()));
assert_eq!(config.global.enable.origin, Some(".rumdl.toml".to_string()));
}
}