summaryrefslogtreecommitdiffstats
path: root/src/tests
diff options
context:
space:
mode:
authorDan Davison <dandavison7@gmail.com>2020-05-09 12:32:01 -0400
committerDan Davison <dandavison7@gmail.com>2020-05-16 09:04:38 -0400
commit79979c272d6daa876ffb3dbad7a6550c22155c8c (patch)
tree30baf82bd2892ffb73bfd3f6dab6ce21b6ae6ebd /src/tests
parent21537d6eb8f07d6c1e71c757fb8f0fd583449219 (diff)
Hunk highlighting test and refactoring
- New command-line option --syntax highlight to select which category of hunk lines to be syntax highlighted. --highlight-removed is now deprecated. - New hunk highlighting test tests syntax highlighting and background colors - Refactor: construction of style modifiers for config struct - Refactor: compute minus and plus foreground style sections separately - Refactor: foreground style logic - Refactor: rearrange style modifier construction for config - Refactor: change variable name: => highlight_minus_lines - Refactor: clean up --color-only config implementation
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/ansi_test_utils.rs111
-rw-r--r--src/tests/integration_test_utils.rs68
-rw-r--r--src/tests/mod.rs4
-rw-r--r--src/tests/test_example_diffs.rs575
-rw-r--r--src/tests/test_hunk_highlighting.rs160
5 files changed, 918 insertions, 0 deletions
diff --git a/src/tests/ansi_test_utils.rs b/src/tests/ansi_test_utils.rs
new file mode 100644
index 00000000..ad3c1084
--- /dev/null
+++ b/src/tests/ansi_test_utils.rs
@@ -0,0 +1,111 @@
+#[cfg(test)]
+pub mod ansi_test_utils {
+ use ansi_parser::{self, AnsiParser};
+ use console::strip_ansi_codes;
+ use itertools::Itertools;
+ use syntect::highlighting::StyleModifier;
+
+ use crate::bat::assets::HighlightingAssets;
+ use crate::cli;
+ use crate::config::{ColorLayer::*, Config};
+ use crate::delta::State;
+ use crate::paint;
+
+ pub fn is_syntax_highlighted(line: &str) -> bool {
+ line.ansi_parse()
+ .filter(|tok| match tok {
+ ansi_parser::Output::TextBlock(s) => string_has_some_foreground_color(s),
+ _ => false,
+ })
+ .next()
+ .is_some()
+ }
+
+ pub fn line_has_background_color(line: &str, state: &State, config: &Config) -> bool {
+ line.ansi_parse()
+ .filter(|tok| match tok {
+ ansi_parser::Output::TextBlock(s) => string_has_background_color(s, state, config),
+ _ => false,
+ })
+ .next()
+ .is_some()
+ }
+
+ pub fn line_has_no_background_color(line: &str) -> bool {
+ line.ansi_parse()
+ .filter(|tok| match tok {
+ ansi_parser::Output::TextBlock(s) => string_has_some_background_color(s),
+ _ => false,
+ })
+ .next()
+ .is_none()
+ }
+
+ fn string_has_background_color(string: &str, state: &State, config: &Config) -> bool {
+ let painted = paint::paint_text_background(
+ "",
+ config
+ .get_color(state, Background)
+ .unwrap_or_else(|| panic!("state {:?} does not have a background color", state)),
+ true,
+ );
+ let ansi_sequence = painted.trim_end_matches(paint::ANSI_SGR_RESET);
+ string.starts_with(ansi_sequence)
+ }
+
+ fn string_has_some_background_color(s: &str) -> bool {
+ s.starts_with("\x1b[48;")
+ }
+
+ fn string_has_some_foreground_color(s: &str) -> bool {
+ s.starts_with("\x1b[38;")
+ }
+
+ pub fn assert_line_has_expected_ansi_sequences(line: &str, expected: &Vec<(&str, &str)>) {
+ assert_eq!(line.ansi_parse().count(), expected.len());
+ for ((expected_ansi_sequence, _), ref token) in expected.iter().zip_eq(line.ansi_parse()) {
+ match token {
+ ansi_parser::Output::TextBlock(s) => {
+ assert!(s.starts_with(*expected_ansi_sequence));
+ }
+ ansi_parser::Output::Escape(_) => {
+ assert_eq!(expected_ansi_sequence, &paint::ANSI_SGR_RESET);
+ }
+ }
+ }
+ }
+
+ pub fn assert_has_color_other_than_plus_color(string: &str, options: &cli::Opt) {
+ let (string_without_any_color, string_with_plus_color_only) =
+ get_color_variants(string, &options);
+ assert_ne!(string, string_without_any_color);
+ assert_ne!(string, string_with_plus_color_only);
+ }
+
+ pub fn assert_has_plus_color_only(string: &str, options: &cli::Opt) {
+ let (string_without_any_color, string_with_plus_color_only) =
+ get_color_variants(string, &options);
+ assert_ne!(string, string_without_any_color);
+ assert_eq!(string, string_with_plus_color_only);
+ }
+
+ pub fn get_color_variants(string: &str, options: &cli::Opt) -> (String, String) {
+ let assets = HighlightingAssets::new();
+ let config = cli::process_command_line_arguments(&assets, &options);
+
+ let string_without_any_color = strip_ansi_codes(string).to_string();
+ let string_with_plus_color_only = paint_text(
+ &string_without_any_color,
+ config.plus_style_modifier,
+ &config,
+ );
+ (string_without_any_color, string_with_plus_color_only)
+ }
+
+ fn paint_text(input: &str, style_modifier: StyleModifier, config: &Config) -> String {
+ let mut output = String::new();
+ let style = config.no_style.apply(style_modifier);
+ paint::paint_text(&input, style, &mut output, config.true_color);
+ output
+ }
+}
diff --git a/src/tests/integration_test_utils.rs b/src/tests/integration_test_utils.rs
new file mode 100644
index 00000000..2e941757
--- /dev/null
+++ b/src/tests/integration_test_utils.rs
@@ -0,0 +1,68 @@
+#[cfg(test)]
+pub mod integration_test_utils {
+ use bytelines::ByteLines;
+ use console::strip_ansi_codes;
+ use std::io::BufReader;
+
+ use crate::bat::assets::HighlightingAssets;
+ use crate::cli;
+ use crate::delta::delta;
+
+ pub fn get_command_line_options() -> cli::Opt {
+ cli::Opt {
+ light: false,
+ dark: false,
+ minus_color: None,
+ minus_emph_color: None,
+ minus_foreground_color: None,
+ minus_emph_foreground_color: None,
+ plus_color: None,
+ plus_emph_color: None,
+ plus_foreground_color: None,
+ plus_emph_foreground_color: None,
+ color_only: false,
+ keep_plus_minus_markers: false,
+ theme: None,
+ lines_to_be_syntax_highlighted: "0+".to_string(),
+ highlight_minus_lines: false,
+ commit_style: cli::SectionStyle::Plain,
+ commit_color: "Yellow".to_string(),
+ file_style: cli::SectionStyle::Underline,
+ file_color: "Blue".to_string(),
+ hunk_style: cli::SectionStyle::Box,
+ hunk_color: "blue".to_string(),
+ true_color: "always".to_string(),
+ width: Some("variable".to_string()),
+ paging_mode: "auto".to_string(),
+ tab_width: 4,
+ show_background_colors: false,
+ list_languages: false,
+ list_theme_names: false,
+ list_themes: false,
+ max_line_distance: 0.3,
+ }
+ }
+
+ pub fn get_line_of_code_from_delta(input: &str, options: &cli::Opt) -> String {
+ let output = run_delta(&input, &options);
+ let line_of_code = output.lines().nth(12).unwrap();
+ assert!(strip_ansi_codes(line_of_code) == " class X:");
+ line_of_code.to_string()
+ }
+
+ pub fn run_delta(input: &str, options: &cli::Opt) -> String {
+ let mut writer: Vec<u8> = Vec::new();
+
+ let assets = HighlightingAssets::new();
+ let config = cli::process_command_line_arguments(&assets, &options);
+
+ delta(
+ ByteLines::new(BufReader::new(input.as_bytes())),
+ &config,
+ &assets,
+ &mut writer,
+ )
+ .unwrap();
+ String::from_utf8(writer).unwrap()
+ }
+}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
new file mode 100644
index 00000000..afa09977
--- /dev/null
+++ b/src/tests/mod.rs
@@ -0,0 +1,4 @@
+pub mod ansi_test_utils;
+pub mod integration_test_utils;
+pub mod test_example_diffs;
+pub mod test_hunk_highlighting;
diff --git a/src/tests/test_example_diffs.rs b/src/tests/test_example_diffs.rs
new file mode 100644
index 00000000..2d5faa22
--- /dev/null
+++ b/src/tests/test_example_diffs.rs
@@ -0,0 +1,575 @@
+#[cfg(test)]
+mod tests {
+ use crate::tests::ansi_test_utils::ansi_test_utils;
+ use crate::tests::integration_test_utils::integration_test_utils;
+ use console::strip_ansi_codes;
+
+ #[test]
+ fn test_added_file() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ ADDED_FILE_INPUT,
+ &options,
+ ))
+ .to_string();
+ assert!(output.contains("\nadded: a.py\n"));
+ if false {
+ // TODO: hline width
+ assert_eq!(output, ADDED_FILE_EXPECTED_OUTPUT);
+ }
+ }
+
+ #[test]
+ #[ignore] // #128
+ fn test_added_empty_file() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ ADDED_EMPTY_FILE,
+ &options,
+ ))
+ .to_string();
+ assert!(output.contains("\nadded: file\n"));
+ }
+
+ #[test]
+ fn test_added_file_directory_path_containing_space() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ ADDED_FILES_DIRECTORY_PATH_CONTAINING_SPACE,
+ &options,
+ ))
+ .to_string();
+ assert!(output.contains("\nadded: with space/file1\n"));
+ assert!(output.contains("\nadded: nospace/file2\n"));
+ }
+
+ #[test]
+ fn test_renamed_file() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ RENAMED_FILE_INPUT,
+ &options,
+ ))
+ .to_string();
+ assert!(output.contains("\nrenamed: a.py ⟶ b.py\n"));
+ }
+
+ #[test]
+ fn test_recognized_file_type() {
+ // In addition to the background color, the code has language syntax highlighting.
+ let options = integration_test_utils::get_command_line_options();
+ let input = ADDED_FILE_INPUT;
+ let output = integration_test_utils::get_line_of_code_from_delta(&input, &options);
+ ansi_test_utils::assert_has_color_other_than_plus_color(&output, &options);
+ }
+
+ #[test]
+ fn test_unrecognized_file_type_with_theme() {
+ // In addition to the background color, the code has the foreground color using the default
+ // .txt syntax under the theme.
+ let options = integration_test_utils::get_command_line_options();
+ let input = ADDED_FILE_INPUT.replace("a.py", "a");
+ let output = integration_test_utils::get_line_of_code_from_delta(&input, &options);
+ ansi_test_utils::assert_has_color_other_than_plus_color(&output, &options);
+ }
+
+ #[test]
+ fn test_unrecognized_file_type_no_theme() {
+ // The code has the background color only. (Since there is no theme, the code has no
+ // foreground ansi color codes.)
+ let mut options = integration_test_utils::get_command_line_options();
+ options.theme = Some("none".to_string());
+ let input = ADDED_FILE_INPUT.replace("a.py", "a");
+ let output = integration_test_utils::get_line_of_code_from_delta(&input, &options);
+ ansi_test_utils::assert_has_plus_color_only(&output, &options);
+ }
+
+ #[test]
+ fn test_diff_unified_two_files() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ DIFF_UNIFIED_TWO_FILES,
+ &options,
+ ))
+ .to_string();
+ let mut lines = output.split('\n');
+
+ // Header
+ assert_eq!(lines.nth(1).unwrap(), "comparing: one.rs ⟶ src/two.rs");
+ // Line
+ assert_eq!(lines.nth(2).unwrap(), "5");
+ // Change
+ assert_eq!(lines.nth(2).unwrap(), " println!(\"Hello ruster\");");
+ // Next chunk
+ assert_eq!(lines.nth(2).unwrap(), "43");
+ // Unchanged in second chunk
+ assert_eq!(lines.nth(2).unwrap(), " Unchanged");
+ }
+
+ #[test]
+ fn test_diff_unified_two_directories() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ DIFF_UNIFIED_TWO_DIRECTORIES,
+ &options,
+ ))
+ .to_string();
+ let mut lines = output.split('\n');
+
+ // Header
+ assert_eq!(
+ lines.nth(1).unwrap(),
+ "comparing: a/different ⟶ b/different"
+ );
+ // Line number
+ assert_eq!(lines.nth(2).unwrap(), "1");
+ // Change
+ assert_eq!(lines.nth(2).unwrap(), " This is different from b");
+ // File uniqueness
+ assert_eq!(lines.nth(2).unwrap(), "Only in a/: just_a");
+ // FileMeta divider
+ assert!(lines.next().unwrap().starts_with("───────"));
+ // Next hunk
+ assert_eq!(
+ lines.nth(4).unwrap(),
+ "comparing: a/more_difference ⟶ b/more_difference"
+ );
+ }
+
+ #[test]
+ #[ignore] // Ideally, delta would make this test pass. See #121.
+ fn test_delta_ignores_non_diff_input() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ NOT_A_DIFF_OUTPUT,
+ &options,
+ ))
+ .to_string();
+ assert_eq!(output, NOT_A_DIFF_OUTPUT.to_owned() + "\n");
+ }
+
+ #[test]
+ fn test_delta_paints_diff_when_there_is_unrecognized_initial_content() {
+ for input in vec![
+ DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_1,
+ DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_2,
+ ] {
+ let mut options = integration_test_utils::get_command_line_options();
+ options.color_only = true;
+ let output = integration_test_utils::run_delta(input, &options);
+ assert_eq!(strip_ansi_codes(&output).to_string(), input.to_owned());
+ assert_ne!(output, input.to_owned());
+ }
+ }
+
+ #[test]
+ fn test_diff_with_merge_conflict_is_not_truncated() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = integration_test_utils::run_delta(DIFF_WITH_MERGE_CONFLICT, &options);
+ // TODO: The + in the first column is being removed.
+ assert!(strip_ansi_codes(&output).contains("+>>>>>>> Stashed changes"));
+ assert_eq!(output.split('\n').count(), 46);
+ }
+
+ #[test]
+ fn test_diff_with_merge_conflict_is_passed_on_unchanged_under_color_only() {
+ let mut options = integration_test_utils::get_command_line_options();
+ options.color_only = true;
+ let output = integration_test_utils::run_delta(DIFF_WITH_MERGE_CONFLICT, &options);
+ assert_eq!(
+ strip_ansi_codes(&output).to_string(),
+ DIFF_WITH_MERGE_CONFLICT.to_owned()
+ );
+ }
+
+ #[test]
+ fn test_submodule_contains_untracked_content() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ SUBMODULE_CONTAINS_UNTRACKED_CONTENT_INPUT,
+ &options,
+ ))
+ .to_string();
+ assert!(output.contains("\nSubmodule x/y/z contains untracked content\n"));
+ }
+
+ #[test]
+ fn test_triple_dash_at_beginning_of_line_in_code() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ TRIPLE_DASH_AT_BEGINNING_OF_LINE_IN_CODE,
+ &options,
+ ))
+ .to_string();
+ assert!(
+ output.contains(" -- instance (Category p, Category q) => Category (p ∧ q) where\n")
+ );
+ }
+
+ #[test]
+ fn test_binary_files_differ() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(
+ BINARY_FILES_DIFFER,
+ &options,
+ ))
+ .to_string();
+ assert!(output.contains("Binary files /dev/null and b/foo differ\n"));
+ }
+
+ #[test]
+ fn test_diff_in_diff() {
+ let options = integration_test_utils::get_command_line_options();
+ let output = strip_ansi_codes(&integration_test_utils::run_delta(DIFF_IN_DIFF, &options))
+ .to_string();
+ assert!(output.contains("\n ---\n"));
+ assert!(output.contains("\n Subject: [PATCH] Init\n"));
+ }
+
+ const DIFF_IN_DIFF: &str = "\
+diff --git a/0001-Init.patch b/0001-Init.patch
+deleted file mode 100644
+index 5e35a67..0000000
+--- a/0001-Init.patch
++++ /dev/null
+@@ -1,22 +0,0 @@
+-From d3a8fe3e62be67484729c19e9d8db071f8b1d60c Mon Sep 17 00:00:00 2001
+-From: Maximilian Bosch <maximilian@mbosch.me>
+-Date: Sat, 28 Dec 2019 15:51:48 +0100
+-Subject: [PATCH] Init
+-
+----
+- README.md | 3 +++
+- 1 file changed, 3 insertions(+)
+- create mode 100644 README.md
+-
+-diff --git a/README.md b/README.md
+-new file mode 100644
+-index 0000000..2e6ca05
+---- /dev/null
+-+++ b/README.md
+-@@ -0,0 +1,3 @@
+-+# Test
+-+
+-+abc
+---
+-2.23.1
+-
+diff --git a/README.md b/README.md
+index 2e6ca05..8ae0569 100644
+--- a/README.md
++++ b/README.md
+@@ -1,3 +1 @@
+ # Test
+-
+-abc
+";
+
+ const ADDED_FILE_INPUT: &str = "\
+commit d28dc1ac57e53432567ec5bf19ad49ff90f0f7a5
+Author: Dan Davison <dandavison7@gmail.com>
+Date: Thu Jul 11 10:41:11 2019 -0400
+
+ .
+
+diff --git a/a.py b/a.py
+new file mode 100644
+index 0000000..8c55b7d
+--- /dev/null
++++ b/a.py
+@@ -0,0 +1,3 @@
++# hello
++class X:
++ pass";
+
+ const ADDED_FILE_EXPECTED_OUTPUT: &str = "\
+commit d28dc1ac57e53432567ec5bf19ad49ff90f0f7a5
+Author: Dan Davison <dandavison7@gmail.com>
+Date: Thu Jul 11 10:41:11 2019 -0400
+
+ .
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+added: a.py
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+────────────────────────────────────────────────────────────────────────────────
+
+────────────────────────────────────────────────────────────────────────────────
+ # hello
+ class X:
+ pass
+";
+
+ const ADDED_EMPTY_FILE: &str = "
+commit c0a18433cb6e0ca8f796bfae9e31d95b06b91597 (HEAD -> master)
+Author: Dan Davison <dandavison7@gmail.com>
+Date: Sun Apr 26 16:32:58 2020 -0400
+
+ Initial commit
+
+diff --git a/file b/file
+new file mode 100644
+index 0000000..e69de29
+";
+
+ const ADDED_FILES_DIRECTORY_PATH_CONTAINING_SPACE: &str = "
+commit 654e180c8d5329904d584c44b661149f68bd2911 (HEAD -> master)
+Author: Dan Davison <dandavison7@gmail.com>
+Date: Sun Apr 26 16:30:58 2020 -0400
+
+ Initial commit
+
+diff --git a/nospace/file2 b/nospace/file2
+new file mode 100644
+index 0000000..af1b8ae
+--- /dev/null
++++ b/nospace/file2
+@@ -0,0 +1 @@
++file2 contents
+diff --git a/with space/file1 b/with space/file1
+new file mode 100644
+index 0000000..84d55c5
+--- /dev/null
++++ b/with space/file1
+@@ -0,0 +1 @@
++file1 contents
+";
+
+ const RENAMED_FILE_INPUT: &str = "\
+commit 1281650789680f1009dfff2497d5ccfbe7b96526
+Author: Dan Davison <dandavison7@gmail.com>
+Date: Wed Jul 17 20:40:23 2019 -0400
+
+ rename
+
+diff --git a/a.py b/b.py
+similarity index 100%
+rename from a.py
+rename to b.py
+";
+
+ const DIFF_UNIFIED_TWO_FILES: &str = "\
+--- one.rs 2019-11-20 06:16:08.000000000 +0100
++++ src/two.rs 2019-11-18 18:41:16.000000000 +0100
+@@ -5,3 +5,3 @@
+ println!(\"Hello world\");
+-println!(\"Hello rust\");
++println!(\"Hello ruster\");
+
+@@ -43,6 +43,6 @@
+ // Some more changes
+-Change one
+ Unchanged
++Change two
+ Unchanged
+-Change three
++Change four
+ Unchanged
+";
+
+ const DIFF_UNIFIED_TWO_DIRECTORIES: &str = "\
+diff -u a/different b/different
+--- a/different 2019-11-20 06:47:56.000000000 +0100
++++ b/different 2019-11-20 06:47:56.000000000 +0100
+@@ -1,3 +1,3 @@
+ A simple file for testing
+ the diff command in unified mode
+-This is different from b
++This is different from a
+Only in a/: just_a
+Only in b/: just_b
+--- a/more_difference 2019-11-20 06:47:56.000000000 +0100
++++ b/more_difference 2019-11-20 06:47:56.000000000 +0100
+@@ -1,3 +1,3 @@
+ Another different file
+ with a name that start with 'm' making it come after the 'Only in'
+-This is different from b
++This is different from a
+";
+
+ const NOT_A_DIFF_OUTPUT: &str = "\
+Hello world
+This is a regular file that contains:
+--- some/file/here 06:47:56.000000000 +0100
++++ some/file/there 06:47:56.000000000 +0100
+ Some text here
+-Some text with a minus
++Some text with a plus
+";
+
+ const SUBMODULE_CONTAINS_UNTRACKED_CONTENT_INPUT: &str = "\
+--- a
++++ b
+@@ -2,3 +2,4 @@
+ x
+ y
+ z
+-a
++b
+ z
+ y
+ x
+Submodule x/y/z contains untracked content
+";
+
+ const TRIPLE_DASH_AT_BEGINNING_OF_LINE_IN_CODE: &str = "\
+commit d481eaa8a249c6daecb05a97e8af1b926b0c02be
+Author: FirstName LastName <me@gmail.com>
+Date: Thu Feb 6 14:02:49 2020 -0500
+
+ Reorganize
+
+diff --git a/src/Category/Coproduct.hs b/src/Category/Coproduct.hs
+deleted file mode 100644
+index ba28bfd..0000000
+--- a/src/Category/Coproduct.hs
++++ /dev/null
+@@ -1,18 +0,0 @@
+-{-# LANGUAGE InstanceSigs #-}
+-module Category.Coproduct where
+-
+-import Prelude hiding ((.), id)
+-
+-import Control.Category
+-
+-import Category.Hacks
+-
+--- data (p ∨ q) (a :: (k, k)) (b :: (k, k)) where
+--- (:<:) :: p a b -> (∨) p q '(a, c) '(b, d)
+--- (:>:) :: q c d -> (∨) p q '(a, c) '(b, d)
+---
+--- instance (Category p, Category q) => Category (p ∧ q) where
+--- (p1 :×: q1) . (p2 :×: q2) = (p1 . p2) :×: (q1 . q2)
+---
+--- id :: forall a. (p ∧ q) a a
+--- id | IsTup <- isTup @a = id :×: id
+";
+
+ const BINARY_FILES_DIFFER: &str = "
+commit ad023698217b086f1bef934be62b4523c95f64d9 (HEAD -> master)
+Author: Dan Davison <dandavison7@gmail.com>
+Date: Wed Feb 12 08:05:53 2020 -0600
+
+ .
+
+diff --git a/foo b/foo
+new file mode 100644
+index 0000000..b572921
+Binary files /dev/null and b/foo differ
+";
+
+ // git --no-pager show -p --cc --format= --numstat --stat
+ // #121
+ const DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_1: &str = "
+1 5 src/delta.rs
+ src/delta.rs | 6 +-----
+ 1 file changed, 1 insertion(+), 5 deletions(-)
+
+diff --git a/src/delta.rs b/src/delta.rs
+index da10d2b..39cff42 100644
+--- a/src/delta.rs
++++ b/src/delta.rs
+@@ -67,11 +67,6 @@ where
+ let source = detect_source(&mut lines_peekable);
+
+ for raw_line in lines_peekable {
+- if source == Source::Unknown {
+- writeln!(painter.writer, \"{}\", raw_line)?;
+- continue;
+- }
+-
+ let line = strip_ansi_codes(&raw_line).to_string();
+ if line.starts_with(\"commit \") {
+ painter.paint_buffered_lines();
+@@ -674,6 +669,7 @@ mod tests {
+ }
+
+ #[test]
++ #[ignore] // Ideally, delta would make this test pass.
+ fn test_delta_ignores_non_diff_input() {
+ let options = get_command_line_options();
+ let output = strip_ansi_codes(&run_delta(NOT_A_DIFF_OUTPUT, &options)).to_string();
+";
+
+ // git stash show --stat --patch
+ // #100
+ const DIFF_WITH_UNRECOGNIZED_PRECEDING_MATERIAL_2: &str = "
+ src/cli.rs | 2 ++
+ src/config.rs | 4 +++-
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/src/cli.rs b/src/cli.rs
+index bd5f1d5..55ba315 100644
+--- a/src/cli.rs
++++ b/src/cli.rs
+@@ -286,6 +286,8 @@ pub fn process_command_line_arguments<'a>(
+ }
+ };
+
++ println!(\"true_color is {}\", true_color);
++
+ config::get_config(
+ opt,
+ &assets.syntax_set,
+diff --git a/src/config.rs b/src/config.rs
+index cba6064..ba1a4de 100644
+--- a/src/config.rs
++++ b/src/config.rs
+@@ -181,7 +181,9 @@ fn color_from_rgb_or_ansi_code(s: &str) -> Color {
+ process::exit(1);
+ };
+ if s.starts_with(\"#\") {
+- Color::from_str(s).unwrap_or_else(|_| die())
++ let col = Color::from_str(s).unwrap_or_else(|_| die());
++ println!(\"{} => {} {} {} {}\", s, col.r, col.g, col.b, col.a);
++ col
+ } else {
+ s.parse::<u8>()
+ .ok()
+";
+
+ const DIFF_WITH_MERGE_CONFLICT: &str = r#"
+diff --cc Makefile
+index 759070d,3daf9eb..0000000
+--- a/Makefile
++++ b/Makefile
+@@@ -4,13 -4,16 +4,37 @@@ build
+ lint:
+ cargo clippy
+
+++<<<<<<< Updated upstream
+ +test: unit-test end-to-end-test
+ +
+ +unit-test:
+ + cargo test
+ +
+ +end-to-end-test: build
+ + bash -c "diff -u <(git log -p) <(git log -p | target/release/delta --color-only | perl -pe 's/\e\[[0-9;]*m//g')"
+++||||||| constructed merge base
+++test:
+++ cargo test
+++ bash -c "diff -u <(git log -p) \
+++ <(git log -p | delta --width variable \
+++ --tabs 0 \
+++ --retain-plus-minus-markers \
+++ --commit-style plain \
+++ --file-style plain \
+++ --hunk-style plain \
+++ | ansifilter)"
+++=======
++ test:
++ cargo test --release
++ bash -c "diff -u <(git log -p) \
++ <(git log -p | target/release/delta --width variable \
++ --tabs 0 \
++ --retain-plus-minus-markers \
++ --commit-style plain \
++ --file-style plain \
++ --hunk-style plain \
++ | ansifilter)" > /dev/null
+++>>>>>>> Stashed changes
+
+ release:
+ @make -f release.Makefile release
+"#;
+}
diff --git a/src/tests/test_hunk_highlighting.rs b/src/tests/test_hunk_highlighting.rs
new file mode 100644
index 00000000..5edcf3a0
--- /dev/null
+++ b/src/tests/test_hunk_highlighting.rs
@@ -0,0 +1,160 @@
+#[cfg(test)]
+mod tests {
+ use itertools::Itertools;
+
+ use crate::bat::assets::HighlightingAssets;
+ use crate::cli;
+ use crate::config::ColorLayer::*;
+ use crate::delta::State;
+ use crate::paint;
+ use crate::tests::ansi_test_utils::ansi_test_utils;
+ use crate::tests::integration_test_utils::integration_test_utils;
+
+ #[test]
+ fn test_hunk_highlighting() {
+ let mut options = integration_test_utils::get_command_line_options();
+ options.max_line_distance = 1.0;
+ for lines_to_be_syntax_highlighted in
+ vec!["none", "all", "+", "0", "0+", "-", "-+", "-0", "-0+"]
+ {
+ options.lines_to_be_syntax_highlighted = lines_to_be_syntax_highlighted.to_string();
+ _do_hunk_color_test(&options);
+ }
+ }
+
+ fn _do_hunk_color_test(options: &cli::Opt) {
+ let output = integration_test_utils::run_delta(
+ DIFF_YIELDING_ALL_HUNK_LINE_COLOR_CATEGORIES,
+ options,
+ );
+
+ let lines = output.trim().split("\n").skip(4);
+
+ // Hack: The config has been built once already in run_delta
+ let assets = HighlightingAssets::new();
+ let config = cli::process_command_line_arguments(&assets, &options);
+
+ let minus =
+ paint::paint_text_background("", config.minus_style_modifier.background.unwrap(), true)
+ .trim_end_matches(paint::ANSI_SGR_RESET)
+ .to_string();
+ let minus_emph = paint::paint_text_background(
+ "",
+ config.minus_emph_style_modifier.background.unwrap(),