require 'spec_helper'

describe Semantic::Version do
  before(:each) do
    @test_versions = [
      '1.0.0',
      '12.45.182',
      '0.0.1-pre.1',
      '1.0.1-pre.5+build.123.5',
      '1.1.1+123',
      '0.0.0+hello',
      '1.2.3-1'
    ]

    @bad_versions = [
      'a.b.c',
      '1.a.3',
      'a.3.4',
      '5.2.a',
      'pre3-1.5.3',
      "I am not a valid semver\n0.0.0\nbut I still pass"
    ]
  end

  context "parsing" do
    it "parses valid SemVer versions" do
      @test_versions.each do |v|
        expect { Semantic::Version.new v }.to_not raise_error()
      end
    end

    it "raises an error on invalid versions" do
      @bad_versions.each do |v|
        expect { Semantic::Version.new v }.to raise_error()
      end
    end

    it "stores parsed versions in member variables" do
      v1 = Semantic::Version.new '1.5.9'
      v1.major.should == 1
      v1.minor.should == 5
      v1.patch.should == 9
      v1.pre.should be_nil
      v1.build.should be_nil

      v2 = Semantic::Version.new '0.0.1-pre.1'
      v2.major.should == 0
      v2.minor.should == 0
      v2.patch.should == 1
      v2.pre.should == 'pre.1'
      v2.build.should be_nil

      v3 = Semantic::Version.new '1.0.1-pre.5+build.123.5'
      v3.major.should == 1
      v3.minor.should == 0
      v3.patch.should == 1
      v3.pre.should == 'pre.5'
      v3.build.should == 'build.123.5'

      v4 = Semantic::Version.new '0.0.0+hello'
      v4.major.should == 0
      v4.minor.should == 0
      v4.patch.should == 0
      v4.pre.should be_nil
      v4.build.should == 'hello'
    end
    
    it "provides round-trip fidelity for an empty build parameter" do
      v = Semantic::Version.new("1.2.3")
      v.build = ""
      expect(Semantic::Version.new(v.to_s).build).to eq(v.build)
    end

    it "provides round-trip fidelity for a nil build parameter" do
      v = Semantic::Version.new("1.2.3+build")
      v.build = nil
      expect(Semantic::Version.new(v.to_s).build).to eq(v.build)
    end
  end

  context "comparisons" do
    before(:each) do
      # These three are all semantically equivalent, according to the spec.
      @v1_5_9_pre_1 = Semantic::Version.new '1.5.9-pre.1'
      @v1_5_9_pre_1_build_5127 = Semantic::Version.new '1.5.9-pre.1+build.5127'
      @v1_5_9_pre_1_build_4352 = Semantic::Version.new '1.5.9-pre.1+build.4352'

      @v1_5_9 = Semantic::Version.new '1.5.9'
      @v1_6_0 = Semantic::Version.new '1.6.0'
    end

    it "determines sort order" do
      # The second parameter here can be a string, so we want to ensure that this kind of comparison works also.
      (@v1_5_9_pre_1 <=> @v1_5_9_pre_1.to_s).should == 0

      (@v1_5_9_pre_1 <=> @v1_5_9_pre_1_build_5127).should == 0
      (@v1_5_9_pre_1 <=> @v1_5_9).should == -1
      (@v1_5_9_pre_1_build_5127 <=> @v1_5_9).should == -1

      @v1_5_9_pre_1_build_5127.build.should == 'build.5127'

      (@v1_5_9 <=> @v1_5_9).should == 0

      (@v1_5_9 <=> @v1_6_0).should == -1
      (@v1_6_0 <=> @v1_5_9).should == 1
      (@v1_6_0 <=> @v1_5_9_pre_1).should == 1
      (@v1_5_9_pre_1 <=> @v1_6_0).should == -1

      [@v1_5_9_pre_1, @v1_5_9_pre_1_build_5127, @v1_5_9, @v1_6_0]
        .reverse
        .sort
        .should == [@v1_5_9_pre_1, @v1_5_9_pre_1_build_5127, @v1_5_9, @v1_6_0]
    end

    it "determines whether it is greater than another instance" do
      # These should be equal, since "Build metadata SHOULD be ignored when determining version precedence".
      # (SemVer 2.0.0-rc.2, paragraph 10 - http://www.semver.org)
      @v1_5_9_pre_1.should_not > @v1_5_9_pre_1_build_5127
      @v1_5_9_pre_1.should_not < @v1_5_9_pre_1_build_5127

      @v1_6_0.should > @v1_5_9
      @v1_5_9.should_not > @v1_6_0
      @v1_5_9.should > @v1_5_9_pre_1_build_5127
      @v1_5_9.should > @v1_5_9_pre_1
    end

    it "determines whether it is less than another instance" do
      @v1_5_9_pre_1.should_not < @v1_5_9_pre_1_build_5127
      @v1_5_9_pre_1_build_5127.should_not < @v1_5_9_pre_1
      @v1_5_9_pre_1.should < @v1_5_9
      @v1_5_9_pre_1.should < @v1_6_0
      @v1_5_9_pre_1_build_5127.should < @v1_6_0
      @v1_5_9.should < @v1_6_0
    end

    it "determines whether it is greater than or equal to another instance" do
      @v1_5_9_pre_1.should >= @v1_5_9_pre_1
      @v1_5_9_pre_1.should >= @v1_5_9_pre_1_build_5127
      @v1_5_9_pre_1_build_5127.should >= @v1_5_9_pre_1
      @v1_5_9.should >= @v1_5_9_pre_1
      @v1_6_0.should >= @v1_5_9
      @v1_5_9_pre_1_build_5127.should_not >= @v1_6_0
    end

    it "determines whether it is less than or equal to another instance" do
      @v1_5_9_pre_1.should <= @v1_5_9_pre_1_build_5127
      @v1_6_0.should_not <= @v1_5_9
      @v1_5_9_pre_1_build_5127.should <= @v1_5_9_pre_1_build_5127
      @v1_5_9.should_not <= @v1_5_9_pre_1
    end

    it "determines whether it is semantically equal to another instance" do
      @v1_5_9_pre_1.should == @v1_5_9_pre_1.dup
      @v1_5_9_pre_1_build_5127.should == @v1_5_9_pre_1_build_5127.dup

      # "Semantically equal" is the keyword here; these are by definition not "equal" (different build), but should be treated as
      # equal according to the spec.
      @v1_5_9_pre_1_build_4352.should == @v1_5_9_pre_1_build_5127
      @v1_5_9_pre_1_build_4352.should == @v1_5_9_pre_1
    end

    it "determines whether it satisfies >= style specifications" do
      @v1_6_0.satisfies('>=1.6.0').should be true
      @v1_6_0.satisfies('<=1.6.0').should be true
      @v1_6_0.satisfies('>=1.5.0').should be true
      @v1_6_0.satisfies('<=1.5.0').should_not be true

      # partial / non-semver numbers after comparator are extremely common in
      # version specifications in the wild

      @v1_6_0.satisfies('>1.5').should be true
      @v1_6_0.satisfies('<1').should_not be true
    end

    it "determines whether it satisfies * style specifications" do
      @v1_6_0.satisfies('1.*').should be true
      @v1_6_0.satisfies('1.6.*').should be true
      @v1_6_0.satisfies('2.*').should_not be true
      @v1_6_0.satisfies('1.5.*').should_not be true
    end

    it "determines whether it satisfies ~ style specifications" do
      @v1_6_0.satisfies('~1.6').should be true
      @v1_5_9_pre_1.satisfies('~1.5').should be true
      @v1_6_0.satisfies('~1.5').should_not be true
    end

  end

  context "type coercions" do
    it "converts to a string" do
      @test_versions.each do |v|
        Semantic::Version.new(v).to_s.should == v
      end
    end

    it "converts to an array" do
      Semantic::Version.new('1.0.0').to_a.should == [1, 0, 0, nil, nil]
      Semantic::Version.new('6.1.4-pre.5').to_a.should == [6, 1, 4, 'pre.5', nil]
      Semantic::Version.new('91.6.0+build.17').to_a.should == [91, 6, 0, nil, 'build.17']
      Semantic::Version.new('0.1.5-pre.7+build191').to_a.should == [0, 1, 5, 'pre.7', 'build191']
    end

    it "converts to a hash" do
      Semantic::Version.new('1.0.0').to_h.should == { major: 1, minor: 0, patch: 0, pre: nil, build: nil }
      Semantic::Version.new('6.1.4-pre.5').to_h.should == { major: 6, minor: 1, patch: 4, pre: 'pre.5', build: nil }
      Semantic::Version.new('91.6.0+build.17').to_h.should == { major: 91, minor: 6, patch: 0, pre: nil, build: 'build.17' }
      Semantic::Version.new('0.1.5-pre.7+build191').to_h.should == { major: 0, minor: 1, patch: 5, pre: 'pre.7', build: 'build191' }
    end

    it "aliases conversion methods" do
      v = Semantic::Version.new('0.0.0')
      [:to_hash, :to_array, :to_string].each { |sym| v.should respond_to(sym) }
    end
  end

  describe '#major!' do
    subject { described_class.new('1.2.3-pre1+build2') }

    context 'changing the major term' do
      it 'changes the major version and resets the others' do
        subject.major!.should == '2.0.0'
      end
    end
  end

  describe '#minor' do
    subject { described_class.new('1.2.3-pre1+build2') }

    context 'changing the minor term' do
      it 'changes minor term and resets patch, pre and build' do
        subject.minor!.should == '1.3.0'
      end
    end
  end

  describe '#patch' do
    subject { described_class.new('1.2.3-pre1+build2') }

    context 'changing the patch term' do
      it 'changes the patch term and resets the pre and build' do
        subject.patch!.should == '1.2.4'
      end
    end
  end
end