# encoding: utf-8
require 'spec_helper'
require 'ostruct'
require 'timeout'
require 'rspec/support/differ'

module RSpec
  module Support
    describe Differ do
      describe '#diff' do
        let(:differ) { RSpec::Support::Differ.new }

        it "outputs unified diff of two strings" do
          expected = "foo\nzap\nbar\nthis\nis\nsoo\nvery\nvery\nequal\ninsert\na\nanother\nline\n"
          actual   = "foo\nbar\nzap\nthis\nis\nsoo\nvery\nvery\nequal\ninsert\na\nline\n"

          expected_diff = <<-'EOD'


@@ -1,6 +1,6 @@
 foo
-zap
 bar
+zap
 this
 is
 soo
@@ -9,6 +9,5 @@
 equal
 insert
 a
-another
 line
EOD

          diff = differ.diff(actual, expected)
          expect(diff).to eql(expected_diff)
        end

        if String.method_defined?(:encoding)
          it "returns an empty string if strings are not multiline" do
            expected = "Tu avec carte {count} item has".encode('UTF-16LE')
            actual   = "Tu avec carté {count} itém has".encode('UTF-16LE')

            expect(differ.diff(actual, expected)).to be_empty
          end

          it 'copes with encoded strings' do
            expected = "Tu avec carte {count} item has\n".encode('UTF-16LE')
            actual   = "Tu avec carté {count} itém has\n".encode('UTF-16LE')
            expect(differ.diff(actual, expected)).to eql(<<-EOD.encode('UTF-16LE'))

@@ -1,2 +1,2 @@
-Tu avec carte {count} item has
+Tu avec carté {count} itém has
EOD
          end

          it 'handles differently encoded strings that are compatible' do
            expected = "abc\n".encode('us-ascii')
            actual   = "강인철\n".encode('UTF-8')
            expect(differ.diff(actual, expected)).to eql "\n@@ -1,2 +1,2 @@\n-abc\n+강인철\n"
          end

          it 'uses the default external encoding when the two strings have incompatible encodings' do
            expected = "Tu avec carte {count} item has\n"
            actual   = "Tu avec carté {count} itém has\n".encode('UTF-16LE')
            expect(differ.diff(actual, expected)).to eq("\n@@ -1,2 +1,2 @@\n-Tu avec carte {count} item has\n+Tu avec carté {count} itém has\n")
            expect(differ.diff(actual, expected).encoding).to eq(Encoding.default_external)
          end

          it 'handles any encoding error that occurs with a helpful error message' do
            expect(RSpec::Support::HunkGenerator).to receive(:new).
              and_raise(Encoding::CompatibilityError)
            expected = "Tu avec carte {count} item has\n".encode('us-ascii')
            actual   = "Tu avec carté {count} itém has\n"
            diff = differ.diff(actual, expected)
            expect(diff).to match(/Could not produce a diff/)
            expect(diff).to match(/actual string \(UTF-8\)/)
            expect(diff).to match(/expected string \(US-ASCII\)/)
          end
        end

        it "outputs unified diff message of two objects" do
          animal_class = Class.new do
            def initialize(name, species)
              @name, @species = name, species
            end

            def inspect
              <<-EOA
<Animal
  name=#{@name},
  species=#{@species}
>
              EOA
            end
          end

          expected = animal_class.new "bob", "giraffe"
          actual   = animal_class.new "bob", "tortoise"

          expected_diff = <<'EOD'

@@ -1,5 +1,5 @@
 <Animal
   name=bob,
-  species=tortoise
+  species=giraffe
 >
EOD

          diff = differ.diff(expected,actual)
          expect(diff).to eq expected_diff
        end

        it "outputs unified diff message of two arrays" do
          expected = [ :foo, 'bar', :baz, 'quux', :metasyntactic, 'variable', :delta, 'charlie', :width, 'quite wide' ]
          actual   = [ :foo, 'bar', :baz, 'quux', :metasyntactic, 'variable', :delta, 'tango'  , :width, 'very wide'  ]

          expected_diff = <<'EOD'


@@ -5,7 +5,7 @@
  :metasyntactic,
  "variable",
  :delta,
- "tango",
+ "charlie",
  :width,
- "very wide"]
+ "quite wide"]
EOD

          diff = differ.diff(expected,actual)
          expect(diff).to eq expected_diff
        end

        it 'outputs a unified diff message for an array which flatten recurses' do
          klass = Class.new do
            def to_ary; [self]; end
            def inspect; "<BrokenObject>"; end
          end
          obj = klass.new

          diff = ''
          Timeout::timeout(1) do
            diff = differ.diff [obj], []
          end

          expect(diff).to eq <<-EOD

