diff --git a/lib/build/TaskRunner.js b/lib/build/TaskRunner.js index 605619a8d..3f0e4e7c4 100644 --- a/lib/build/TaskRunner.js +++ b/lib/build/TaskRunner.js @@ -309,12 +309,13 @@ class TaskRunner { params.dependencies = dependencies; } - if (task.getSpecVersion() === "3.0") { + const specVersion = task.getSpecVersion(); + if (specVersion.gte("3.0")) { params.options.taskName = newTaskName; params.log = logger.getGroupLogger(`builder:custom-task:${newTaskName}`); } - const taskUtilInterface = taskUtil.getInterface(task.getSpecVersion()); + const taskUtilInterface = taskUtil.getInterface(specVersion); // Interface is undefined if specVersion does not support taskUtil if (taskUtilInterface) { params.taskUtil = taskUtilInterface; diff --git a/lib/build/definitions/application.js b/lib/build/definitions/application.js index 24416540f..5a21557fc 100644 --- a/lib/build/definitions/application.js +++ b/lib/build/definitions/application.js @@ -34,7 +34,7 @@ export default function({project, taskUtil, getTask}) { // Support rules should not be minified to have readable code in the Support Assistant const minificationPattern = ["/**/*.js", "!**/*.support.js"]; - if (["2.6"].includes(project.getSpecVersion())) { + if (project.getSpecVersion().gte("2.6")) { const minificationExcludes = project.getMinificationExcludes(); if (minificationExcludes.length) { enhancePatternWithExcludes(minificationPattern, minificationExcludes, "/resources/"); diff --git a/lib/build/definitions/library.js b/lib/build/definitions/library.js index 82de4da1a..0d86a2d6d 100644 --- a/lib/build/definitions/library.js +++ b/lib/build/definitions/library.js @@ -73,7 +73,7 @@ export default function({project, taskUtil, getTask}) { // Support rules should not be minified to have readable code in the Support Assistant const minificationPattern = ["/resources/**/*.js", "!**/*.support.js"]; - if (["2.6"].includes(project.getSpecVersion())) { + if (project.getSpecVersion().gte("2.6")) { const minificationExcludes = project.getMinificationExcludes(); if (minificationExcludes.length) { enhancePatternWithExcludes(minificationPattern, minificationExcludes, "/resources/"); diff --git a/lib/build/helpers/TaskUtil.js b/lib/build/helpers/TaskUtil.js index 1529accf1..d71689106 100644 --- a/lib/build/helpers/TaskUtil.js +++ b/lib/build/helpers/TaskUtil.js @@ -184,7 +184,6 @@ class TaskUtil { * * @public * @typedef {object} @ui5/project/build/helpers/TaskUtil~ProjectInterface - * @property {Function} getSpecVersion Get the project Specification Version * @property {Function} getType Get the project type * @property {Function} getName Get the project name * @property {Function} getVersion Get the project version @@ -269,11 +268,13 @@ class TaskUtil { * Get an interface to an instance of this class that only provides those functions * that are supported by the given custom task extension specification version. * - * @param {string} specVersion Specification version of custom task extension + * @param {@ui5/project/specifications/SpecificationVersion} specVersion + * SpecVersionComparator instance of the custom task * @returns {object} An object with bound instance methods supported by the given specification version */ getInterface(specVersion) { - if (["0.1", "1.0", "1.1", "2.0", "2.1"].includes(specVersion)) { + if (specVersion.lte("2.1")) { + // Tasks defining specVersion <= 2.1 do not have access to any TaskUtil APIs return undefined; } @@ -283,26 +284,17 @@ class TaskUtil { bindFunctions(this, baseInterface, [ "setTag", "clearTag", "getTag", "isRootProject", "registerCleanupTask" ]); - switch (specVersion) { - case "2.2": - case "2.3": - case "2.4": - case "2.5": - case "2.6": - return baseInterface; - case "3.0": + + if (specVersion.gte("3.0")) { // getProject function, returning an interfaced project instance baseInterface.getProject = (projectName) => { const project = this.getProject(projectName); const baseProjectInterface = {}; bindFunctions(project, baseProjectInterface, [ - "getSpecVersion", "getType", "getName", "getVersion", "getNamespace", + "getType", "getName", "getVersion", "getNamespace", "getRootReader", "getReader", "getCustomConfiguration", "isFrameworkProject" ]); - switch (specVersion) { - case "3.0": - return baseProjectInterface; - } + return baseProjectInterface; }; // getDependencies function, returning an array of project names baseInterface.getDependencies = (projectName) => { @@ -318,11 +310,8 @@ class TaskUtil { ].forEach((factoryFunction) => { baseInterface.resourceFactory[factoryFunction] = this.resourceFactory[factoryFunction]; }); - - return baseInterface; - default: - throw new Error(`TaskUtil: Unknown or unsupported Specification Version ${specVersion}`); } + return baseInterface; } } diff --git a/lib/build/helpers/createBuildManifest.js b/lib/build/helpers/createBuildManifest.js index ebc0b3ead..c1f1d08e7 100644 --- a/lib/build/helpers/createBuildManifest.js +++ b/lib/build/helpers/createBuildManifest.js @@ -38,7 +38,7 @@ export default async function(project, buildConfig) { const metadata = { project: { - specVersion: project.getSpecVersion(), + specVersion: project.getSpecVersion().toString(), type, metadata: { name: projectName, diff --git a/lib/specifications/Specification.js b/lib/specifications/Specification.js index 227c9937d..3c16e69bd 100644 --- a/lib/specifications/Specification.js +++ b/lib/specifications/Specification.js @@ -1,5 +1,6 @@ import logger from "@ui5/logger"; import {createReader} from "@ui5/fs/resourceFactory"; +import SpecificationVersion from "./SpecificationVersion.js"; /** * Abstract superclass for all projects and extensions @@ -50,10 +51,9 @@ class Specification { const config = JSON.parse(JSON.stringify(configuration)); const {validate} = await import("../validation/validator.js"); - if (config.specVersion === "0.1" || config.specVersion === "1.0" || - config.specVersion === "1.1") { + if (SpecificationVersion.major(config.specVersion) <= 1) { const originalSpecVersion = config.specVersion; - this._log.verbose(`Detected legacy specification version ${config.specVersion}, defined for ` + + this._log.verbose(`Detected legacy Specification Version ${config.specVersion}, defined for ` + `${config.kind} ${config.metadata.name}. ` + `Attempting to migrate the project to a supported specification version...`); this._migrateLegacyProject(config); @@ -69,20 +69,12 @@ class Specification { `Validation error after migration of ${config.kind} ${config.metadata.name}:`); this._log.verbose(err.message); throw new Error( - `${config.kind} ${config.metadata.name} defines unsupported specification version ` + + `${config.kind} ${config.metadata.name} defines unsupported Specification Version ` + `${originalSpecVersion}. Please manually upgrade to 2.0 or higher. ` + `For details see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions - ` + `An attempted migration to a supported specification version failed, ` + `likely due to unrecognized configuration. Check verbose log for details.`); } - } else if (config.specVersion !== "2.0" && - config.specVersion !== "2.1" && config.specVersion !== "2.2" && - config.specVersion !== "2.3" && config.specVersion !== "2.4" && - config.specVersion !== "2.5" && config.specVersion !== "2.6") { - throw new Error( - `Unsupported specification version ${config.specVersion} defined in ${config.kind} ` + - `${config.metadata.name}. Your UI5 CLI installation might be outdated. ` + - `For details see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions`); } else { await validate({ config, @@ -102,7 +94,8 @@ class Specification { this._name = config.metadata.name; this._kind = config.kind; this._type = config.type; - this._specVersion = config.specVersion; + this._specVersionString = config.specVersion; + this._specVersion = new SpecificationVersion(this._specVersionString); this._config = config; return this; @@ -142,10 +135,10 @@ class Specification { } /** - * Get the Specification Version + * Returns an instance of a helper class representing a Specification Version * * @public - * @returns {string} Specification Version + * @returns {@ui5/project/specifications/SpecificationVersion} */ getSpecVersion() { return this._specVersion; diff --git a/lib/specifications/SpecificationVersion.js b/lib/specifications/SpecificationVersion.js new file mode 100644 index 000000000..ae48edee3 --- /dev/null +++ b/lib/specifications/SpecificationVersion.js @@ -0,0 +1,293 @@ +import semver from "semver"; + +const SPEC_VERSION_PATTERN = /^\d+\.\d+$/; +const SUPPORTED_VERSIONS = [ + "0.1", "1.0", "1.1", + "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", + "3.0" +]; + +/** + * Helper class representing a Specification Version. Featuring helper functions for easy comparison + * of versions. + * + * @public + * @class + * @alias @ui5/project/specifications/utils/SpecificationVersion + */ +class SpecificationVersion { + #specVersion; + #semverVersion; + + /** + * @public + * @param {string} specVersion Specification Version to use for all comparison operations + * @throws {Error} Throws if provided Specification Version is not supported by this version of @ui5/project + */ + constructor(specVersion) { + this.#specVersion = specVersion; + this.#semverVersion = getSemverCompatibleVersion(specVersion); // Throws for unsupported versions + } + + /** + * Returns the Specification Version + * + * @public + * @returns {string} Specification Version + */ + toString() { + return this.#specVersion; + } + + /** + * Returns the major-version of the instance's Specification Version + * + * @public + * @returns {integer} Major version + */ + major() { + return semver.major(this.#semverVersion); + } + + /** + * Returns the minor-version of the instance's Specification Version + * + * @public + * @returns {integer} Minor version + */ + minor() { + return semver.minor(this.#semverVersion); + } + + /** + * Test whether the instance's Specification Version falls into the provided range + * + * @public +@param {string} range [Semver]{@link https://www.npmjs.com/package/semver}-style version range, +for example 2.2 - 2.4 + * @returns {boolean} True if the instance's Specification Version falls into the provided range + */ + satisfies(range) { + return semver.satisfies(this.#semverVersion, range); + } + + /** + * Test whether the instance's Specification Version is greater than the provided test version + * + * @public + * @param {string} testVersion A Specification Version to compare the instance's Specification Version to + * @returns {boolean} True if the instance's Specification Version is greater than the provided version + */ + gt(testVersion) { + return handleSemverComparator(semver.gt, this.#semverVersion, testVersion); + } + + /** + * Test whether the instance's Specification Version is greater than or equal to the provided test version + * + * @public + * @param {string} testVersion A Specification Version to compare the instance's Specification Version to + * @returns {boolean} True if the instance's Specification Version is greater than or equal to the provided version + */ + gte(testVersion) { + return handleSemverComparator(semver.gte, this.#semverVersion, testVersion); + } + + /** + * Test whether the instance's Specification Version is smaller than the provided test version + * + * @public + * @param {string} testVersion A Specification Version to compare the instance's Specification Version to + * @returns {boolean} True if the instance's Specification Version is smaller than the provided version + */ + lt(testVersion) { + return handleSemverComparator(semver.lt, this.#semverVersion, testVersion); + } + + /** + * Test whether the instance's Specification Version is smaller than or equal to the provided test version + * + * @public + * @param {string} testVersion A Specification Version to compare the instance's Specification Version to + * @returns {boolean} True if the instance's Specification Version is smaller than or equal to the provided version + */ + lte(testVersion) { + return handleSemverComparator(semver.lte, this.#semverVersion, testVersion); + } + + /** + * Test whether the instance's Specification Version is equal to the provided test version + * + * @public + * @param {string} testVersion A Specification Version to compare the instance's Specification Version to + * @returns {boolean} True if the instance's Specification Version is equal to the provided version + */ + eq(testVersion) { + return handleSemverComparator(semver.eq, this.#semverVersion, testVersion); + } + + /** + * Test whether the instance's Specification Version is not equal to the provided test version + * + * @public + * @param {string} testVersion A Specification Version to compare the instance's Specification Version to + * @returns {boolean} True if the instance's Specification Version is not equal to the provided version + */ + neq(testVersion) { + return handleSemverComparator(semver.neq, this.#semverVersion, testVersion); + } + + /** + * Test whether the provided Specification Version is supported by this version of @ui5/project + * + * @public + * @param {string} testVersion A Specification Version to compare the instance's Specification Version to + * @returns {boolean} True if the provided Specification Version is supported + */ + static isSupportedSpecVersion(testVersion) { + return SUPPORTED_VERSIONS.includes(testVersion); + } + + /** + * Returns the major-version of the provided Specification Version + * + * @public + * @param {string} specVersion Specification Version + * @returns {integer} Major version + */ + static major(specVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.major(); + } + + /** + * Returns the minor-version of the provided Specification Version + * + * @public + * @param {string} specVersion Specification Version + * @returns {integer} Minor version + */ + static minor(specVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.minor(); + } + + /** + * Test whether the provided Specification Version falls into the provided range + * + * @public + * @param {string} specVersion Specification Version + * @param {string} range [Semver]{@link https://www.npmjs.com/package/semver}-style version range, + * for example 2.2 - 2.4 + * @returns {boolean} True if the provided Specification Version falls into the provided range + */ + static satisfies(specVersion, range) { + const comparator = new SpecificationVersion(specVersion); + return comparator.satisfies(range); + } + + /** + * Test whether the provided Specification Version is greater than the provided test version + * + * @public + * @param {string} specVersion Specification Version + * @param {string} testVersion A Specification Version to compare the provided Specification Version to + * @returns {boolean} True if the provided Specification Version is greater than the provided version + */ + static gt(specVersion, testVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.gt(testVersion); + } + + /** + * Test whether the provided Specification Version is greater than or equal to the provided test version + * + * @public + * @param {string} specVersion Specification Version + * @param {string} testVersion A Specification Version to compare the provided Specification Version to + * @returns {boolean} True if the provided Specification Version is greater than or equal to the provided version + */ + static gte(specVersion, testVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.gte(testVersion); + } + + /** + * Test whether the provided Specification Version is smaller than the provided test version + * + * @public + * @param {string} specVersion Specification Version + * @param {string} testVersion A Specification Version to compare the provided Specification Version to + * @returns {boolean} True if the provided Specification Version is smaller than the provided version + */ + static lt(specVersion, testVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.lt(testVersion); + } + + /** + * Test whether the provided Specification Version is smaller than or equal to the provided test version + * + * @public + * @param {string} specVersion Specification Version + * @param {string} testVersion A Specification Version to compare the provided Specification Version to + * @returns {boolean} True if the provided Specification Version is smaller than or equal to the provided version + */ + static lte(specVersion, testVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.lte(testVersion); + } + + /** + * Test whether the provided Specification Version is equal to the provided test version + * + * @public + * @param {string} specVersion Specification Version + * @param {string} testVersion A Specification Version to compare the provided Specification Version to + * @returns {boolean} True if the provided Specification Version is equal to the provided version + */ + static eq(specVersion, testVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.eq(testVersion); + } + + /** + * Test whether the provided Specification Version is not equal to the provided test version + * + * @public + * @param {string} specVersion Specification Version + * @param {string} testVersion A Specification Version to compare the provided Specification Version to + * @returns {boolean} True if the provided Specification Version is not equal to the provided version + */ + static neq(specVersion, testVersion) { + const comparator = new SpecificationVersion(specVersion); + return comparator.neq(testVersion); + } +} + +function getUnsupportedSpecVersionMessage(specVersion) { + return `Unsupported Specification Version ${specVersion} defined. Your UI5 CLI installation might be outdated. ` + + `For details, see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions`; +} + +function getSemverCompatibleVersion(specVersion) { + if (SpecificationVersion.isSupportedSpecVersion(specVersion)) { + return specVersion + ".0"; + } + throw new Error(getUnsupportedSpecVersionMessage(specVersion)); +} + +function handleSemverComparator(comparator, baseVersion, testVersion) { + if (SPEC_VERSION_PATTERN.test(testVersion)) { + const a = baseVersion; + const b = testVersion + ".0"; + return comparator(a, b); + } + throw new Error("Invalid spec version expectation given in comparator: " + testVersion); +} + +export default SpecificationVersion; + +// Export local function for testing only +export const __localFunctions__ = (process.env.NODE_ENV === "test") ? + {getSemverCompatibleVersion, handleSemverComparator} : /* istanbul ignore next */ undefined; diff --git a/package.json b/package.json index 1fb5dab89..454f6f0d8 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ ], "type": "module", "exports": { + "./specifications/SpecificationVersion": "./lib/specifications/SpecificationVersion.js", "./ui5Framework/Openui5Resolver": "./lib/ui5Framework/Openui5Resolver.js", "./ui5Framework/Sapui5Resolver": "./lib/ui5Framework/Sapui5Resolver.js", "./validation/validator": "./lib/validation/validator.js", diff --git a/test/lib/build/ProjectBuilder.js b/test/lib/build/ProjectBuilder.js index 8a0227f55..3bca28270 100644 --- a/test/lib/build/ProjectBuilder.js +++ b/test/lib/build/ProjectBuilder.js @@ -13,7 +13,6 @@ function getMockProject(type, id = "b") { getType: () => type, getCopyright: noop, getVersion: noop, - getSpecVersion: () => "0.1", getReader: () => "reader", getWorkspace: () => "workspace", }; diff --git a/test/lib/build/TaskRunner.js b/test/lib/build/TaskRunner.js index 77f46f011..d7410cdd9 100644 --- a/test/lib/build/TaskRunner.js +++ b/test/lib/build/TaskRunner.js @@ -24,8 +24,12 @@ function getMockProject(type) { getPropertiesFileSourceEncoding: noop, getCopyright: noop, getVersion: noop, - getSpecVersion: () => "0.1", getMinificationExcludes: emptyarray, + getSpecVersion: () => { + return { + gte: () => false + }; + }, getComponentPreloadPaths: () => [ "project/b/**/Component.js" ], @@ -391,9 +395,14 @@ test("Custom tasks is unknown", async (t) => { test("Custom task is called correctly", async (t) => { const {sinon, graph, taskUtil, taskRepository, TaskRunner} = t.context; const taskStub = sinon.stub(); + const specVersionGteStub = sinon.stub().returns(false); + const mockSpecVersion = { + toString: () => "2.6", + gte: specVersionGteStub + }; graph.getExtension.returns({ getTask: () => taskStub, - getSpecVersion: () => "2.6" + getSpecVersion: () => mockSpecVersion }); t.context.taskUtil.getInterface.returns("taskUtil interface"); const project = getMockProject("module"); @@ -412,6 +421,9 @@ test("Custom task is called correctly", async (t) => { dependencies: "dependencies" }); + t.is(specVersionGteStub.callCount, 1, "SpecificationVersion#gte got called once"); + t.is(specVersionGteStub.getCall(0).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); t.deepEqual(taskStub.getCall(0).args[0], { @@ -426,16 +438,21 @@ test("Custom task is called correctly", async (t) => { }, "Task got called with one argument"); t.is(taskUtil.getInterface.callCount, 1, "taskUtil#getInterface got called once"); - t.is(taskUtil.getInterface.getCall(0).args[0], "2.6", + t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, "taskUtil#getInterface got called with correct argument"); }); test("Custom task with legacy spec version", async (t) => { const {sinon, graph, taskUtil, taskRepository, TaskRunner} = t.context; const taskStub = sinon.stub(); + const specVersionGteStub = sinon.stub().returns(false); + const mockSpecVersion = { + toString: () => "1.0", + gte: specVersionGteStub + }; graph.getExtension.returns({ getTask: () => taskStub, - getSpecVersion: () => "1.0" + getSpecVersion: () => mockSpecVersion }); t.context.taskUtil.getInterface.returns(undefined); // simulating no taskUtil for old specVersion const project = getMockProject("module"); @@ -454,6 +471,9 @@ test("Custom task with legacy spec version", async (t) => { dependencies: "dependencies" }); + t.is(specVersionGteStub.callCount, 1, "SpecificationVersion#gte got called once"); + t.is(specVersionGteStub.getCall(0).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); t.deepEqual(taskStub.getCall(0).args[0], { @@ -467,16 +487,21 @@ test("Custom task with legacy spec version", async (t) => { }, "Task got called with one argument"); t.is(taskUtil.getInterface.callCount, 1, "taskUtil#getInterface got called once"); - t.is(taskUtil.getInterface.getCall(0).args[0], "1.0", + t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, "taskUtil#getInterface got called with correct argument"); }); test("Custom task with specVersion 3.0", async (t) => { const {sinon, graph, taskUtil, taskRepository, TaskRunner} = t.context; const taskStub = sinon.stub(); + const specVersionGteStub = sinon.stub().returns(true); + const mockSpecVersion = { + toString: () => "3.0", + gte: specVersionGteStub + }; graph.getExtension.returns({ getTask: () => taskStub, - getSpecVersion: () => "3.0" + getSpecVersion: () => mockSpecVersion }); t.context.taskUtil.getInterface.returns(undefined); // simulating no taskUtil for old specVersion const project = getMockProject("module"); @@ -495,6 +520,9 @@ test("Custom task with specVersion 3.0", async (t) => { dependencies: "dependencies" }, "log"); + t.is(specVersionGteStub.callCount, 1, "SpecificationVersion#gte got called once"); + t.is(specVersionGteStub.getCall(0).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); t.deepEqual(taskStub.getCall(0).args[0], { @@ -510,7 +538,7 @@ test("Custom task with specVersion 3.0", async (t) => { }, "Task got called with one argument"); t.is(taskUtil.getInterface.callCount, 1, "taskUtil#getInterface got called once"); - t.is(taskUtil.getInterface.getCall(0).args[0], "3.0", + t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, "taskUtil#getInterface got called with correct argument"); }); @@ -519,17 +547,29 @@ test("Multiple custom tasks with same name are called correctly", async (t) => { const taskStub1 = sinon.stub(); const taskStub2 = sinon.stub(); const taskStub3 = sinon.stub(); + const mockSpecVersionA = { + toString: () => "2.5", + gte: () => false + }; + const mockSpecVersionB = { + toString: () => "2.6", + gte: () => false + }; + const mockSpecVersionC = { + toString: () => "3.0", + gte: () => true + }; graph.getExtension.onFirstCall().returns({ getTask: () => taskStub1, - getSpecVersion: () => "2.5" + getSpecVersion: () => mockSpecVersionA }); graph.getExtension.onSecondCall().returns({ getTask: () => taskStub2, - getSpecVersion: () => "2.6" + getSpecVersion: () => mockSpecVersionB }); graph.getExtension.onThirdCall().returns({ getTask: () => taskStub3, - getSpecVersion: () => "3.0" + getSpecVersion: () => mockSpecVersionC }); const project = getMockProject("module"); project.getCustomTasks = () => [ @@ -599,11 +639,11 @@ test("Multiple custom tasks with same name are called correctly", async (t) => { }, "Task 3 got called with one argument"); t.is(taskUtil.getInterface.callCount, 3, "taskUtil#getInterface got called once"); - t.is(taskUtil.getInterface.getCall(0).args[0], "2.5", + t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersionA, "taskUtil#getInterface got called with correct argument on first call"); - t.is(taskUtil.getInterface.getCall(1).args[0], "3.0", + t.is(taskUtil.getInterface.getCall(1).args[0], mockSpecVersionC, "taskUtil#getInterface got called with correct argument on second call"); - t.is(taskUtil.getInterface.getCall(2).args[0], "2.6", + t.is(taskUtil.getInterface.getCall(2).args[0], mockSpecVersionB, "taskUtil#getInterface got called with correct argument on third call"); }); diff --git a/test/lib/build/definitions/application.js b/test/lib/build/definitions/application.js index 76240a617..4fe95a23a 100644 --- a/test/lib/build/definitions/application.js +++ b/test/lib/build/definitions/application.js @@ -14,7 +14,12 @@ function getMockProject() { getPropertiesFileSourceEncoding: () => "UTF-412", getCopyright: () => "copyright", getVersion: () => "version", - getSpecVersion: () => "2.6", + getSpecVersion: () => { + return { + toString: () => "2.6", + gte: () => true + }; + }, getMinificationExcludes: emptyarray, getComponentPreloadPaths: emptyarray, getComponentPreloadNamespaces: emptyarray, @@ -103,7 +108,12 @@ test("Standard build", (t) => { test("Standard build with legacy spec version", (t) => { const {project, taskUtil, getTask} = t.context; - project.getSpecVersion = () => "0.1"; + project.getSpecVersion = () => { + return { + toString: () => "0.1", + gte: () => false + }; + }; const generateBundleTaskStub = sinon.stub(); getTask.returns({ task: generateBundleTaskStub @@ -364,7 +374,12 @@ test("Minification excludes", (t) => { test("Minification excludes not applied for legacy specVersion", (t) => { const {project, taskUtil, getTask} = t.context; - project.getSpecVersion = () => "2.5"; + project.getSpecVersion = () => { + return { + toString: () => "2.5", + gte: () => false + }; + }; project.getMinificationExcludes = () => ["**.html"]; const tasks = application({ diff --git a/test/lib/build/definitions/library.js b/test/lib/build/definitions/library.js index 444266638..165c8103d 100644 --- a/test/lib/build/definitions/library.js +++ b/test/lib/build/definitions/library.js @@ -14,7 +14,12 @@ function getMockProject() { getPropertiesFileSourceEncoding: () => "UTF-412", getCopyright: () => "copyright", getVersion: () => "version", - getSpecVersion: () => "2.6", + getSpecVersion: () => { + return { + toString: () => "2.6", + gte: () => true + }; + }, getMinificationExcludes: emptyarray, getComponentPreloadPaths: emptyarray, getComponentPreloadNamespaces: emptyarray, @@ -151,7 +156,12 @@ test("Standard build", async (t) => { test("Standard build with legacy spec version", (t) => { const {project, taskUtil, getTask} = t.context; - project.getSpecVersion = () => "0.1"; + project.getSpecVersion = () => { + return { + toString: () => "0.1", + gte: () => false + }; + }; const tasks = library({ project, taskUtil, getTask @@ -432,7 +442,12 @@ test("Minification excludes", (t) => { test("Minification excludes not applied for legacy specVersion", (t) => { const {project, taskUtil, getTask} = t.context; - project.getSpecVersion = () => "2.5"; + project.getSpecVersion = () => { + return { + toString: () => "2.5", + gte: () => false + }; + }; project.getMinificationExcludes = () => ["**.html"]; const tasks = library({ diff --git a/test/lib/build/definitions/themeLibrary.js b/test/lib/build/definitions/themeLibrary.js index e7c04a425..a5f72c5a4 100644 --- a/test/lib/build/definitions/themeLibrary.js +++ b/test/lib/build/definitions/themeLibrary.js @@ -13,7 +13,11 @@ function getMockProject() { getType: () => "theme-library", getCopyright: () => "copyright", getVersion: () => "version", - getSpecVersion: () => "2.6", + getSpecVersion: () => { + return { + toString: () => "2.6" + }; + }, getMinificationExcludes: emptyarray, getComponentPreloadPaths: emptyarray, getComponentPreloadNamespaces: emptyarray, diff --git a/test/lib/build/helpers/TaskUtil.js b/test/lib/build/helpers/TaskUtil.js index bc2a57c5f..d960db74b 100644 --- a/test/lib/build/helpers/TaskUtil.js +++ b/test/lib/build/helpers/TaskUtil.js @@ -1,11 +1,16 @@ import test from "ava"; import sinon from "sinon"; import TaskUtil from "../../../../lib/build/helpers/TaskUtil.js"; +import SpecificationVersion from "../../../../lib/specifications/SpecificationVersion.js"; test.afterEach.always((t) => { sinon.restore(); }); +function getSpecificationVersion(specVersion) { + return new SpecificationVersion(specVersion); +} + const STANDARD_TAGS = Object.freeze({ IsDebugVariant: "ui5:IsDebugVariant", HasDebugVariant: "ui5:HasDebugVariant", @@ -15,9 +20,7 @@ const STANDARD_TAGS = Object.freeze({ test("Instantiation", (t) => { const taskUtil = new TaskUtil({ - projectBuildContext: { - // STANDARD_TAGS: ["some tag", "some other tag", "Thursday"] - } + projectBuildContext: {} }); t.deepEqual(taskUtil.STANDARD_TAGS, STANDARD_TAGS, "Correct standard tags exposed"); @@ -183,6 +186,22 @@ test("getDependencies", (t) => { t.is(res, "Pony farm!", "Correct result"); }); +test("resourceFactory", (t) => { + const {resourceFactory} = new TaskUtil({ + projectBuildContext: {} + }); + t.is(typeof resourceFactory.createResource, "function", + "resourceFactory function createResource is available"); + t.is(typeof resourceFactory.createReaderCollectionPrioritized, "function", + "resourceFactory function createReaderCollectionPrioritized is available"); + t.is(typeof resourceFactory.createFilterReader, "function", + "resourceFactory function createFilterReader is available"); + t.is(typeof resourceFactory.createLinkReader, "function", + "resourceFactory function createLinkReader is available"); + t.is(typeof resourceFactory.createFlatReader, "function", + "resourceFactory function createFlatReader is available"); +}); + test("registerCleanupTask", (t) => { const registerCleanupTaskStub = sinon.stub(); const taskUtil = new TaskUtil({ @@ -202,7 +221,7 @@ test("getInterface: specVersion 1.0", (t) => { projectBuildContext: {} }); - const interfacedTaskUtil = taskUtil.getInterface("1.0"); + const interfacedTaskUtil = taskUtil.getInterface(getSpecificationVersion("1.0")); t.is(interfacedTaskUtil, undefined, "no interface provided"); }); @@ -212,7 +231,7 @@ test("getInterface: specVersion 2.2", (t) => { projectBuildContext: {} }); - const interfacedTaskUtil = taskUtil.getInterface("2.2"); + const interfacedTaskUtil = taskUtil.getInterface(getSpecificationVersion("2.2")); t.deepEqual(Object.keys(interfacedTaskUtil), [ "STANDARD_TAGS", @@ -236,7 +255,7 @@ test("getInterface: specVersion 2.3", (t) => { projectBuildContext: {} }); - const interfacedTaskUtil = taskUtil.getInterface("2.3"); + const interfacedTaskUtil = taskUtil.getInterface(getSpecificationVersion("2.3")); t.deepEqual(Object.keys(interfacedTaskUtil), [ "STANDARD_TAGS", @@ -260,7 +279,7 @@ test("getInterface: specVersion 2.4", (t) => { projectBuildContext: {} }); - const interfacedTaskUtil = taskUtil.getInterface("2.4"); + const interfacedTaskUtil = taskUtil.getInterface(getSpecificationVersion("2.4")); t.deepEqual(Object.keys(interfacedTaskUtil), [ "STANDARD_TAGS", @@ -284,7 +303,7 @@ test("getInterface: specVersion 2.5", (t) => { projectBuildContext: {} }); - const interfacedTaskUtil = taskUtil.getInterface("2.5"); + const interfacedTaskUtil = taskUtil.getInterface(getSpecificationVersion("2.5")); t.deepEqual(Object.keys(interfacedTaskUtil), [ "STANDARD_TAGS", @@ -308,7 +327,7 @@ test("getInterface: specVersion 2.6", (t) => { projectBuildContext: {} }); - const interfacedTaskUtil = taskUtil.getInterface("2.6"); + const interfacedTaskUtil = taskUtil.getInterface(getSpecificationVersion("2.6")); t.deepEqual(Object.keys(interfacedTaskUtil), [ "STANDARD_TAGS", @@ -350,7 +369,7 @@ test("getInterface: specVersion 3.0", (t) => { } }); - const interfacedTaskUtil = taskUtil.getInterface("3.0"); + const interfacedTaskUtil = taskUtil.getInterface(getSpecificationVersion("3.0")); t.deepEqual(Object.keys(interfacedTaskUtil), [ "STANDARD_TAGS", @@ -375,7 +394,6 @@ test("getInterface: specVersion 3.0", (t) => { // getProject const interfacedProject = interfacedTaskUtil.getProject("pony"); t.deepEqual(Object.keys(interfacedProject), [ - "getSpecVersion", "getType", "getName", "getVersion", @@ -386,7 +404,6 @@ test("getInterface: specVersion 3.0", (t) => { "isFrameworkProject", ], "Correct methods are provided"); - t.is(interfacedProject.getSpecVersion(), "specVersion", "getSpecVersion function is bound correctly"); t.is(interfacedProject.getType(), "type", "getType function is bound correctly"); t.is(interfacedProject.getName(), "name", "getName function is bound correctly"); t.is(interfacedProject.getVersion(), "version", "getVersion function is bound correctly"); @@ -415,28 +432,3 @@ test("getInterface: specVersion 3.0", (t) => { t.is(typeof resourceFactory.createFlatReader, "function", "resourceFactory function createFlatReader is available"); }); - -test("getInterface: specVersion undefined", (t) => { - const taskUtil = new TaskUtil({ - projectBuildContext: {} - }); - - const err = t.throws(() => { - taskUtil.getInterface(); - }); - - t.is(err.message, "TaskUtil: Unknown or unsupported Specification Version undefined", - "Throw with correct error message"); -}); - -test("getInterface: specVersion unknown", (t) => { - const taskUtil = new TaskUtil({ - projectBuildContext: {} - }); - const err = t.throws(() => { - taskUtil.getInterface("1.5"); - }); - - t.is(err.message, "TaskUtil: Unknown or unsupported Specification Version 1.5", - "Throw with correct error message"); -}); diff --git a/test/lib/package-exports.js b/test/lib/package-exports.js index 7d85d4e45..349f99de2 100644 --- a/test/lib/package-exports.js +++ b/test/lib/package-exports.js @@ -13,11 +13,12 @@ test("export of package.json", (t) => { // Check number of definied exports test("check number of exports", (t) => { const packageJson = require("@ui5/project/package.json"); - t.is(Object.keys(packageJson.exports).length, 8); + t.is(Object.keys(packageJson.exports).length, 9); }); // Public API contract (exported modules) [ + "specifications/SpecificationVersion", "ui5Framework/Openui5Resolver", "ui5Framework/Sapui5Resolver", "validation/validator", diff --git a/test/lib/specifications/Specification.js b/test/lib/specifications/Specification.js index 214f56f32..b292b9e76 100644 --- a/test/lib/specifications/Specification.js +++ b/test/lib/specifications/Specification.js @@ -48,6 +48,9 @@ test("Configurations", async (t) => { const project = await Specification.create(t.context.basicProjectInput); t.is(project.getKind(), "project", "Returned correct kind configuration"); t.is(project.getType(), "application", "Returned correct type configuration"); + t.is(project.getSpecVersion().toString(), "2.3", "Returned correct specification version"); + t.is(project.getSpecVersion().major(), 2, + "SpecVersionComparator returned correct major version"); }); test("Access project root resources via reader", async (t) => { @@ -110,7 +113,7 @@ test("Migrate legacy project", async (t) => { t.context.basicProjectInput.configuration.specVersion = "1.0"; const project = await Specification.create(t.context.basicProjectInput); - t.is(project.getSpecVersion(), "2.6", "Project got migrated to latest specVersion"); + t.is(project.getSpecVersion().toString(), "2.6", "Project got migrated to latest specVersion"); }); test("Migrate legacy project unexpected configuration", async (t) => { @@ -119,14 +122,14 @@ test("Migrate legacy project unexpected configuration", async (t) => { const err = await t.throwsAsync(Specification.create(t.context.basicProjectInput)); t.is(err.message, - "project application.a defines unsupported specification version 1.0. Please manually upgrade to 2.0 or " + + "project application.a defines unsupported Specification Version 1.0. Please manually upgrade to 2.0 or " + "higher. For details see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions - " + "An attempted migration to a supported specification version failed, likely due to unrecognized " + "configuration. Check verbose log for details.", "Threw with expected error message"); }); -test("Migrate legacy module", async (t) => { +test("Migrate legacy module: specVersion 1.0", async (t) => { const project = await Specification.create({ id: "my.task", version: "3.4.7-beta", @@ -144,7 +147,28 @@ test("Migrate legacy module", async (t) => { } }); - t.is(project.getSpecVersion(), "2.6", "Project got migrated to latest specVersion"); + t.is(project.getSpecVersion().toString(), "2.6", "Project got migrated to latest specVersion"); +}); + +test("Migrate legacy module: specVersion 0.1", async (t) => { + const project = await Specification.create({ + id: "my.task", + version: "3.4.7-beta", + modulePath: genericExtensionPath, + configuration: { + specVersion: "0.1", + kind: "extension", + type: "task", + metadata: { + name: "task-a" + }, + task: { + path: "lib/extensionModule.js" + } + } + }); + + t.is(project.getSpecVersion().toString(), "2.6", "Project got migrated to latest specVersion"); }); test("Migrate legacy extension", async (t) => { @@ -171,7 +195,7 @@ test("Migrate legacy extension", async (t) => { } }); - t.is(project.getSpecVersion(), "2.6", "Project got migrated to latest specVersion"); + t.is(project.getSpecVersion().toString(), "2.6", "Project got migrated to latest specVersion"); }); [{ @@ -266,3 +290,12 @@ test("create: Unknown type", async (t) => { message: "Unable to create Specification instance: Unknown specification type 'foo'" }); }); + +test("Invalid specVersion", async (t) => { + t.context.basicProjectInput.configuration.specVersion = "0.5"; + await t.throwsAsync(Specification.create(t.context.basicProjectInput), { + message: + "Unsupported Specification Version 0.5 defined. Your UI5 CLI installation might be outdated. " + + "For details, see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions" + }, "Threw with expected error message"); +}); diff --git a/test/lib/specifications/utils/SpecificationVersion.js b/test/lib/specifications/utils/SpecificationVersion.js new file mode 100644 index 000000000..8094f75cb --- /dev/null +++ b/test/lib/specifications/utils/SpecificationVersion.js @@ -0,0 +1,242 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import SpecificationVersion from "../../../../lib/specifications/SpecificationVersion.js"; +import {__localFunctions__} from "../../../../lib/specifications/SpecificationVersion.js"; + +const unsupportedSpecVersionText = (specVersion) => + `Unsupported Specification Version ${specVersion} defined. Your UI5 CLI installation might be outdated. ` + + `For details, see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions`; + +test.beforeEach((t) => { + t.context.sinon = sinonGlobal.createSandbox(); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test.serial("Invalid specVersion", (t) => { + const {sinon} = t.context; + const isSupportedSpecVersionStub = + sinon.stub(SpecificationVersion, "isSupportedSpecVersion").returns(false); + + t.throws(() => { + new SpecificationVersion("2.5"); + }, { + message: unsupportedSpecVersionText("2.5") + }, "Threw with expected error message"); + + t.is(isSupportedSpecVersionStub.callCount, 1, "Static isSupportedSpecVersionStub has been called once"); + t.deepEqual(isSupportedSpecVersionStub.getCall(0).args, ["2.5"], + "Static isSupportedSpecVersionStub has been called with expected arguments"); +}); + +test("(instance) toString", (t) => { + t.is(new SpecificationVersion("0.1").toString(), "0.1"); + t.is(new SpecificationVersion("1.1").toString(), "1.1"); +}); + +test("(instance) major", (t) => { + t.is(new SpecificationVersion("0.1").major(), 0); + t.is(new SpecificationVersion("1.1").major(), 1); + t.is(new SpecificationVersion("2.1").major(), 2); + + t.is(t.throws(() => { + new SpecificationVersion("0.2").major(); + }).message, unsupportedSpecVersionText("0.2")); +}); + +test("(instance) minor", (t) => { + t.is(new SpecificationVersion("2.1").minor(), 1); + t.is(new SpecificationVersion("2.2").minor(), 2); + t.is(new SpecificationVersion("2.3").minor(), 3); + + t.is(t.throws(() => { + new SpecificationVersion("1.2").minor(); + }).message, unsupportedSpecVersionText("1.2")); +}); + +test("(instance) satisfies", (t) => { + // range: 1.x + t.is(new SpecificationVersion("1.0").satisfies("1.x"), true); + t.is(new SpecificationVersion("1.1").satisfies("1.x"), true); + t.is(new SpecificationVersion("2.0").satisfies("1.x"), false); + + // range: ^2.2 + t.is(new SpecificationVersion("2.1").satisfies("^2.2"), false); + t.is(new SpecificationVersion("2.2").satisfies("^2.2"), true); + t.is(new SpecificationVersion("2.3").satisfies("^2.2"), true); + + // range: > 1.0 + t.is(new SpecificationVersion("1.0").satisfies("> 1.0"), false); + t.is(new SpecificationVersion("1.1").satisfies("> 1.0"), true); + t.is(new SpecificationVersion("2.2").satisfies("> 1.0"), true); + + // range: 2.2 - 2.4 + t.is(new SpecificationVersion("2.1").satisfies("2.2 - 2.4"), false); + t.is(new SpecificationVersion("2.2").satisfies("2.2 - 2.4"), true); + t.is(new SpecificationVersion("2.3").satisfies("2.2 - 2.4"), true); + t.is(new SpecificationVersion("2.4").satisfies("2.2 - 2.4"), true); + t.is(new SpecificationVersion("2.5").satisfies("2.2 - 2.4"), false); + + // range: 0.1 || 1.0 - 1.1 || ^2.5 + t.is(new SpecificationVersion("0.1").satisfies("0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(new SpecificationVersion("1.0").satisfies("0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(new SpecificationVersion("1.1").satisfies("0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(new SpecificationVersion("2.4").satisfies("0.1 || 1.0 - 1.1 || ^2.5"), false); + t.is(new SpecificationVersion("2.5").satisfies("0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(new SpecificationVersion("2.6").satisfies("0.1 || 1.0 - 1.1 || ^2.5"), true); + + // unsupported spec version + t.is(t.throws(() => { + new SpecificationVersion("0.2").satisfies("1.x"); + }).message, unsupportedSpecVersionText("0.2")); +}); + +test("(instance) low level comparator", (t) => { + t.is(new SpecificationVersion("2.1").gt("2.2"), false); + t.is(new SpecificationVersion("2.2").gt("2.2"), false); + t.is(new SpecificationVersion("2.3").gt("2.2"), true); + + t.is(new SpecificationVersion("2.1").gte("2.2"), false); + t.is(new SpecificationVersion("2.2").gte("2.2"), true); + t.is(new SpecificationVersion("2.3").gte("2.2"), true); + + t.is(new SpecificationVersion("2.1").lt("2.2"), true); + t.is(new SpecificationVersion("2.2").lt("2.2"), false); + t.is(new SpecificationVersion("2.3").lt("2.2"), false); + + t.is(new SpecificationVersion("2.1").lte("2.2"), true); + t.is(new SpecificationVersion("2.2").lte("2.2"), true); + t.is(new SpecificationVersion("2.3").lte("2.2"), false); + + t.is(new SpecificationVersion("2.0").eq("2.2"), false); + t.is(new SpecificationVersion("2.2").eq("2.2"), true); + + t.is(new SpecificationVersion("2.0").neq("2.2"), true); + t.is(new SpecificationVersion("2.2").neq("2.2"), false); +}); + +test("(static) isSupportedSpecVersion", (t) => { + t.is(SpecificationVersion.isSupportedSpecVersion("0.1"), true); + t.is(SpecificationVersion.isSupportedSpecVersion("1.0"), true); + t.is(SpecificationVersion.isSupportedSpecVersion("1.1"), true); + t.is(SpecificationVersion.isSupportedSpecVersion("2.0"), true); + t.is(SpecificationVersion.isSupportedSpecVersion("2.4"), true); + t.is(SpecificationVersion.isSupportedSpecVersion("0.2"), false); + t.is(SpecificationVersion.isSupportedSpecVersion("1.2"), false); + t.is(SpecificationVersion.isSupportedSpecVersion(1.1), false); + t.is(SpecificationVersion.isSupportedSpecVersion("foo"), false); + t.is(SpecificationVersion.isSupportedSpecVersion(""), false); + t.is(SpecificationVersion.isSupportedSpecVersion(), false); +}); + +test("(static) major", (t) => { + t.is(SpecificationVersion.major("0.1"), 0); + t.is(SpecificationVersion.major("1.1"), 1); + t.is(SpecificationVersion.major("2.1"), 2); + + t.is(t.throws(() => { + SpecificationVersion.major("0.2"); + }).message, unsupportedSpecVersionText("0.2")); +}); + +test("(static) minor", (t) => { + t.is(SpecificationVersion.minor("2.1"), 1); + t.is(SpecificationVersion.minor("2.2"), 2); + t.is(SpecificationVersion.minor("2.3"), 3); + + t.is(t.throws(() => { + SpecificationVersion.minor("1.2"); + }).message, unsupportedSpecVersionText("1.2")); +}); + +test("(static) satisfies", (t) => { + // range: 1.x + t.is(SpecificationVersion.satisfies("1.0", "1.x"), true); + t.is(SpecificationVersion.satisfies("1.1", "1.x"), true); + t.is(SpecificationVersion.satisfies("2.0", "1.x"), false); + + // range: ^2.2 + t.is(SpecificationVersion.satisfies("2.1", "^2.2"), false); + t.is(SpecificationVersion.satisfies("2.2", "^2.2"), true); + t.is(SpecificationVersion.satisfies("2.3", "^2.2"), true); + + // range: > 1.0 + t.is(SpecificationVersion.satisfies("1.0", "> 1.0"), false); + t.is(SpecificationVersion.satisfies("1.1", "> 1.0"), true); + t.is(SpecificationVersion.satisfies("2.2", "> 1.0"), true); + + // range: 2.2 - 2.4 + t.is(SpecificationVersion.satisfies("2.1", "2.2 - 2.4"), false); + t.is(SpecificationVersion.satisfies("2.2", "2.2 - 2.4"), true); + t.is(SpecificationVersion.satisfies("2.3", "2.2 - 2.4"), true); + t.is(SpecificationVersion.satisfies("2.4", "2.2 - 2.4"), true); + t.is(SpecificationVersion.satisfies("2.5", "2.2 - 2.4"), false); + + // range: 0.1 || 1.0 - 1.1 || ^2.5 + t.is(SpecificationVersion.satisfies("0.1", "0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(SpecificationVersion.satisfies("1.0", "0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(SpecificationVersion.satisfies("1.1", "0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(SpecificationVersion.satisfies("2.4", "0.1 || 1.0 - 1.1 || ^2.5"), false); + t.is(SpecificationVersion.satisfies("2.5", "0.1 || 1.0 - 1.1 || ^2.5"), true); + t.is(SpecificationVersion.satisfies("2.6", "0.1 || 1.0 - 1.1 || ^2.5"), true); + + // unsupported spec version + t.is(t.throws(() => { + SpecificationVersion.satisfies("0.2", "1.x"); + }).message, unsupportedSpecVersionText("0.2")); +}); + +test("(static) low level comparator", (t) => { + t.is(SpecificationVersion.gt("2.1", "2.2"), false); + t.is(SpecificationVersion.gt("2.2", "2.2"), false); + t.is(SpecificationVersion.gt("2.3", "2.2"), true); + + t.is(SpecificationVersion.gte("2.1", "2.2"), false); + t.is(SpecificationVersion.gte("2.2", "2.2"), true); + t.is(SpecificationVersion.gte("2.3", "2.2"), true); + + t.is(SpecificationVersion.lt("2.1", "2.2"), true); + t.is(SpecificationVersion.lt("2.2", "2.2"), false); + t.is(SpecificationVersion.lt("2.3", "2.2"), false); + + t.is(SpecificationVersion.lte("2.1", "2.2"), true); + t.is(SpecificationVersion.lte("2.2", "2.2"), true); + t.is(SpecificationVersion.lte("2.3", "2.2"), false); + + t.is(SpecificationVersion.eq("2.0", "2.2"), false); + t.is(SpecificationVersion.eq("2.2", "2.2"), true); + + t.is(SpecificationVersion.neq("2.0", "2.2"), true); + t.is(SpecificationVersion.neq("2.2", "2.2"), false); +}); + +test("getSemverCompatibleVersion", (t) => { + t.is(__localFunctions__.getSemverCompatibleVersion("0.1"), "0.1.0"); + t.is(__localFunctions__.getSemverCompatibleVersion("1.1"), "1.1.0"); + t.is(__localFunctions__.getSemverCompatibleVersion("2.0"), "2.0.0"); + + t.is(t.throws(() => { + __localFunctions__.getSemverCompatibleVersion("1.2.3"); + }).message, unsupportedSpecVersionText("1.2.3")); + t.is(t.throws(() => { + __localFunctions__.getSemverCompatibleVersion("0.99"); + }).message, unsupportedSpecVersionText("0.99")); + t.is(t.throws(() => { + __localFunctions__.getSemverCompatibleVersion("foo"); + }).message, unsupportedSpecVersionText("foo")); + t.is(t.throws(() => { + __localFunctions__.getSemverCompatibleVersion(); + }).message, unsupportedSpecVersionText("undefined")); +}); + +test("handleSemverComparator", (t) => { + const comparatorStub = t.context.sinon.stub().returns("foobar"); + t.is(__localFunctions__.handleSemverComparator(comparatorStub, "1.1.0", "2.2"), "foobar"); + t.deepEqual(comparatorStub.getCall(0).args, ["1.1.0", "2.2.0"]); + + t.is(t.throws(() => { + __localFunctions__.handleSemverComparator(undefined, undefined, "a.b"); + }).message, "Invalid spec version expectation given in comparator: a.b"); +});