use super::*;
fn scramble(text: &str) -> String {
scramble_with(1, text, true)
}
fn scramble_with(seed: u64, text: &str, para_start: bool) -> String {
let chars: Vec<char> = text.chars().collect();
let protect = vec![false; chars.len()];
Scrambler::new(seed)
.scramble(&chars, &protect, para_start)
.into_iter()
.collect()
}
const PROSE: &str = "Ich war in großer Verlegenheit: eine dringende Reise stand mir bevor; \
ein Schwerkranker wartete auf mich in einem zehn Meilen entfernten Dorfe";
const AUSTEN: &str = "However little known the feelings or views of such a man may be on \
his first entering a neighbourhood, this truth is so well fixed in the minds of the \
surrounding families, that he is considered as the rightful property of some one or \
other of their daughters.";
#[test]
fn letters_change_in_kind_and_everything_else_stays() {
let src = "In seinem Äu\u{ad}ßern erinnert wenig an die Zeit, da er noch Streitroß Alexanders \
von Mazedonien war. »Doch sah ich letzthin« e\u{301}ben 12, 3½ – \"My dear Mr. \
Bennet,\" said his lady to him one day.";
let chars: Vec<char> = src.chars().collect();
let sc = Scrambler::new(1);
sc.set_lang_budget(0);
let out: String = sc
.scramble(&chars, &vec![false; chars.len()], true)
.into_iter()
.collect();
assert_eq!(src.chars().count(), out.chars().count());
for (a, b) in src.chars().zip(out.chars()) {
if a.is_alphabetic() {
assert!(b.is_alphabetic(), "{a} -> {b}");
assert_eq!(a.is_uppercase(), b.is_uppercase(), "{a} -> {b}");
if a != 'ß' {
assert_ne!(a, b, "{src} -> {out}");
}
let kind = |c: char| {
let l = lower1(c);
if l == 'ß' {
2
} else if "äöüëïÿ".contains(l) {
1
} else if VOWELS.contains(l) {
0
} else {
3
}
};
assert_eq!(kind(a), kind(b), "{a} -> {b}");
} else {
assert_eq!(a, b);
}
}
assert!(out.contains('ß'));
}
#[test]
fn it_is_deterministic_and_the_seed_matters() {
assert_eq!(scramble(PROSE), scramble(PROSE));
assert_ne!(scramble_with(1, PROSE, true), scramble_with(2, PROSE, true));
}
#[test]
fn a_word_is_replaced_alike_wherever_it_stands() {
let out = scramble("Bucephalus Bucephalus, BUCEPHALUS bucephalus");
let words: Vec<&str> = out.split([' ', ',']).filter(|w| !w.is_empty()).collect();
assert_eq!(words[0], words[1]);
assert_eq!(words[0].to_lowercase(), words[2].to_lowercase());
assert_eq!(words[0].to_lowercase(), words[3]);
assert_ne!(words[0], "Bucephalus");
assert!(words[2].chars().all(char::is_uppercase));
}
#[test]
fn the_importers_words_stay_in_short_lines_only() {
for (text, kept) in [
("Chapter Three: The Storm", "Chapter Three: The "),
("Kapitel einundzwanzig", "Kapitel einundzwanzig"),
("Erstes Kapitel", "Erstes Kapitel"),
("Teil 2", "Teil 2"),
("IV. Der Sturm", "IV. Der "),
("Prolog", "Prolog"),
("About the Author", "About the Author"),
("Weitere Bücher von Franz Kafka", "Weitere Bücher von "),
("— Ende —", "— Ende —"),
("The End", "The End"),
("15 November 1978", "15 November 1978"),
("23.15 Uhr", "23.15 Uhr"),
("11:42 p.m.", "11:42 p.m."),
("Copyright 2020 Jane Austen", "Copyright 2020 "),
("ISBN 978-3-16-148410-0", "ISBN 978-3-16-148410-0"),
("www.example.org", "www."),
] {
let out = scramble(text);
assert!(out.starts_with(kept), "{text} -> {out}");
assert_eq!(out == text, kept == text, "{text} -> {out}");
}
let out = scramble("Bucephalus, approx. 4,000 words");
assert!(
out.ends_with("4,000 words") && !out.starts_with("Bucephalus"),
"{out}"
);
let out = scramble(PROSE);
assert!(
!out.contains("zehn") && !out.contains("Verlegenheit"),
"{out}"
);
assert!(scramble(&format!("Chapter 3: {PROSE}")).starts_with("Chapter 3: "));
assert!(!scramble(&format!("{PROSE}. Chapter")).ends_with("Chapter"));
}
#[test]
fn a_name_in_a_chapter_title_does_not_stay() {
let out = scramble("Chapter 3: Netherfield");
assert!(out.starts_with("Chapter 3: "));
assert!(!out.contains("Netherfield"));
}
#[test]
fn dedications_and_author_lines_keep_their_first_words() {
assert!(scramble("For Mrs. Bennet").starts_with("For "));
assert!(scramble("*Für Dr. Bucephalus*").starts_with("*Für "));
assert!(scramble("by Jane Austen").starts_with("by "));
assert!(scramble("Gewidmet Dr. Bucephalus").starts_with("Gewidmet "));
let long = format!("Gewidmet {PROSE}");
assert!(scramble_with(1, &long, true).starts_with("Gewidmet "));
assert!(!scramble_with(1, &long, false).starts_with("Gewidmet"));
assert!(!scramble("Chapter 3: Liebe").contains("Liebe"));
}
#[test]
fn language_words_stay_while_the_importer_counts() {
let out = scramble(AUSTEN);
assert_eq!(lang::detect([AUSTEN]), Some("en"));
assert_eq!(lang::detect([out.as_str()]), Some("en"), "{out}");
assert!(out.contains(" the ") && !out.contains("feelings"));
let chars: Vec<char> = AUSTEN.chars().collect();
let protect = vec![false; chars.len()];
let sc = Scrambler::new(1);
sc.set_lang_budget(12);
let out: String = sc.scramble(&chars, &protect, false).into_iter().collect();
let (src, new): (Vec<&str>, Vec<&str>) =
(AUSTEN.split(' ').collect(), out.split(' ').collect());
assert_eq!((src[3], src[7]), (new[3], new[7])); assert_ne!(src[14], new[14]); assert_eq!(lang::detect([out.as_str()]), None);
let sc = Scrambler::new(1);
sc.set_lang_budget(0);
let out: String = sc.scramble(&chars, &protect, false).into_iter().collect();
assert!(!out.contains(" the ") && !out.contains(" of "), "{out}");
}
#[test]
fn heading_words_stay_where_the_importer_reads_them() {
for (src, changed) in [
(
"A single man of large fortune; four or five thousand a year.",
&[1, 2, 4, 5, 6, 8, 9][..],
),
(
"Some of his servants are to be in the house by the end of next week.",
&[3, 9, 12, 14, 15][..],
),
] {
let out = scramble(src);
let (a, b): (Vec<&str>, Vec<&str>) = (src.split(' ').collect(), out.split(' ').collect());
for &i in changed {
assert_ne!(a[i], b[i], "{src} -> {out}");
}
}
assert!(scramble("March 3, 1812").starts_with("March "));
let chars: Vec<char> = "told me all about it".chars().collect();
let protect = vec![false; chars.len()];
let long = Context {
words: Some(20),
continuation: true,
..Context::default()
};
let out: String = Scrambler::new(1)
.scramble_in(&chars, &protect, long)
.into_iter()
.collect();
let words: Vec<&str> = out.split(' ').collect();
assert!(words[2] != "all" && words[3] != "about", "{out}");
}
#[test]
fn a_paragraph_starts_at_its_first_letter() {
assert!(scramble("\nFür Dr. Bucephalus").starts_with("\nFür "));
assert!(scramble("\n\n\nby Jane Austen").starts_with("\n\n\nby "));
let out = scramble("Bucephalus\nFür Dr. Bucephalus");
assert!(!out.contains("\nFür"), "{out}");
}
#[test]
fn ornaments_and_line_breaks() {
assert_eq!(scramble("* * *"), "* * *");
assert_eq!(scramble("o O o"), "o O o");
assert_eq!(scramble("x x x"), "x x x");
let out = scramble("Kapitel 1\nDer Anfang");
assert!(out.starts_with("Kapitel 1\nDer "));
assert_ne!(out, "Kapitel 1\nDer Anfang");
assert_eq!(scramble(""), "");
}
#[test]
fn protected_chars_stay() {
let chars: Vec<char> = "Bucephalus Alexanders".chars().collect();
let mut protect = vec![false; chars.len()];
protect[..10].fill(true);
let out: String = Scrambler::new(3)
.scramble(&chars, &protect, false)
.into_iter()
.collect();
assert!(out.starts_with("Bucephalus "));
assert!(!out.ends_with("Alexanders"));
}
#[test]
fn a_replacement_is_never_a_word_the_importer_reads() {
for seed in 0..400u64 {
let sc = Scrambler::new(seed);
for key in [
"tie", "vet", "one", "adn", "hr", "ix", "iv", "mai", "xi", "hes",
] {
let new: String = sc.replacement(key).into_iter().collect();
assert!(!reserved(&new) || reserved(key), "{key} -> {new}");
}
}
}
#[test]
fn other_scripts_and_odd_chars_do_not_panic() {
for text in [
"Привет, мир",
"Καλημέρα κόσμε",
"日本語のテキスト",
"한국어 텍스트",
"Dž İ ı ʼn ẞ ffi",
"naïve café",
"\u{feff}\u{200b}",
] {
let out = scramble(text);
assert_eq!(out.chars().count(), text.chars().count(), "{text}");
}
assert_ne!(scramble("Привет"), "Привет");
}