@@ -1,2 +1,2 @@
-[]
+[<BrokenObject>]
EOD
        end

        it "outputs unified diff message of two hashes" do
          expected = { :foo => 'bar', :baz => 'quux', :metasyntactic => 'variable', :delta => 'charlie', :width =>'quite wide' }
          actual   = { :foo => 'bar', :metasyntactic => 'variable', :delta => 'charlotte', :width =>'quite wide' }

          expected_diff = <<'EOD'

@@ -1,4 +1,5 @@
-:delta => "charlotte",
+:baz => "quux",
+:delta => "charlie",
 :foo => "bar",
 :metasyntactic => "variable",
 :width => "quite wide",
EOD

          diff = differ.diff(expected,actual)
          expect(diff).to eq expected_diff
        end

        it 'outputs unified diff message of two hashes with differing encoding' do
          expected_diff = %Q{
@@ -1,2 +1,2 @@
-"a" => "a",
#{ (RUBY_VERSION.to_f > 1.8) ?  %Q{+"ö" => "ö"} : '+"\303\266" => "\303\266"' },
}

          diff = differ.diff({'ö' => 'ö'}, {'a' => 'a'})
          expect(diff).to eq expected_diff
        end

        it 'outputs unified diff message of two hashes with encoding different to key encoding' do
          expected_diff = %Q{
@@ -1,2 +1,2 @@
-:a => "a",
#{ (RUBY_VERSION.to_f > 1.8) ?  %Q{+\"한글\" => \"한글2\"} : '+"\355\225\234\352\270\200" => "\355\225\234\352\270\2002"' },
}

          diff = differ.diff({ "한글" => "한글2"}, { :a => "a"})
          expect(diff).to eq expected_diff
        end

        it "outputs unified diff message of two hashes with object keys" do
          expected_diff = %Q{
@@ -1,2 +1,2 @@
-["a", "c"] => "b",
+["d", "c"] => "b",
}

          diff = differ.diff({ ['d','c'] => 'b'}, { ['a','c'] => 'b' })
          expect(diff).to eq expected_diff
        end

        it "outputs unified diff of multi line strings" do
          expected = "this is:\n  one string"
          actual   = "this is:\n  another string"

          expected_diff = <<'EOD'

@@ -1,3 +1,3 @@
 this is:
-  another string
+  one string
EOD

          diff = differ.diff(expected,actual)
          expect(diff).to eq expected_diff
        end

        it "splits items with newlines" do
          expected_diff = <<'EOD'

@@ -1,3 +1 @@
-a\nb
-c\nd
EOD

          diff = differ.diff [], ["a\nb", "c\nd"]
          expect(diff).to eql expected_diff
        end

        it "shows inner arrays on a single line" do
          expected_diff = <<'EOD'

@@ -1,3 +1 @@
-a\nb
-["c\nd"]
EOD

          diff = differ.diff [], ["a\nb", ["c\nd"]]
          expect(diff).to eql expected_diff
        end

        it "returns an empty string if no expected or actual" do
          diff = differ.diff nil, nil

          expect(diff).to be_empty
        end

        it "returns an empty string if expected is Numeric" do
          diff = differ.diff 1, "2"

          expect(diff).to be_empty
        end

        it "returns an empty string if actual is Numeric" do
          diff = differ.diff "1", 2

          expect(diff).to be_empty
        end

        it "returns an empty string if expected or actual are procs" do
          diff = differ.diff lambda {}, lambda {}

          expect(diff).to be_empty
        end

        it "returns a String if no diff is returned" do
          diff = differ.diff 1, 2
          expect(diff).to be_a(String)
        end

        it "returns a String if a diff is performed" do
          diff = differ.diff "a\n", "b\n"
          expect(diff).to be_a(String)
        end

        context "with :object_preparer option set" do
          let(:differ) do
            RSpec::Support::Differ.new(:object_preparer => lambda { |s| s.to_s.reverse })
          end

          it "uses the output of object_preparer for diffing" do
            expected = :foo
            actual = :poo

            expected_diff = dedent(<<-EOS)
              |
              |@@ -1,2 +1,2 @@
              |-"oop"
              |+"oof"
              |
            EOS

            diff = differ.diff(expected, actual)
            expect(diff).to eq expected_diff
          end
        end

        context "with :color option set" do
          let(:differ) { RSpec::Support::Differ.new(:color => true) }

          it "outputs colored diffs" do
            expected = "foo bar baz\n"
            actual = "foo bang baz\n"
            expected_diff = "\e[0m\n\e[0m\e[34m@@ -1,2 +1,2 @@\n\e[0m\e[31m-foo bang baz\n\e[0m\e[32m+foo bar baz\n\e[0m"

            diff = differ.diff(expected,actual)
            expect(diff).to eq expected_diff
          end
        end
      end
    end
  end
end