var currentTest = null; Tinytest.add("package-version-parser - validatePackageName", function (test) { var badName = function (packageName, messageExpect) { test.throws(function () { try { PackageVersion.validatePackageName(packageName); } catch (e) { if (! e.versionParserError) { test.fail(e.message); } throw e; } }, messageExpect); }; PackageVersion.validatePackageName('a'); PackageVersion.validatePackageName('a-b'); PackageVersion.validatePackageName('a.b'); badName("$foo", /can only contain/); badName("", /must contain a lowercase/); badName("foo$bar", /can only contain/); badName("Foo", /can only contain/); badName("a b", /can only contain/); badName(".foo", /may not begin with a dot/); badName("foo.", /may not end with a dot/); badName("foo..bar", /not contain two consecutive dots/); badName("-x", /not begin with a hyphen/); badName("--x", /not begin with a hyphen/); badName("0.0", /must contain/); badName(":a", /start or end with a colon/); badName("a:", /start or end with a colon/); // these are ok PackageVersion.validatePackageName('x-'); PackageVersion.validatePackageName('x--y'); PackageVersion.validatePackageName('x--'); }); Tinytest.add("package-version-parser - parse", function (test) { test.isTrue(new PackageVersion("1.2.3") instanceof PackageVersion); var throws = function (v, re) { test.throws(function () { new PackageVersion(v); }, re); }; var formatPV = function (pv) { pv = JSON.parse(JSON.stringify(pv)); delete pv._semverParsed; return (JSON.stringify(pv) .replace(/,(?="prerelease"|"raw")/g, ',\n') .replace(/,/g, ', ') .replace(/"(\w+)":/g, '$1: ') .replace("{", "{\n") .replace("}", "\n}")); }; var equal = function (pv1, pv2) { test.equal(formatPV(pv1), formatPV(pv2)); }; equal(new PackageVersion("1.2.3-rc.5_1+12345"), { major: 1, minor: 2, patch: 3, prerelease: ["rc", 5], wrapNum: 1, build: ["12345"], raw: "1.2.3-rc.5_1+12345", version: "1.2.3-rc.5_1", semver: "1.2.3-rc.5+12345" }); equal(PackageVersion.parse("1.2.3-rc.5_1+12345"), { major: 1, minor: 2, patch: 3, prerelease: ["rc", 5], wrapNum: 1, build: ["12345"], raw: "1.2.3-rc.5_1+12345", version: "1.2.3-rc.5_1", semver: "1.2.3-rc.5+12345" }); equal(new PackageVersion("1.2.3"), { major: 1, minor: 2, patch: 3, prerelease: [], wrapNum: 0, build: [], raw: "1.2.3", version: "1.2.3", semver: "1.2.3" }); throws("1.2", /must look like semver/); throws("1", /must look like semver/); equal(new PackageVersion("1.0.0-rc.1"), { major: 1, minor: 0, patch: 0, prerelease: ["rc", 1], wrapNum: 0, build: [], raw: "1.0.0-rc.1", version: "1.0.0-rc.1", semver: "1.0.0-rc.1" }); throws("1.0.0-.", /must look like semver/); throws("1.0.0-rc.", /must look like semver/); throws("1.0.0-01", /must look like semver/); equal(new PackageVersion("1.2.3-1-1"), { major: 1, minor: 2, patch: 3, prerelease: ["1-1"], wrapNum: 0, build: [], raw: "1.2.3-1-1", version: "1.2.3-1-1", semver: "1.2.3-1-1" }); equal(new PackageVersion("1.2.3_4"), { major: 1, minor: 2, patch: 3, prerelease: [], wrapNum: 4, build: [], raw: "1.2.3_4", version: "1.2.3_4", semver: "1.2.3" }); throws("1.2.3_4_5", /have two _/); throws("1.2.3_0", /must not have a leading zero/); throws("1.2.3_01", /must not have a leading zero/); throws("1.2.3_a", /must contain only digits/); // (prerelease must go *before* the wrap num) throws("1.2.3_a-rc.1", /must contain only digits/); equal(new PackageVersion("1.2.3-4_5"), { major: 1, minor: 2, patch: 3, prerelease: [4], wrapNum: 5, build: [], raw: "1.2.3-4_5", version: "1.2.3-4_5", semver: "1.2.3-4" }); equal(new PackageVersion("1.2.3-rc.1_7+8.9-10.c"), { major: 1, minor: 2, patch: 3, prerelease: ["rc", 1], wrapNum: 7, build: ["8", "9-10", "c"], raw: "1.2.3-rc.1_7+8.9-10.c", version: "1.2.3-rc.1_7", semver: "1.2.3-rc.1+8.9-10.c" }); throws("1.2.3+4+5", /have two \+/); equal(new PackageVersion("1.2.3+x"), { major: 1, minor: 2, patch: 3, prerelease: [], wrapNum: 0, build: ["x"], raw: "1.2.3+x", version: "1.2.3", semver: "1.2.3+x" }); throws("1.2.3+x_1", /must look like semver/); equal(new PackageVersion("1.2.3_1+x"), { major: 1, minor: 2, patch: 3, prerelease: [], wrapNum: 1, build: ["x"], raw: "1.2.3_1+x", version: "1.2.3_1", semver: "1.2.3+x" }); throws("v1.0.0", /must look like semver/); }); Tinytest.add("package-version-parser - constraints - parsePackageConstraint", function (test) { test.isTrue(PackageVersion.parsePackageConstraint("foo") instanceof PackageVersion.PackageConstraint); test.equal(PackageVersion.parsePackageConstraint("foo@1.2.3"), { package: "foo", constraintString: "1.2.3", versionConstraint: { raw: "1.2.3", alternatives: [{type: "compatible-with", versionString: "1.2.3"}] } }); test.equal(PackageVersion.parsePackageConstraint("foo"), { package: "foo", constraintString: "", versionConstraint: { raw: "", alternatives: [{type: "any-reasonable", versionString: null}] } }); test.equal(PackageVersion.parsePackageConstraint("foo@1.0.0 || =2.0.0"), { package: "foo", constraintString: "1.0.0 || =2.0.0", versionConstraint: { raw: "1.0.0 || =2.0.0", alternatives: [{type: "compatible-with", versionString: "1.0.0"}, {type: "exactly", versionString: "2.0.0"}] } }); test.equal(new PackageVersion.PackageConstraint("foo@1.0.0 || =2.0.0"), PackageVersion.parsePackageConstraint("foo@1.0.0 || =2.0.0")); test.equal(PackageVersion.parsePackageConstraint("foo", null), PackageVersion.parsePackageConstraint("foo")); test.equal(PackageVersion.parsePackageConstraint("foo", ""), PackageVersion.parsePackageConstraint("foo")); test.equal(PackageVersion.parsePackageConstraint("foo", "1.0.0"), PackageVersion.parsePackageConstraint("foo@1.0.0")); test.equal(PackageVersion.parsePackageConstraint("foo", "=1.0.0"), PackageVersion.parsePackageConstraint("foo@=1.0.0")); test.throws(function () { PackageVersion.parsePackageConstraint("", "1.0.0"); }); test.throws(function () { PackageVersion.parsePackageConstraint("foo@1.0.0", "1.0.0"); }); test.throws(function () { PackageVersion.parsePackageConstraint("foo@", "1.0.0"); }); test.throws(function () { PackageVersion.parsePackageConstraint("foo@"); }, /leave off the @/); test.throws(function () { PackageVersion.parsePackageConstraint("foo@", ""); }); test.throws(function () { PackageVersion.parsePackageConstraint("a@b@c"); }); test.throws(function () { PackageVersion.parsePackageConstraint("foo@||"); }, /Invalid constraint string: \|\|/); test.throws(function () { PackageVersion.parsePackageConstraint("foo@=||="); }, /Empty string is not a valid version/); test.equal(new PackageVersion.PackageConstraint( "foo", new PackageVersion.VersionConstraint(null)), { package: "foo", constraintString: "", versionConstraint: { raw: "", alternatives: [{type: "any-reasonable", versionString: null}] } }); test.equal(PackageVersion.parsePackageConstraint( "foo", PackageVersion.parseVersionConstraint("1.0.0 || =2.0.0")), { package: "foo", constraintString: "1.0.0 || =2.0.0", versionConstraint: { raw: "1.0.0 || =2.0.0", alternatives: [{type: "compatible-with", versionString: "1.0.0"}, {type: "exactly", versionString: "2.0.0"}] } }); test.equal(PackageVersion.parseVersionConstraint(null), {raw: "", alternatives: [{type: "any-reasonable", versionString: null}]}); test.equal(PackageVersion.parseVersionConstraint(""), {raw: "", alternatives: [{type: "any-reasonable", versionString: null}]}); test.equal(PackageVersion.parsePackageConstraint("foo").toString(), "foo"); test.equal(PackageVersion.parsePackageConstraint("foo", null).toString(), "foo"); test.equal(PackageVersion.parsePackageConstraint("foo@1.0.0").toString(), "foo@1.0.0"); test.equal(PackageVersion.parsePackageConstraint( "foo@=1.0.0 || 2.0.0").toString(), "foo@=1.0.0 || 2.0.0"); }); var t = function (pConstraintString, expected, descr) { var constraintString = pConstraintString.replace(/^.*?(@|$)/, ''); var versionConstraint = { raw: constraintString, alternatives: expected.alternatives }; currentTest.equal( PackageVersion.parsePackageConstraint(pConstraintString), { package: expected.package, constraintString: constraintString, versionConstraint: { raw: constraintString, alternatives: expected.alternatives } }, descr); }; var FAIL = function (versionString, errorExpect) { currentTest.throws(function () { PackageVersion.parsePackageConstraint(versionString); }, errorExpect); }; Tinytest.add("package-version-parser - constraints - any-reasonable", function (test) { currentTest = test; t("foo", { package: "foo", alternatives: [{ versionString: null, type: "any-reasonable" } ]}); t("foo-1234", { package: "foo-1234", alternatives: [{ versionString: null, type: "any-reasonable" } ]}); FAIL("bad_name"); }); Tinytest.add("package-version-parser - constraints - compatible version, compatible-with", function (test) { currentTest = test; t("foo@1.2.3", { package: "foo", alternatives: [{ versionString: "1.2.3", type: "compatible-with" } ]}); t("foo-1233@1.2.3", { package: "foo-1233", alternatives: [{ versionString: "1.2.3", type: "compatible-with" } ]}); t("foo-bar@3.2.1", { package: "foo-bar", alternatives: [{ versionString: "3.2.1", type: "compatible-with" } ]}); FAIL("42@0.2.0"); FAIL("foo@1.2.3.4"); FAIL("foo@1.4"); FAIL("foo@1"); FAIL("foo@"); FAIL("foo@@"); FAIL("foo@x.y.z"); FAIL("foo@<1.2"); FAIL("foo<1.2"); FAIL("foo@1.2.3_abc"); FAIL("foo@1.2.3+1234_1"); FAIL("foo@1.2.3_1-rc1"); FAIL("foo-1233@1.2.3_0", /must not have a leading zero/); FAIL("foo-1233@1.2.3_a", /must contain only digits/); FAIL("foo-1233@1.2.3_", /wrap number must follow/); FAIL("foo-1233@1.2.3_0123"); t("foo@1.2.3_1", { package: "foo", alternatives: [{ versionString: "1.2.3_1", type: "compatible-with" } ]}); t("foo-bar@3.2.1-rc0_123", { package: "foo-bar", alternatives: [{ versionString: "3.2.1-rc0_123", type: "compatible-with" } ]}); t("foo-1233@1.2.3_5+1234", { package: "foo-1233", alternatives: [{ versionString: "1.2.3_5+1234", type: "compatible-with" } ]}); t("foo", { package: "foo", alternatives: [{ versionString: null, type: "any-reasonable" } ]}); }); Tinytest.add("package-version-parser - constraints - compatible version, exactly", function (test) { currentTest = test; t("foo@=1.2.3", { package: "foo", alternatives: [ { versionString: "1.2.3", type: "exactly" } ]}); t("foo-bar@=3.2.1", { package: "foo-bar", alternatives: [{ versionString: "3.2.1", type: "exactly" } ]}); t("foo@=1.2.3_1", { package: "foo", alternatives: [{ versionString: "1.2.3_1", type: "exactly" } ]}); t("foo-bar@=3.2.1_34", { package: "foo-bar", alternatives: [{ versionString: "3.2.1_34", type: "exactly" } ]}); FAIL("42@=0.2.0"); FAIL("foo@=1.2.3.4"); FAIL("foo@=1.4"); FAIL("foo@=1"); FAIL("foo@@="); FAIL("foo@=@"); FAIL("foo@=x.y.z"); FAIL("foo@=<1.2"); FAIL("foo@<=1.2"); FAIL("foo<=1.2"); FAIL("foo@=1.2.3_rc0"); // We no longer support @>=. FAIL("foo@>=1.2.3"); FAIL("foo-bar@>=3.2.1"); FAIL("42@>=0.2.0"); FAIL("foo@>=1.2.3.4"); FAIL("foo@>=1.4"); FAIL("foo@>=1"); FAIL("foo@@>="); FAIL("foo@>=@"); FAIL("foo@>=x.y.z"); FAIL("foo@=>12.3.11"); }); Tinytest.add("package-version-parser - constraints - or", function (test) { currentTest = test; t("foo@1.0.0 || 2.0.0 || 3.0.0 || =4.0.0-rc1", { package: "foo", alternatives: [{ versionString: "1.0.0", type: "compatible-with"}, { versionString: "2.0.0", type: "compatible-with"}, { versionString: "3.0.0", type: "compatible-with"}, { versionString: "4.0.0-rc1", type: "exactly"}] }); t("foo@1.0.0|| 2.0.0||3.0.0 || =4.0.0-rc1", { package: "foo", alternatives: [{ versionString: "1.0.0", type: "compatible-with"}, { versionString: "2.0.0", type: "compatible-with"}, { versionString: "3.0.0", type: "compatible-with"}, { versionString: "4.0.0-rc1", type: "exactly"}] }); t("foo-bar@=3.2.1 || 1.0.0", { package: "foo-bar", alternatives: [{ versionString: "3.2.1", type: "exactly"}, { versionString: "1.0.0", type: "compatible-with"}] }); t("foo@=1.2.3_1 || 1.2.4", { package: "foo", alternatives: [{ versionString: "1.2.3_1", type: "exactly"}, { versionString: "1.2.4", type: "compatible-with"}] }); t("foo-bar@=3.2.1_34 || =3.2.1-rc1", { package: "foo-bar", alternatives: [{ versionString: "3.2.1_34", type: "exactly"}, { versionString: "3.2.1-rc1", type: "exactly"}] }); FAIL("foo@1.0.0 1.0.0"); FAIL("foo@1.0.0 | 1.0.0"); FAIL("foo || bar"); FAIL("foo@1.0.0-rc|1.0.0"); // This is the current implementation, but is arguably not great. FAIL("foo@1.0.0 "); // trailing space }); Tinytest.add( "package-version-parser - less than, compare, version magnitude", function (test) { var compare = function (v1, v2, expected) { if (expected === '<') { test.isTrue(PackageVersion.lessThan(v1, v2)); test.isTrue(PackageVersion.versionMagnitude(v1) < PackageVersion.versionMagnitude(v2)); test.isTrue(PackageVersion.compare(v1, v2) < 0); } else if (expected === '=') { test.isFalse(PackageVersion.lessThan(v1, v2)); test.isFalse(PackageVersion.lessThan(v2, v1)); test.isTrue(PackageVersion.versionMagnitude(v1) === PackageVersion.versionMagnitude(v2)); test.isTrue(PackageVersion.compare(v1, v2) === 0); } else if (expected === '>') { test.isTrue(PackageVersion.lessThan(v2, v1)); test.isTrue(PackageVersion.versionMagnitude(v1) > PackageVersion.versionMagnitude(v2)); test.isTrue(PackageVersion.compare(v1, v2) > 0); } else { throw new Error("expected should be '<', '=' or '>'"); } }; compare("1.0.0", "1.2.0", "<"); compare("1.0.0_50", "1.0.1", "<"); compare("1.0.0_50", "1.2.0", "<"); compare("1.0.0_1", "1.0.0_2", "<"); compare("1.0.0_2", "1.0.0_10", "<"); // verify that we compare _N "wrap numbers" as numbers, not strings compare("1.0.0", "1.0.0_2", "<"); compare("1.99.0_99", "3.0.0_2", "<"); compare("1.99.0", "2.0.0", "<"); compare("1.0.0_5", "1.0.0_2", ">"); compare("1.0.0_99", "1.2.0", "<"); compare("1.0.0_99", "1.0.1", "<"); compare("1.0.0_1", "1.0.0_2", "<"); compare("1.0.0", "1.0.0_2", "<"); compare("1.99.0_99", "3.0.0_2", "<"); compare("1.0.0_5", "1.0.0_2", ">"); compare("1.0.0", "1.0.0", "="); compare("1.0.0_5", "1.0.0_5", "="); compare("1.2.0", "1.0.0", ">"); compare("1.0.1", "1.0.0_5", ">"); // Rule 11 from http://semver.org compare("0.99.99", "1.0.0-alpha.1", "<"); compare("1.0.0-alpha", "1.0.0-alpha.1", "<"); compare("1.0.0-alpha.1", "1.0.0-alpha.beta", "<"); compare("1.0.0-alpha.beta", "1.0.0-beta", "<"); compare("1.0.0-beta", "1.0.0-beta.2", "<"); compare("1.0.0-beta.2", "1.0.0-beta.11", "<"); compare("1.0.0-beta.11", "1.0.0-rc.1", "<"); compare("1.0.0-rc.1", "1.0.0", "<"); // dashes are allowed in prerelease parts compare("1.0.0--alpha", "1.0.0-alpha", "<"); compare("1.0.0-a-lpha", "1.0.0-alpha", "<"); // test single character prerelease parts compare("1.0.0-r.1", "1.0.0", "<"); // test the edges of `versionMagnitude` compare("1.0.0-zzzzzzzzzzzz", "1.0.0", "<"); // prerelease parts can contain digits and non-digits compare("1.0.0-r1", "1.0.0-rc", "<"); // Our broken implementation of Rule 11 (see [*] above the // declaration of PackageVersion.versionMagnitude). Maybe one day // we'll fix it, in which case replace "===" with ">" test.isTrue(PackageVersion.versionMagnitude("1.0.0-beta.0") === PackageVersion.versionMagnitude("1.0.0-bear.0")); }); Tinytest.add("package-version-parser - Invalid in 0.9.2", function (test) { // Note that invalidFirstFormatConstraint assumes that the initial version // passed in has been previously checked to be valid in 0.9.3. // These are invalid in 0.9.2, but valid in 0.9.3 and above. var invalidVersions = ["1.0.0_1", "1.0.0 || 2.0.0", "1.0.0-rc1_1", "3.4.0-rc1 || =1.0.0"]; invalidVersions.forEach(function (v) { test.isTrue(PackageVersion.invalidFirstFormatConstraint(v)); }); // These are all valid in 0.9.2. var validVersions = ["1.0.0", "2.0.0-rc1", "=2.5.0"]; validVersions.forEach(function (v) { test.isFalse(PackageVersion.invalidFirstFormatConstraint(v)); }); }); Tinytest.add("package-version-parser - sort", function (test) { var versions = ["1.0.0", "1.0.0-rc.0", "1.0.10", "1.0.2"]; versions.sort(PackageVersion.compare); test.equal(versions, ["1.0.0-rc.0", "1.0.0", "1.0.2", "1.0.10"]); });