require 'spec.custom_assertions'

local v = require 'semver'

local function checkVersion(ver, major, minor, patch, prerelease, build)
  assert.equal(major, ver.major)
  assert.equal(minor, ver.minor)
  assert.equal(patch, ver.patch)
  assert.equal(prerelease, ver.prerelease)
  assert.equal(build, ver.build)
end

describe('semver', function()

  describe('creation', function()

    describe('from numbers', function()
      it('parses 3 numbers correctly', function()
        checkVersion(v(1,2,3), 1,2,3)
      end)

      it('parses 2 numbers correctly', function()
        checkVersion(v(1,2), 1,2,0)
      end)

      it('parses 1 number correctly', function()
        checkVersion(v(1), 1,0,0)
      end)

      it('parses prereleases', function()
        checkVersion(v(1,2,3,"alpha"), 1,2,3,"alpha")
      end)
      it('parses builds', function()
        checkVersion(v(1,2,3,nil,"build.1"), 1,2,3,nil,"build.1")
      end)
      it('parses prereleases + builds', function()
        checkVersion(v(1,2,3,"alpha","build.1"), 1,2,3,"alpha","build.1")
      end)
    end)

    describe("from strings", function()
      it("1.2.3", function()
        checkVersion( v'1.2.3', 1,2,3)
      end)
      it("10.20.123", function()
        checkVersion( v'10.20.123', 10,20,123)
      end)
      it("2.0", function()
        checkVersion( v'2.0', 2,0,0)
      end)
      it("5", function()
        checkVersion( v'5', 5,0,0)
      end)
      it("1.2.3-alpha", function()
        checkVersion( v'1.2.3-alpha', 1,2,3,'alpha' )
      end)
      it("1.2.3+build.15", function()
        checkVersion( v'1.2.3+build.15', 1,2,3,nil,'build.15' )
      end)
      it("1.2.3-rc1+build.15", function()
        checkVersion( v'1.2.3-rc1+build.15', 1,2,3,'rc1','build.15' )
      end)
    end)

    describe('errors', function()
      it('no parameters are passed', function()
        assert.error(function() v() end)
      end)
      it('negative numbers', function()
        assert.error(function() v(-1, 0, 0) end)
        assert.error(function() v( 0,-1, 0) end)
        assert.error(function() v( 0, 0,-1) end)
      end)
      it('floats', function()
        assert.error(function() v(.1, 0, 0) end)
        assert.error(function() v( 0,.1, 0) end)
        assert.error(function() v( 0, 0,.1) end)
      end)
      it('empty string', function()
        assert.error(function() v("") end)
      end)
      it('garbage at the beginning of the string', function()
        assert.error(function() v("foobar1.2.3") end)
      end)
      it('garbage at the end of the string', function()
        assert.error(function() v("1.2.3foobar") end)
      end)
      it('a non-string or number is passed', function()
        assert.error(function() v({}) end)
      end)
      it('an invalid prerelease', function()
        assert.error(function() v'1.2.3-%?' end)
      end)
      it('an invalid build', function()
        assert.error(function() v'1.2.3+%?' end)
      end)
    end)
  end)

  describe("tostring", function()
    it("works with major, minor and patch", function()
      assert.equal("1.2.3", tostring(v(1,2,3)))
    end)

    it("works with a prerelease", function()
      assert.equal("1.2.3-beta", tostring(v(1,2,3,'beta')))
    end)
    it("works with a build", function()
      assert.equal("1.2.3+foobar", tostring(v(1,2,3,nil,'foobar')))
    end)
    it("works with a prerelease and a build", function()
      assert.equal("1.2.3-alpha+foobar", tostring(v'1.2.3-alpha+foobar'))
    end)
  end)

  describe("==", function()
    it("is true when major, minor and patch are the same", function()
      assert.equal(v'1.0.0', v'1.0.0')
    end)
    it("is false when major, minor, patch or prerelease are not the same", function()
      assert.not_equal(v'1.0.0', v'1.0.1')
      assert.not_equal(v'1.0.0', v'1.1.0')
      assert.not_equal(v'1.0.0', v'2.0.0')
      assert.not_equal(v'1.0.0', v'1.0.0-alpha')
    end)
    it("ignores builds", function()
      assert.equal(v'1.2.3', v'1.2.3+1')
      assert.equal(v'1.2.3+1', v'1.2.3+2')
    end)
  end)

  describe("<", function()
    it("compares correctly when major, minor and patch are equal", function()
      assert.not_less(v'1.0.0', v'1.0.0')
      assert.not_greater(v'1.0.0', v'1.0.0')
      assert.equal(v'1.0.0', v'1.0.0')
    end)

    it("#focus prioritizes major over minor", function()
      --assert.less(v'1.100.10', v'2.0.0')
      assert.not_greater(v'1.100.10', v'2.0.0')
      --assert.greater(v'2', v'1')
      --assert.not_less(v'2', v'1')
    end)
    it("when equal major, compares minor", function()
      assert.less(v'1.2.0', v'1.3.0')
      assert.not_greater(v'1.2.0', v'1.3.0')
      assert.greater(v'1.1', v'1.0')
      assert.not_less(v'1.1', v'1.0')
    end)
    it("when equal major and minor, compares patch", function()
      assert.less(v'0.0.1', v'0.0.10')
      assert.not_greater(v'0.0.1', v'0.0.10')
      assert.greater(v'0.0.2', v'0.0.1')
      assert.not_less(v'0.0.2', v'0.0.1')
    end)
    describe("prereleases", function()
      it("compares correctly when major, minor, patch and prerelease are equal", function()
        assert.not_less(v'1.0.0-1', v'1.0.0-1')
        assert.not_greater(v'1.0.0-1', v'1.0.0-1')
        assert.equal(v'1.0.0-1', v'1.0.0-1')
      end)
      it("prioritizes non-prereleases over prereleases", function()
        assert.less(v'1.0.0-rc1', v'1.0.0')
        assert.not_greater(v'1.0.0-rc1', v'1.0.0')
        assert.greater(v'1.2.3', v'1.2.3-alpha')
        assert.not_less(v'1.2.3', v'1.2.3-alpha')
      end)
      it("compares identifiers with only digits numerically", function()
        assert.less(v'1.0.0-1', v'1.0.0-2')
        assert.not_greater(v'1.0.0-1', v'1.0.0-2')
        assert.greater(v'1.0.0-2', v'1.0.0-1')
        assert.not_less(v'1.0.0-2', v'1.0.0-1')
      end)
      it("compares idendifiers with letters or dashes lexiconumerically", function()
        assert.less(v'1.0.0-alpha', v'1.0.0-beta')
        assert.not_greater(v'1.0.0-alpha', v'1.0.0-beta')
        assert.less(v'1.0.0-alpha-10', v'1.0.0-alpha-2')
        assert.not_greater(v'1.0.0-alpha-10', v'1.0.0-alpha-2')
        assert.greater(v'1.0.0-beta', v'1.0.0-alpha')
        assert.not_less(v'1.0.0-beta', v'1.0.0-alpha')
        assert.greater(v'1.0.0-alpha-2', v'1.0.0-alpha-10')
        assert.not_less(v'1.0.0-alpha-2', v'1.0.0-alpha-10')
      end)
      it("prioritizes lexiconumericals over numbers", function()
        assert.less(v'1.0.0-1', v'1.0.0-alpha')
        assert.not_greater(v'1.0.0-1', v'1.0.0-alpha')
        assert.less(v'1.0.0-2', v'1.0.0-1asdf')
        assert.not_greater(v'1.0.0-2', v'1.0.0-1asdf')
        assert.greater(v'1.0.0-alpha', v'1.0.0-1')
        assert.not_less(v'1.0.0-alpha', v'1.0.0-1')
        assert.greater(v'1.0.0-1asdf', v'1.0.0-2')
        assert.not_less(v'1.0.0-1asdf', v'1.0.0-2')
      end)
      it("splits identifiers by colons, comparing every pair individually", function()
        assert.less(v'1.0.0-alpha', v'1.0.0-alpha.1')
        assert.not_greater(v'1.0.0-alpha', v'1.0.0-alpha.1')
        assert.less(v'1.0.0-alpha.1', v'1.0.0-beta.2')
        assert.not_greater(v'1.0.0-alpha.1', v'1.0.0-beta.2')
        assert.less(v'1.0.0-beta.2', v'1.0.0-beta.11')
        assert.not_greater(v'1.0.0-beta.2', v'1.0.0-beta.11')
        assert.less(v'1.0.0-beta.11', v'1.0.0-rc.1')
        assert.not_greater(v'1.0.0-beta.11', v'1.0.0-rc.1')

        assert.greater(v'1.0.0-alpha.1', v'1.0.0-alpha')
        assert.not_less(v'1.0.0-alpha.1', v'1.0.0-alpha')
        assert.greater(v'1.0.0-beta.2', v'1.0.0-alpha.1')
        assert.not_less(v'1.0.0-beta.2', v'1.0.0-alpha.1')
        assert.greater(v'1.0.0-beta.11', v'1.0.0-beta.2')
        assert.not_less(v'1.0.0-beta.11', v'1.0.0-beta.2')
        assert.greater(v'1.0.0-rc.1', v'1.0.0-beta.11')
        assert.not_less(v'1.0.0-rc.1', v'1.0.0-beta.11')
      end)
    end)
    describe("builds", function()
      it("false independently of the build", function()
        assert.not_less(v'1.0.0+build1', v'1.0.0')
        assert.not_less(v'1.0.0+build1', v'1.0.0+build3')
        assert.not_less(v'1.0.0-beta+build1', v'1.0.0-beta+build2')
        assert.not_greater(v'1.0.0+build1', v'1.0.0')
        assert.not_greater(v'1.0.0+build1', v'1.0.0+build3')
        assert.not_greater(v'1.0.0-beta+build1', v'1.0.0-beta+build2')
      end)
    end)
  end)

  describe("nextPatch", function()
    it("increases the patch number by 1", function()
      assert.equal(v'1.0.1', v'1.0.0':nextPatch())
    end)
    it("resets prerelease and build", function()
      assert.equal(v'1.0.1', v'1.0.0-a+b':nextPatch())
    end)
  end)

  describe("nextMinor", function()
    it("increases the minor number by 1", function()
      assert.equal(v'1.2.0', v'1.1.0':nextMinor())
    end)
    it("resets the patch number, prerelease and build", function()
      assert.equal(v'1.2.0', v'1.1.7-a+b':nextMinor())
    end)
  end)

  describe("nextMajor", function()
    it("increases the major number by 1", function()
      assert.equal(v'2.0.0', v'1.0.0':nextMajor())
    end)
    it("resets the minor, patch, prerelease and build", function()
      assert.equal(v'2.0.0', v'1.2.3-a+b':nextMajor())
    end)
  end)

  describe("^", function()
    it("true for self", function()
      assert.is_true(v(1,2,3) ^ v(1,2,3))
      assert.is_true(v(0,1,0) ^ v(0,1,0))
      assert.is_true(v("0.1.1+build0") ^ v(0,1,1))
    end)
    it("different major versions mean it's always unsafe", function()
      assert.is_false(v(2,0,0) ^ v(3,0,0))
      assert.is_false(v(2,0,0) ^ v(1,0,0))
    end)

    it("patches, prereleases and builds are ignored", function()
      assert.is_true(v(1,2,3) ^ v(1,2,0))
      assert.is_true(v(1,2,3) ^ v(1,2,5))
      assert.is_true(v(1,2,3,'foo') ^ v(1,2,3))
      assert.is_true(v(1,2,3,nil,'bar') ^ v(1,2,3))
    end)

    it("is safe to upgrade to a newer minor version", function()
      assert.is_true(v(1,2,0) ^ v(1,5,0))
    end)
    it("is unsafe to downgrade to an earlier minor version", function()
      assert.is_false(v(1,5,0) ^ v(1,2,0))
    end)

    it("is unsafe to upgrade any pre-1.0 versions", function()
      assert.is_false(v("0.0.1-alpha") ^ v(0,0,1))
      assert.is_false(v("0.0.1") ^ v(0,0,2))
      assert.is_false(v("0.0.1+build0") ^ v(0,0,2))
      assert.is_false(v(0,0,1) ^ v(0,1,0))
      assert.is_false(v(0,0,1) ^ v(1,0,0))
      assert.is_false(v(0,0,1) ^ v("1.0.0-alpha"))
    end)
  end)

  describe("_VERSION", function()
    it("can be extracted from the lib", function()
      local x = v._VERSION
      assert.equal('table', type(x))
    end)
  end)

end)
