#[cfg(test)]
mod tests {
use crate::config::{ConfigSource, SourcedValue};
use std::collections::HashSet;
fn make_sourced_vec(values: Vec<&str>, source: ConfigSource) -> SourcedValue<Vec<String>> {
SourcedValue::new(values.iter().map(|s| s.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(|s| s.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(vec!["MD013"], ConfigSource::UserConfig);
user_disable.merge_union(vec!["MD041".to_string()], ConfigSource::PyprojectToml, None, 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(vec!["MD013", "MD041"], ConfigSource::UserConfig);
user_disable.merge_union(
vec!["MD013".to_string(), "MD047".to_string()],
ConfigSource::PyprojectToml,
None,
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(vec!["MD013"], ConfigSource::UserConfig);
user_disable.merge_union(vec![], ConfigSource::PyprojectToml, None, None);
assert_vec_eq(&user_disable.value, &["MD013"]);
}
#[test]
fn test_merge_union_respects_precedence() {
let mut user_disable = make_sourced_vec(vec!["MD013"], ConfigSource::UserConfig);
user_disable.merge_union(vec!["MD041".to_string()], ConfigSource::Default, None, 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(vec!["MD013"], ConfigSource::UserConfig);
disable.merge_union(vec!["MD041".to_string()], ConfigSource::UserConfig, None, None);
assert_vec_eq(&disable.value, &["MD013", "MD041"]);
}
#[test]
fn test_merge_replace_replaces_arrays() {
let mut user_enable = make_sourced_vec(vec!["MD001", "MD002"], ConfigSource::UserConfig);
user_enable.merge_override(vec!["MD003".to_string()], ConfigSource::PyprojectToml, None, 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(vec!["MD001"], ConfigSource::PyprojectToml);
enable.merge_override(vec!["MD002".to_string()], ConfigSource::UserConfig, None, 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, 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, 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, SourcedGlobalConfig};
let mut config = SourcedConfig::default();
config.global.disable = make_sourced_vec(vec!["MD013", "MD041"], ConfigSource::UserConfig);
config.global.enable = make_sourced_vec(vec![], ConfigSource::UserConfig);
let mut project_fragment = SourcedConfigFragment {
extends: None,
global: SourcedGlobalConfig::default(),
per_file_ignores: SourcedValue::new(Default::default(), ConfigSource::Default),
per_file_flavor: SourcedValue::new(Default::default(), ConfigSource::Default),
code_block_tools: SourcedValue::new(Default::default(), ConfigSource::Default),
rules: Default::default(),
rule_display_names: Default::default(),
unknown_keys: vec![],
};
project_fragment.global.disable = make_sourced_vec(vec!["MD047"], ConfigSource::PyprojectToml);
project_fragment.global.enable = make_sourced_vec(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, SourcedGlobalConfig};
let mut config = SourcedConfig::default();
config.global.disable = make_sourced_vec(vec!["MD013"], ConfigSource::UserConfig);
config.global.enable = make_sourced_vec(vec![], ConfigSource::UserConfig);
let mut project_fragment = SourcedConfigFragment {
extends: None,
global: SourcedGlobalConfig::default(),
per_file_ignores: SourcedValue::new(Default::default(), ConfigSource::Default),
per_file_flavor: SourcedValue::new(Default::default(), ConfigSource::Default),
code_block_tools: SourcedValue::new(Default::default(), ConfigSource::Default),
rules: Default::default(),
rule_display_names: Default::default(),
unknown_keys: vec![],
};
project_fragment.global.enable = make_sourced_vec(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(vec!["MD001"], ConfigSource::PyprojectToml);
enable.merge_override(vec!["MD002".to_string()], ConfigSource::Cli, None, None);
assert_vec_eq(&enable.value, &["MD002"]);
assert_eq!(enable.source, ConfigSource::Cli);
}
#[test]
fn test_precedence_order() {
use crate::config::ConfigSource;
fn get_precedence(src: ConfigSource) -> u8 {
match src {
ConfigSource::Default => 0,
ConfigSource::UserConfig => 1,
ConfigSource::PyprojectToml => 2,
ConfigSource::ProjectConfig => 3,
ConfigSource::Cli => 4,
}
}
assert!(get_precedence(ConfigSource::Default) < get_precedence(ConfigSource::UserConfig));
assert!(get_precedence(ConfigSource::UserConfig) < get_precedence(ConfigSource::PyprojectToml));
assert!(get_precedence(ConfigSource::PyprojectToml) < get_precedence(ConfigSource::ProjectConfig));
assert!(get_precedence(ConfigSource::ProjectConfig) < get_precedence(ConfigSource::Cli));
}
#[test]
fn test_empty_enable_doesnt_clear_disable() {
use crate::config::{SourcedConfig, SourcedConfigFragment, SourcedGlobalConfig};
let mut config = SourcedConfig::default();
config.global.disable = make_sourced_vec(vec!["MD013", "MD041"], ConfigSource::UserConfig);
let project_fragment = SourcedConfigFragment {
extends: None,
global: SourcedGlobalConfig::default(),
per_file_ignores: SourcedValue::new(Default::default(), ConfigSource::Default),
per_file_flavor: SourcedValue::new(Default::default(), ConfigSource::Default),
code_block_tools: SourcedValue::new(Default::default(), ConfigSource::Default),
rules: Default::default(),
rule_display_names: Default::default(),
unknown_keys: vec![],
};
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(vec!["MD013"], ConfigSource::Default);
disable.merge_union(vec!["MD041".to_string()], ConfigSource::UserConfig, None, None);
assert_vec_eq(&disable.value, &["MD013", "MD041"]);
disable.merge_union(vec!["MD047".to_string()], ConfigSource::PyprojectToml, None, None);
assert_vec_eq(&disable.value, &["MD013", "MD041", "MD047"]);
disable.merge_union(vec!["MD001".to_string()], ConfigSource::Cli, None, None);
assert_vec_eq(&disable.value, &["MD013", "MD041", "MD047", "MD001"]);
}
#[test]
fn test_history_tracking() {
let mut disable = make_sourced_vec(vec!["MD013"], ConfigSource::UserConfig);
assert_eq!(disable.overrides.len(), 1);
disable.merge_union(
vec!["MD041".to_string()],
ConfigSource::PyprojectToml,
Some("project.toml".to_string()),
Some(5),
);
assert_eq!(disable.overrides.len(), 2);
assert_eq!(disable.overrides[1].source, ConfigSource::PyprojectToml);
assert_eq!(disable.overrides[1].file, Some("project.toml".to_string()));
assert_eq!(disable.overrides[1].line, Some(5));
}
}