diff --git a/lib/specifications/Specification.js b/lib/specifications/Specification.js index 5c2af4142..3e60eed07 100644 --- a/lib/specifications/Specification.js +++ b/lib/specifications/Specification.js @@ -39,6 +39,9 @@ class Specification { case "application": { return createAndInitializeSpec("types/Application.js", parameters); } + case "component": { + return createAndInitializeSpec("types/Component.js", parameters); + } case "library": { return createAndInitializeSpec("types/Library.js", parameters); } diff --git a/lib/specifications/SpecificationVersion.js b/lib/specifications/SpecificationVersion.js index ae48edee3..7bbac1eef 100644 --- a/lib/specifications/SpecificationVersion.js +++ b/lib/specifications/SpecificationVersion.js @@ -4,7 +4,7 @@ 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" + "3.0", "3.1" ]; /** @@ -63,8 +63,8 @@ class SpecificationVersion { * 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 + * @param {string} range [Semver]{@link https://www.npmjs.com/package/semver}-style version range, + * for example 2.2 - 2.4 or =3.0 * @returns {boolean} True if the instance's Specification Version falls into the provided range */ satisfies(range) { @@ -263,6 +263,22 @@ for example 2.2 - 2.4 const comparator = new SpecificationVersion(specVersion); return comparator.neq(testVersion); } + + /** + * Create an array of Specification Versions that match with the provided range. This is mainly used + * for testing purposes. I.e. to execute identical tests for a range of specification versions. + * + * @public + * @param {string} range [Semver]{@link https://www.npmjs.com/package/semver}-style version range, + * for example 2.2 - 2.4 or =3.0 + * @returns {string[]} Array of versions that match the specified range + */ + static getVersionsForRange(range) { + return SUPPORTED_VERSIONS.filter((specVersion) => { + const comparator = new SpecificationVersion(specVersion); + return comparator.satisfies(range); + }); + } } function getUnsupportedSpecVersionMessage(specVersion) { diff --git a/lib/specifications/types/Application.js b/lib/specifications/types/Application.js index 05303eb92..9506c74b9 100644 --- a/lib/specifications/types/Application.js +++ b/lib/specifications/types/Application.js @@ -66,11 +66,17 @@ class Application extends ComponentProject { return null; // Applications do not have a dedicated test directory } + /** + * Get a resource reader for the sources of the project (excluding any test resources) + * without a virtual base path + * + * @returns {@ui5/fs/ReaderCollection} Reader collection + */ _getRawSourceReader() { return createReader({ fsBasePath: this.getSourcePath(), virBasePath: "/", - name: `Source reader for application project ${this.getName()}`, + name: `Raw source reader for application project ${this.getName()}`, project: this }); } diff --git a/lib/specifications/types/Component.js b/lib/specifications/types/Component.js new file mode 100644 index 000000000..227dc629c --- /dev/null +++ b/lib/specifications/types/Component.js @@ -0,0 +1,259 @@ +import fsPath from "node:path"; +import ComponentProject from "../ComponentProject.js"; +import {createReader} from "@ui5/fs/resourceFactory"; + +/** + * Component + * + * @public + * @class + * @alias @ui5/project/specifications/types/Component + * @extends @ui5/project/specifications/ComponentProject + * @hideconstructor + */ +class Component extends ComponentProject { + constructor(parameters) { + super(parameters); + + this._pManifests = Object.create(null); + + this._srcPath = "src"; + this._testPath = "test"; + this._testPathExists = false; + + this._propertiesFilesSourceEncoding = "UTF-8"; + } + + /* === Attributes === */ + + /** + * Get the cachebuster signature type configuration of the project + * + * @returns {string} time or hash + */ + getCachebusterSignatureType() { + return this._config.builder && this._config.builder.cachebuster && + this._config.builder.cachebuster.signatureType || "time"; + } + + /** + * Get the path of the project's source directory. This might not be POSIX-style on some platforms. + * + * @public + * @returns {string} Absolute path to the source directory of the project + */ + getSourcePath() { + return fsPath.join(this.getRootPath(), this._srcPath); + } + + /* === Resource Access === */ + /** + * Get a resource reader for the sources of the project (excluding any test resources) + * + * @param {string[]} excludes List of glob patterns to exclude + * @returns {@ui5/fs/ReaderCollection} Reader collection + */ + _getSourceReader(excludes) { + return createReader({ + fsBasePath: this.getSourcePath(), + virBasePath: `/resources/${this._namespace}/`, + name: `Source reader for component project ${this.getName()}`, + project: this, + excludes + }); + } + + /** + * Get a resource reader for the test-resources of the project + * + * @param {string[]} excludes List of glob patterns to exclude + * @returns {@ui5/fs/ReaderCollection} Reader collection + */ + _getTestReader(excludes) { + if (!this._testPathExists) { + return null; + } + const testReader = createReader({ + fsBasePath: fsPath.join(this.getRootPath(), this._testPath), + virBasePath: `/test-resources/${this._namespace}/`, + name: `Runtime test-resources reader for component project ${this.getName()}`, + project: this, + excludes + }); + return testReader; + } + + /** + * Get a resource reader for the sources of the project (excluding any test resources) + * without a virtual base path + * + * @returns {@ui5/fs/ReaderCollection} Reader collection + */ + _getRawSourceReader() { + return createReader({ + fsBasePath: this.getSourcePath(), + virBasePath: "/", + name: `Raw source reader for component project ${this.getName()}`, + project: this + }); + } + + /* === Internals === */ + /** + * @private + * @param {object} config Configuration object + */ + async _configureAndValidatePaths(config) { + await super._configureAndValidatePaths(config); + + if (config.resources && config.resources.configuration && config.resources.configuration.paths) { + if (config.resources.configuration.paths.src) { + this._srcPath = config.resources.configuration.paths.src; + } + if (config.resources.configuration.paths.test) { + this._testPath = config.resources.configuration.paths.test; + } + } + if (!(await this._dirExists("/" + this._srcPath))) { + throw new Error( + `Unable to find source directory '${this._srcPath}' in component project ${this.getName()}`); + } + this._testPathExists = await this._dirExists("/" + this._testPath); + + this._log.verbose(`Path mapping for component project ${this.getName()}:`); + this._log.verbose(` Physical root path: ${this.getRootPath()}`); + this._log.verbose(` Mapped to:`); + this._log.verbose(` /resources/ => ${this._srcPath}`); + this._log.verbose( + ` /test-resources/ => ${this._testPath}${this._testPathExists ? "" : " [does not exist]"}`); + } + + /** + * @private + * @param {object} config Configuration object + * @param {object} buildDescription Cache metadata object + */ + async _parseConfiguration(config, buildDescription) { + await super._parseConfiguration(config, buildDescription); + + if (buildDescription) { + this._namespace = buildDescription.namespace; + return; + } + this._namespace = await this._getNamespace(); + } + + /** + * Determine component namespace either based on a project`s + * manifest.json or manifest.appdescr_variant (fallback if present) + * + * @returns {string} Namespace of the project + * @throws {Error} if namespace can not be determined + */ + async _getNamespace() { + try { + return await this._getNamespaceFromManifestJson(); + } catch (manifestJsonError) { + if (manifestJsonError.code !== "ENOENT") { + throw manifestJsonError; + } + // No manifest.json present + // => attempt fallback to manifest.appdescr_variant (typical for App Variants) + try { + return await this._getNamespaceFromManifestAppDescVariant(); + } catch (appDescVarError) { + if (appDescVarError.code === "ENOENT") { + // Fallback not possible: No manifest.appdescr_variant present + // => Throw error indicating missing manifest.json + // (do not mention manifest.appdescr_variant since it is only + // relevant for the rather "uncommon" App Variants) + throw new Error( + `Could not find required manifest.json for project ` + + `${this.getName()}: ${manifestJsonError.message}`); + } + throw appDescVarError; + } + } + } + + /** + * Determine application namespace by checking manifest.json. + * Any maven placeholders are resolved from the projects pom.xml + * + * @returns {string} Namespace of the project + * @throws {Error} if namespace can not be determined + */ + async _getNamespaceFromManifestJson() { + const manifest = await this._getManifest("/manifest.json"); + let appId; + // check for a proper sap.app/id in manifest.json to determine namespace + if (manifest["sap.app"] && manifest["sap.app"].id) { + appId = manifest["sap.app"].id; + } else { + throw new Error( + `No sap.app/id configuration found in manifest.json of project ${this.getName()}`); + } + + if (this._hasMavenPlaceholder(appId)) { + try { + appId = await this._resolveMavenPlaceholder(appId); + } catch (err) { + throw new Error( + `Failed to resolve namespace of project ${this.getName()}: ${err.message}`); + } + } + const namespace = appId.replace(/\./g, "/"); + this._log.verbose( + `Namespace of project ${this.getName()} is ${namespace} (from manifest.json)`); + return namespace; + } + + /** + * Determine application namespace by checking manifest.appdescr_variant. + * + * @returns {string} Namespace of the project + * @throws {Error} if namespace can not be determined + */ + async _getNamespaceFromManifestAppDescVariant() { + const manifest = await this._getManifest("/manifest.appdescr_variant"); + let appId; + // check for the id property in manifest.appdescr_variant to determine namespace + if (manifest && manifest.id) { + appId = manifest.id; + } else { + throw new Error( + `No "id" property found in manifest.appdescr_variant of project ${this.getName()}`); + } + + const namespace = appId.replace(/\./g, "/"); + this._log.verbose( + `Namespace of project ${this.getName()} is ${namespace} (from manifest.appdescr_variant)`); + return namespace; + } + + /** + * Reads and parses a JSON file with the provided name from the projects source directory + * + * @param {string} filePath Name of the JSON file to read. Typically "manifest.json" or "manifest.appdescr_variant" + * @returns {Promise} resolves with an object containing the content requested manifest file + */ + async _getManifest(filePath) { + if (this._pManifests[filePath]) { + return this._pManifests[filePath]; + } + return this._pManifests[filePath] = this._getRawSourceReader().byPath(filePath) + .then(async (resource) => { + if (!resource) { + throw new Error( + `Could not find resource ${filePath} in project ${this.getName()}`); + } + return JSON.parse(await resource.getString()); + }).catch((err) => { + throw new Error( + `Failed to read ${filePath} for project ` + + `${this.getName()}: ${err.message}`); + }); + } +} + +export default Component; diff --git a/lib/specifications/types/Library.js b/lib/specifications/types/Library.js index 064568570..abf7faf28 100644 --- a/lib/specifications/types/Library.js +++ b/lib/specifications/types/Library.js @@ -107,9 +107,10 @@ class Library extends ComponentProject { } /** - * * Get a resource reader for the sources of the project (excluding any test resources) + * without a virtual base path * In the future the path structure can be flat or namespaced depending on the project + * setup * * @returns {@ui5/fs/ReaderCollection} Reader collection */ @@ -117,7 +118,7 @@ class Library extends ComponentProject { return resourceFactory.createReader({ fsBasePath: this.getSourcePath(), virBasePath: "/", - name: `Source reader for library project ${this.getName()}`, + name: `Raw source reader for library project ${this.getName()}`, project: this }); } diff --git a/lib/validation/schema/specVersion/kind/extension.json b/lib/validation/schema/specVersion/kind/extension.json index 27bf6005c..633aaea6b 100644 --- a/lib/validation/schema/specVersion/kind/extension.json +++ b/lib/validation/schema/specVersion/kind/extension.json @@ -5,7 +5,7 @@ "type": "object", "required": ["specVersion", "kind", "type", "metadata"], "properties": { - "specVersion": { "enum": ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] }, + "specVersion": { "enum": ["3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] }, "kind": { "enum": ["extension"] }, diff --git a/lib/validation/schema/specVersion/kind/extension/project-shim.json b/lib/validation/schema/specVersion/kind/extension/project-shim.json index 415785f1d..cb5a4dbfa 100644 --- a/lib/validation/schema/specVersion/kind/extension/project-shim.json +++ b/lib/validation/schema/specVersion/kind/extension/project-shim.json @@ -6,14 +6,14 @@ "required": ["specVersion", "kind", "type", "metadata", "shims"], "if": { "properties": { - "specVersion": { "enum": ["3.0"] } + "specVersion": { "enum": ["3.0", "3.1"] } } }, "then": { "additionalProperties": false, "properties": { "specVersion": { - "enum": ["3.0"] + "enum": ["3.0", "3.1"] }, "kind": { "enum": ["extension"] diff --git a/lib/validation/schema/specVersion/kind/extension/server-middleware.json b/lib/validation/schema/specVersion/kind/extension/server-middleware.json index a65db5b56..1b46323be 100644 --- a/lib/validation/schema/specVersion/kind/extension/server-middleware.json +++ b/lib/validation/schema/specVersion/kind/extension/server-middleware.json @@ -7,13 +7,13 @@ "required": ["specVersion", "kind", "type", "metadata", "middleware"], "if": { "properties": { - "specVersion": { "enum": ["3.0"] } + "specVersion": { "enum": ["3.0", "3.1"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["3.0"] }, + "specVersion": { "enum": ["3.0", "3.1"] }, "kind": { "enum": ["extension"] }, diff --git a/lib/validation/schema/specVersion/kind/extension/task.json b/lib/validation/schema/specVersion/kind/extension/task.json index f19291e0c..7e02c82eb 100644 --- a/lib/validation/schema/specVersion/kind/extension/task.json +++ b/lib/validation/schema/specVersion/kind/extension/task.json @@ -6,13 +6,13 @@ "required": ["specVersion", "kind", "type", "metadata", "task"], "if": { "properties": { - "specVersion": { "enum": ["3.0"] } + "specVersion": { "enum": ["3.0", "3.1"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["3.0"] }, + "specVersion": { "enum": ["3.0", "3.1"] }, "kind": { "enum": ["extension"] }, diff --git a/lib/validation/schema/specVersion/kind/project.json b/lib/validation/schema/specVersion/kind/project.json index f42fa8a2b..d1aa74b58 100644 --- a/lib/validation/schema/specVersion/kind/project.json +++ b/lib/validation/schema/specVersion/kind/project.json @@ -5,7 +5,7 @@ "type": "object", "required": ["specVersion", "type"], "properties": { - "specVersion": { "enum": ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] }, + "specVersion": { "enum": ["3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] }, "kind": { "enum": ["project", null], "$comment": "Using null to allow not defining 'kind' which defaults to project" @@ -13,12 +13,14 @@ "type": { "enum": [ "application", + "component", "library", "theme-library", "module" ] } }, + "if": { "properties": { "type": {"const": null} @@ -61,6 +63,16 @@ }, "then": { "$ref": "project/module.json" + }, + "else": { + "if": { + "properties": { + "type": {"const": "component"} + } + }, + "then": { + "$ref": "project/component.json" + } } } } diff --git a/lib/validation/schema/specVersion/kind/project/application.json b/lib/validation/schema/specVersion/kind/project/application.json index 81073634a..1091be04e 100644 --- a/lib/validation/schema/specVersion/kind/project/application.json +++ b/lib/validation/schema/specVersion/kind/project/application.json @@ -6,13 +6,13 @@ "required": ["specVersion", "type", "metadata"], "if": { "properties": { - "specVersion": { "enum": ["3.0"] } + "specVersion": { "enum": ["3.0", "3.1"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["3.0"] }, + "specVersion": { "enum": ["3.0", "3.1"] }, "kind": { "enum": ["project", null] }, diff --git a/lib/validation/schema/specVersion/kind/project/component.json b/lib/validation/schema/specVersion/kind/project/component.json new file mode 100644 index 000000000..49539317f --- /dev/null +++ b/lib/validation/schema/specVersion/kind/project/component.json @@ -0,0 +1,108 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://ui5.sap/schema/specVersion/kind/project/component.json", + + "type": "object", + "required": ["specVersion", "type", "metadata"], + "if": { + "properties": { + "specVersion": { "enum": ["3.1"] } + } + }, + "then": { + "additionalProperties": false, + "properties": { + "specVersion": { "enum": ["3.1"] }, + "kind": { + "enum": ["project", null] + }, + "type": { + "enum": ["component"] + }, + "metadata": { + "$ref": "../project.json#/definitions/metadata-3.0" + }, + "framework": { + "$ref": "../project.json#/definitions/framework" + }, + "resources": { + "$ref": "#/definitions/resources" + }, + "builder": { + "$ref": "#/definitions/builder-specVersion-3.0" + }, + "server": { + "$ref": "../project.json#/definitions/server" + }, + "customConfiguration": { + "type": "object", + "additionalProperties": true + } + } + }, + "else": { + }, + + "definitions": { + "resources": { + "type": "object", + "additionalProperties": false, + "properties": { + "configuration": { + "type": "object", + "additionalProperties": false, + "properties": { + "propertiesFileSourceEncoding": { + "$ref": "../project.json#/definitions/resources-configuration-propertiesFileSourceEncoding" + }, + "paths": { + "type": "object", + "additionalProperties": false, + "properties": { + "src": { + "type": "string" + }, + "test": { + "type": "string" + } + } + } + } + } + } + }, + "builder-specVersion-3.0": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "../project.json#/definitions/builder-resources" + }, + "cachebuster": { + "type": "object", + "additionalProperties": false, + "properties": { + "signatureType": { + "enum": ["time", "hash"] + } + } + }, + "bundles": { + "$ref": "../project.json#/definitions/builder-bundles-3.0" + }, + "componentPreload": { + "$ref": "../project.json#/definitions/builder-componentPreload-specVersion-2.3" + }, + "customTasks": { + "$ref": "../project.json#/definitions/customTasks" + }, + "minification": { + "$ref": "../project.json#/definitions/builder-minification" + }, + "settings": { + "$ref": "../project.json#/definitions/builder-settings" + } + } + } + } +} diff --git a/lib/validation/schema/specVersion/kind/project/library.json b/lib/validation/schema/specVersion/kind/project/library.json index d36a1f83e..d5114b633 100644 --- a/lib/validation/schema/specVersion/kind/project/library.json +++ b/lib/validation/schema/specVersion/kind/project/library.json @@ -6,13 +6,13 @@ "required": ["specVersion", "type", "metadata"], "if": { "properties": { - "specVersion": { "enum": ["3.0"] } + "specVersion": { "enum": ["3.0", "3.1"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["3.0"] }, + "specVersion": { "enum": ["3.0", "3.1"] }, "kind": { "enum": ["project", null] }, diff --git a/lib/validation/schema/specVersion/kind/project/module.json b/lib/validation/schema/specVersion/kind/project/module.json index 9684f27d1..e6fe250b8 100644 --- a/lib/validation/schema/specVersion/kind/project/module.json +++ b/lib/validation/schema/specVersion/kind/project/module.json @@ -6,13 +6,13 @@ "required": ["specVersion", "type", "metadata"], "if": { "properties": { - "specVersion": { "enum": ["3.0"] } + "specVersion": { "enum": ["3.1"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["3.0"] }, + "specVersion": { "enum": ["3.1"] }, "kind": { "enum": ["project", null] }, @@ -26,7 +26,7 @@ "$ref": "#/definitions/resources" }, "builder": { - "$ref": "#/definitions/builder-specVersion-2.5" + "$ref": "#/definitions/builder-specVersion-3.1" }, "server": { "$ref": "../project.json#/definitions/server" @@ -40,13 +40,13 @@ "else": { "if": { "properties": { - "specVersion": { "enum": ["2.5", "2.6"] } + "specVersion": { "enum": ["3.0"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["2.5", "2.6"] }, + "specVersion": { "enum": ["3.0"] }, "kind": { "enum": ["project", null] }, @@ -54,7 +54,7 @@ "enum": ["module"] }, "metadata": { - "$ref": "../project.json#/definitions/metadata" + "$ref": "../project.json#/definitions/metadata-3.0" }, "resources": { "$ref": "#/definitions/resources" @@ -74,13 +74,13 @@ "else": { "if": { "properties": { - "specVersion": { "enum": ["2.1", "2.2", "2.3", "2.4"] } + "specVersion": { "enum": ["2.5", "2.6"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["2.1", "2.2", "2.3", "2.4"] }, + "specVersion": { "enum": ["2.5", "2.6"] }, "kind": { "enum": ["project", null] }, @@ -93,6 +93,12 @@ "resources": { "$ref": "#/definitions/resources" }, + "builder": { + "$ref": "#/definitions/builder-specVersion-2.5" + }, + "server": { + "$ref": "../project.json#/definitions/server" + }, "customConfiguration": { "type": "object", "additionalProperties": true @@ -100,20 +106,49 @@ } }, "else": { - "additionalProperties": false, - "properties": { - "specVersion": { "enum": ["2.0"] }, - "kind": { - "enum": ["project", null] - }, - "type": { - "enum": ["module"] - }, - "metadata": { - "$ref": "../project.json#/definitions/metadata" - }, - "resources": { - "$ref": "#/definitions/resources" + "if": { + "properties": { + "specVersion": { "enum": ["2.1", "2.2", "2.3", "2.4"] } + } + }, + "then": { + "additionalProperties": false, + "properties": { + "specVersion": { "enum": ["2.1", "2.2", "2.3", "2.4"] }, + "kind": { + "enum": ["project", null] + }, + "type": { + "enum": ["module"] + }, + "metadata": { + "$ref": "../project.json#/definitions/metadata" + }, + "resources": { + "$ref": "#/definitions/resources" + }, + "customConfiguration": { + "type": "object", + "additionalProperties": true + } + } + }, + "else": { + "additionalProperties": false, + "properties": { + "specVersion": { "enum": ["2.0"] }, + "kind": { + "enum": ["project", null] + }, + "type": { + "enum": ["module"] + }, + "metadata": { + "$ref": "../project.json#/definitions/metadata" + }, + "resources": { + "$ref": "#/definitions/resources" + } } } } @@ -149,6 +184,18 @@ "$ref": "../project.json#/definitions/builder-settings" } } + }, + "builder-specVersion-3.1": { + "type": "object", + "additionalProperties": false, + "properties": { + "resources": { + "$ref": "../project.json#/definitions/builder-resources" + }, + "settings": { + "$ref": "../project.json#/definitions/builder-settings" + } + } } } } diff --git a/lib/validation/schema/specVersion/kind/project/theme-library.json b/lib/validation/schema/specVersion/kind/project/theme-library.json index 522b7064a..44f3f2aa5 100644 --- a/lib/validation/schema/specVersion/kind/project/theme-library.json +++ b/lib/validation/schema/specVersion/kind/project/theme-library.json @@ -6,13 +6,13 @@ "required": ["specVersion", "type", "metadata"], "if": { "properties": { - "specVersion": { "enum": ["3.0"] } + "specVersion": { "enum": ["3.0", "3.1"] } } }, "then": { "additionalProperties": false, "properties": { - "specVersion": { "enum": ["3.0"] }, + "specVersion": { "enum": ["3.0", "3.1"] }, "kind": { "enum": ["project", null] }, diff --git a/lib/validation/schema/specVersion/specVersion.json b/lib/validation/schema/specVersion/specVersion.json index 16458d467..6debcbd3e 100644 --- a/lib/validation/schema/specVersion/specVersion.json +++ b/lib/validation/schema/specVersion/specVersion.json @@ -5,7 +5,7 @@ "type": "object", "required": ["specVersion"], "properties": { - "specVersion": { "enum": ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] }, + "specVersion": { "enum": ["3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] }, "kind": { "enum": ["project", "extension", null], "$comment": "Using null to allow not defining 'kind' which defaults to project" diff --git a/lib/validation/schema/ui5.json b/lib/validation/schema/ui5.json index 59af95301..61795c909 100644 --- a/lib/validation/schema/ui5.json +++ b/lib/validation/schema/ui5.json @@ -10,17 +10,17 @@ "properties": { "specVersion": { "enum": [ - "3.0", + "3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0", "1.1", "1.0", "0.1" ], - "errorMessage": "Unsupported \"specVersion\"\nYour UI5 CLI installation might be outdated.\nSupported specification versions: \"3.0\", \"2.6\", \"2.5\", \"2.4\", \"2.3\", \"2.2\", \"2.1\", \"2.0\", \"1.1\", \"1.0\", \"0.1\"\nFor details, see: https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions" + "errorMessage": "Unsupported \"specVersion\"\nYour UI5 CLI installation might be outdated.\nSupported specification versions: \"3.1\", \"3.0\", \"2.6\", \"2.5\", \"2.4\", \"2.3\", \"2.2\", \"2.1\", \"2.0\", \"1.1\", \"1.0\", \"0.1\"\nFor details, see: https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions" } }, "if": { "properties": { - "specVersion": { "enum": ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] } + "specVersion": { "enum": ["3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"] } } }, "then": { diff --git a/test/fixtures/component.a/middleware.a.js b/test/fixtures/component.a/middleware.a.js new file mode 100644 index 000000000..ea41b01de --- /dev/null +++ b/test/fixtures/component.a/middleware.a.js @@ -0,0 +1 @@ +module.exports = function () {}; diff --git a/test/fixtures/component.a/node_modules/collection/library.a/package.json b/test/fixtures/component.a/node_modules/collection/library.a/package.json new file mode 100644 index 000000000..2179673d4 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.a/package.json @@ -0,0 +1,17 @@ +{ + "name": "library.a", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "ui5": { + "name": "library.a", + "type": "library", + "settings": { + "src": "src", + "test": "test" + } + } +} diff --git a/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/.library b/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/.library new file mode 100644 index 000000000..25c8603f3 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/.library @@ -0,0 +1,17 @@ + + + + library.a + SAP SE + ${copyright} + ${version} + + Library A + + + + library.d + + + + diff --git a/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/themes/base/library.source.less b/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/themes/base/library.source.less new file mode 100644 index 000000000..ff0f1d5e3 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/themes/base/library.source.less @@ -0,0 +1,6 @@ +@libraryAColor1: lightgoldenrodyellow; + +.library-a-foo { + color: @libraryAColor1; + padding: 1px 2px 3px 4px; +} diff --git a/test/fixtures/component.a/node_modules/collection/library.a/test/library/a/Test.html b/test/fixtures/component.a/node_modules/collection/library.a/test/library/a/Test.html new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/component.a/node_modules/collection/library.a/ui5.yaml b/test/fixtures/component.a/node_modules/collection/library.a/ui5.yaml new file mode 100644 index 000000000..8d4784313 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.a/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.a diff --git a/test/fixtures/component.a/node_modules/collection/library.b/package.json b/test/fixtures/component.a/node_modules/collection/library.b/package.json new file mode 100644 index 000000000..2a0243b16 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.b/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.b", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/test/fixtures/component.a/node_modules/collection/library.b/src/library/b/.library b/test/fixtures/component.a/node_modules/collection/library.b/src/library/b/.library new file mode 100644 index 000000000..36052aceb --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.b/src/library/b/.library @@ -0,0 +1,17 @@ + + + + library.b + SAP SE + ${copyright} + ${version} + + Library B + + + + library.d + + + + diff --git a/test/fixtures/component.a/node_modules/collection/library.b/test/library/b/Test.html b/test/fixtures/component.a/node_modules/collection/library.b/test/library/b/Test.html new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/component.a/node_modules/collection/library.b/ui5.yaml b/test/fixtures/component.a/node_modules/collection/library.b/ui5.yaml new file mode 100644 index 000000000..b2fe5be59 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.b/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.b diff --git a/test/fixtures/component.a/node_modules/collection/library.c/package.json b/test/fixtures/component.a/node_modules/collection/library.c/package.json new file mode 100644 index 000000000..64ac75d6f --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.c/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.c", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/test/fixtures/component.a/node_modules/collection/library.c/src/library/c/.library b/test/fixtures/component.a/node_modules/collection/library.c/src/library/c/.library new file mode 100644 index 000000000..4180ce2af --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.c/src/library/c/.library @@ -0,0 +1,17 @@ + + + + library.c + SAP SE + ${copyright} + ${version} + + Library C + + + + library.d + + + + diff --git a/test/fixtures/component.a/node_modules/collection/library.c/test/LibraryC/Test.html b/test/fixtures/component.a/node_modules/collection/library.c/test/LibraryC/Test.html new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/component.a/node_modules/collection/library.c/ui5.yaml b/test/fixtures/component.a/node_modules/collection/library.c/ui5.yaml new file mode 100644 index 000000000..7c5e38a7f --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/library.c/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.c diff --git a/test/fixtures/component.a/node_modules/collection/node_modules/library.d/package.json b/test/fixtures/component.a/node_modules/collection/node_modules/library.d/package.json new file mode 100644 index 000000000..90c75040a --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/node_modules/library.d/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.d", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/test/fixtures/component.a/node_modules/collection/node_modules/library.d/src/library/d/.library b/test/fixtures/component.a/node_modules/collection/node_modules/library.d/src/library/d/.library new file mode 100644 index 000000000..21251d1bb --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/node_modules/library.d/src/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + ${copyright} + ${version} + + Library D + + diff --git a/test/fixtures/component.a/node_modules/collection/node_modules/library.d/test/library/d/Test.html b/test/fixtures/component.a/node_modules/collection/node_modules/library.d/test/library/d/Test.html new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/component.a/node_modules/collection/node_modules/library.d/ui5.yaml b/test/fixtures/component.a/node_modules/collection/node_modules/library.d/ui5.yaml new file mode 100644 index 000000000..a47c1f64c --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/node_modules/library.d/ui5.yaml @@ -0,0 +1,10 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.d +resources: + configuration: + paths: + src: main/src + test: main/test diff --git a/test/fixtures/component.a/node_modules/collection/package.json b/test/fixtures/component.a/node_modules/collection/package.json new file mode 100644 index 000000000..81b948438 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/package.json @@ -0,0 +1,18 @@ +{ + "name": "collection", + "version": "1.0.0", + "description": "Simple Collection", + "dependencies": { + "library.d": "file:../library.d" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "collection": { + "modules": { + "library.a": "./library.a", + "library.b": "./library.b", + "library.c": "./library.c" + } + } +} diff --git a/test/fixtures/component.a/node_modules/collection/ui5.yaml b/test/fixtures/component.a/node_modules/collection/ui5.yaml new file mode 100644 index 000000000..e47048de6 --- /dev/null +++ b/test/fixtures/component.a/node_modules/collection/ui5.yaml @@ -0,0 +1,12 @@ +specVersion: "2.1" +metadata: + name: application.a.collection.dependency.shim +kind: extension +type: project-shim +shims: + collections: + collection: + modules: + "library.a": "./library.a" + "library.b": "./library.b" + "library.c": "./library.c" \ No newline at end of file diff --git a/test/fixtures/component.a/node_modules/library.d/main/src/library/d/.library b/test/fixtures/component.a/node_modules/library.d/main/src/library/d/.library new file mode 100644 index 000000000..53c2d14c9 --- /dev/null +++ b/test/fixtures/component.a/node_modules/library.d/main/src/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + ${version} + + Library D + + diff --git a/test/fixtures/component.a/node_modules/library.d/main/src/library/d/some.js b/test/fixtures/component.a/node_modules/library.d/main/src/library/d/some.js new file mode 100644 index 000000000..81e734360 --- /dev/null +++ b/test/fixtures/component.a/node_modules/library.d/main/src/library/d/some.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); \ No newline at end of file diff --git a/test/fixtures/component.a/node_modules/library.d/main/test/library/d/Test.html b/test/fixtures/component.a/node_modules/library.d/main/test/library/d/Test.html new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/component.a/node_modules/library.d/package.json b/test/fixtures/component.a/node_modules/library.d/package.json new file mode 100644 index 000000000..90c75040a --- /dev/null +++ b/test/fixtures/component.a/node_modules/library.d/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.d", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/test/fixtures/component.a/node_modules/library.d/ui5.yaml b/test/fixtures/component.a/node_modules/library.d/ui5.yaml new file mode 100644 index 000000000..a47c1f64c --- /dev/null +++ b/test/fixtures/component.a/node_modules/library.d/ui5.yaml @@ -0,0 +1,10 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.d +resources: + configuration: + paths: + src: main/src + test: main/test diff --git a/test/fixtures/component.a/package.json b/test/fixtures/component.a/package.json new file mode 100644 index 000000000..cd7457d2b --- /dev/null +++ b/test/fixtures/component.a/package.json @@ -0,0 +1,13 @@ +{ + "name": "component.a", + "version": "1.0.0", + "description": "Simple SAPUI5 based component", + "main": "index.html", + "dependencies": { + "library.d": "file:../library.d", + "collection": "file:../collection" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/test/fixtures/component.a/src/index.html b/test/fixtures/component.a/src/index.html new file mode 100644 index 000000000..77b0207cc --- /dev/null +++ b/test/fixtures/component.a/src/index.html @@ -0,0 +1,9 @@ + + + + Application A + + + + + \ No newline at end of file diff --git a/test/fixtures/component.a/src/manifest.json b/test/fixtures/component.a/src/manifest.json new file mode 100644 index 000000000..781945df9 --- /dev/null +++ b/test/fixtures/component.a/src/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "id1", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} \ No newline at end of file diff --git a/test/fixtures/component.a/src/test.js b/test/fixtures/component.a/src/test.js new file mode 100644 index 000000000..a3df410c3 --- /dev/null +++ b/test/fixtures/component.a/src/test.js @@ -0,0 +1,5 @@ +function test(paramA) { + var variableA = paramA; + console.log(variableA); +} +test(); diff --git a/test/fixtures/component.a/task.a.js b/test/fixtures/component.a/task.a.js new file mode 100644 index 000000000..ea41b01de --- /dev/null +++ b/test/fixtures/component.a/task.a.js @@ -0,0 +1 @@ +module.exports = function () {}; diff --git a/test/fixtures/component.a/ui5-test-configPath.yaml b/test/fixtures/component.a/ui5-test-configPath.yaml new file mode 100644 index 000000000..9dfaec758 --- /dev/null +++ b/test/fixtures/component.a/ui5-test-configPath.yaml @@ -0,0 +1,7 @@ +--- +specVersion: "3.1" +type: component +metadata: + name: component.a +customConfiguration: + configPathTest: true \ No newline at end of file diff --git a/test/fixtures/component.a/ui5-test-corrupt.yaml b/test/fixtures/component.a/ui5-test-corrupt.yaml new file mode 100644 index 000000000..ecce9d7e7 --- /dev/null +++ b/test/fixtures/component.a/ui5-test-corrupt.yaml @@ -0,0 +1 @@ +|-\nfoo\nbar diff --git a/test/fixtures/component.a/ui5-test-empty.yaml b/test/fixtures/component.a/ui5-test-empty.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/component.a/ui5-test-error.yaml b/test/fixtures/component.a/ui5-test-error.yaml new file mode 100644 index 000000000..639b4889a --- /dev/null +++ b/test/fixtures/component.a/ui5-test-error.yaml @@ -0,0 +1,7 @@ +--- +specVersion: "3.1" +type: component +metadata: + name: component.a +xyz: + foo: true \ No newline at end of file diff --git a/test/fixtures/component.a/ui5.yaml b/test/fixtures/component.a/ui5.yaml new file mode 100644 index 000000000..f149b0e87 --- /dev/null +++ b/test/fixtures/component.a/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "3.1" +type: component +metadata: + name: component.a diff --git a/test/fixtures/component.h/pom.xml b/test/fixtures/component.h/pom.xml new file mode 100644 index 000000000..7ee5daf7a --- /dev/null +++ b/test/fixtures/component.h/pom.xml @@ -0,0 +1,41 @@ + + + + + + + 4.0.0 + + + + + com.sap.test + component.h + 1.0.0 + war + + + + + component.h + Simple SAPUI5 based component + + + + + + + component.h + + + + + diff --git a/test/fixtures/component.h/webapp-project.artifactId/manifest.json b/test/fixtures/component.h/webapp-project.artifactId/manifest.json new file mode 100644 index 000000000..7de6072ce --- /dev/null +++ b/test/fixtures/component.h/webapp-project.artifactId/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${project.artifactId}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/test/fixtures/component.h/webapp-properties.appId/manifest.json b/test/fixtures/component.h/webapp-properties.appId/manifest.json new file mode 100644 index 000000000..e1515df70 --- /dev/null +++ b/test/fixtures/component.h/webapp-properties.appId/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${appId}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/test/fixtures/component.h/webapp-properties.componentName/manifest.json b/test/fixtures/component.h/webapp-properties.componentName/manifest.json new file mode 100644 index 000000000..7d63e359c --- /dev/null +++ b/test/fixtures/component.h/webapp-properties.componentName/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "${componentName}", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/test/fixtures/component.h/webapp/Component.js b/test/fixtures/component.h/webapp/Component.js new file mode 100644 index 000000000..cb9bd4068 --- /dev/null +++ b/test/fixtures/component.h/webapp/Component.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent){ + "use strict"; + return UIComponent.extend('application.h.Component', { + metadata: { + manifest: "json" + } + }); +}); diff --git a/test/fixtures/component.h/webapp/manifest.json b/test/fixtures/component.h/webapp/manifest.json new file mode 100644 index 000000000..32b7e4a84 --- /dev/null +++ b/test/fixtures/component.h/webapp/manifest.json @@ -0,0 +1,13 @@ +{ + "_version": "1.1.0", + "sap.app": { + "_version": "1.1.0", + "id": "application.h", + "type": "application", + "applicationVersion": { + "version": "1.2.2" + }, + "embeds": ["embedded"], + "title": "{{title}}" + } +} diff --git a/test/fixtures/component.h/webapp/sectionsA/section1.js b/test/fixtures/component.h/webapp/sectionsA/section1.js new file mode 100644 index 000000000..ac4a81296 --- /dev/null +++ b/test/fixtures/component.h/webapp/sectionsA/section1.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/test/fixtures/component.h/webapp/sectionsA/section2.js b/test/fixtures/component.h/webapp/sectionsA/section2.js new file mode 100644 index 000000000..e009c8286 --- /dev/null +++ b/test/fixtures/component.h/webapp/sectionsA/section2.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/test/fixtures/component.h/webapp/sectionsA/section3.js b/test/fixtures/component.h/webapp/sectionsA/section3.js new file mode 100644 index 000000000..5fd9349d4 --- /dev/null +++ b/test/fixtures/component.h/webapp/sectionsA/section3.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/test/fixtures/component.h/webapp/sectionsB/section1.js b/test/fixtures/component.h/webapp/sectionsB/section1.js new file mode 100644 index 000000000..ac4a81296 --- /dev/null +++ b/test/fixtures/component.h/webapp/sectionsB/section1.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 1 included"); +}); diff --git a/test/fixtures/component.h/webapp/sectionsB/section2.js b/test/fixtures/component.h/webapp/sectionsB/section2.js new file mode 100644 index 000000000..e009c8286 --- /dev/null +++ b/test/fixtures/component.h/webapp/sectionsB/section2.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 2 included"); +}); diff --git a/test/fixtures/component.h/webapp/sectionsB/section3.js b/test/fixtures/component.h/webapp/sectionsB/section3.js new file mode 100644 index 000000000..5fd9349d4 --- /dev/null +++ b/test/fixtures/component.h/webapp/sectionsB/section3.js @@ -0,0 +1,3 @@ +sap.ui.define(["sap/m/Button"], function(Button) { + console.log("Section 3 included"); +}); diff --git a/test/lib/specifications/SpecificationVersion.js b/test/lib/specifications/SpecificationVersion.js index b50aa010d..64efc696f 100644 --- a/test/lib/specifications/SpecificationVersion.js +++ b/test/lib/specifications/SpecificationVersion.js @@ -67,6 +67,12 @@ test("(instance) satisfies", (t) => { t.is(new SpecificationVersion("2.2").satisfies("^2.2"), true); t.is(new SpecificationVersion("2.3").satisfies("^2.2"), true); + // 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); + t.is(new SpecificationVersion("3.1").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); @@ -162,6 +168,12 @@ test("(static) satisfies", (t) => { t.is(SpecificationVersion.satisfies("2.2", "^2.2"), true); t.is(SpecificationVersion.satisfies("2.3", "^2.2"), true); + // 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); + t.is(SpecificationVersion.satisfies("3.1", ">=2.2"), true); + // range: > 1.0 t.is(SpecificationVersion.satisfies("1.0", "> 1.0"), false); t.is(SpecificationVersion.satisfies("1.1", "> 1.0"), true); @@ -212,6 +224,45 @@ test("(static) low level comparator", (t) => { t.is(SpecificationVersion.neq("2.2", "2.2"), false); }); +test("(static) getVersionsForRange", (t) => { + // range: 1.x + t.deepEqual(SpecificationVersion.getVersionsForRange("1.x"), [ + "1.0", "1.1" + ]); + + // range: ^2.2 + t.deepEqual(SpecificationVersion.getVersionsForRange("^2.2"), [ + "2.2", "2.3", "2.4", "2.5", "2.6" + ]); + + // range: >=2.2 + t.deepEqual(SpecificationVersion.getVersionsForRange(">=2.2"), [ + "2.2", "2.3", "2.4", "2.5", "2.6", + "3.0", "3.1", + ]); + + // range: > 1.0 + t.deepEqual(SpecificationVersion.getVersionsForRange("> 1.0"), [ + "1.1", + "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", + "3.0", "3.1", + ]); + + // range: 2.2 - 2.4 + t.deepEqual(SpecificationVersion.getVersionsForRange("2.2 - 2.4"), [ + "2.2", "2.3", "2.4" + ]); + + // range: 0.1 || 1.0 - 1.1 || ^2.5 + t.deepEqual(SpecificationVersion.getVersionsForRange("0.1 || 1.0 - 1.1 || ^2.5"), [ + "0.1", "1.0", "1.1", + "2.5", "2.6" + ]); + + // Incorrect range returns empty array + t.deepEqual(SpecificationVersion.getVersionsForRange("not a range"), []); +}); + test("getSemverCompatibleVersion", (t) => { t.is(__localFunctions__.getSemverCompatibleVersion("0.1"), "0.1.0"); t.is(__localFunctions__.getSemverCompatibleVersion("1.1"), "1.1.0"); diff --git a/test/lib/specifications/types/Component.js b/test/lib/specifications/types/Component.js new file mode 100644 index 000000000..559d4a9f9 --- /dev/null +++ b/test/lib/specifications/types/Component.js @@ -0,0 +1,679 @@ +import test from "ava"; +import path from "node:path"; +import {fileURLToPath} from "node:url"; +import {createResource} from "@ui5/fs/resourceFactory"; +import sinonGlobal from "sinon"; +import Specification from "../../../../lib/specifications/Specification.js"; +import Component from "../../../../lib/specifications/types/Component.js"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const componentAPath = path.join(__dirname, "..", "..", "..", "fixtures", "component.a"); +const componentHPath = path.join(__dirname, "..", "..", "..", "fixtures", "component.h"); + +test.beforeEach((t) => { + t.context.sinon = sinonGlobal.createSandbox(); + t.context.projectInput = { + id: "component.a.id", + version: "1.0.0", + modulePath: componentAPath, + configuration: { + specVersion: "3.1", + kind: "project", + type: "component", + metadata: {name: "component.a"} + } + }; + + t.context.componentHInput = { + id: "component.h.id", + version: "1.0.0", + modulePath: componentHPath, + configuration: { + specVersion: "3.1", + kind: "project", + type: "component", + metadata: {name: "component.h"}, + resources: { + configuration: { + paths: { + src: "webapp" + } + } + } + } + }; +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); +}); + +test("Correct class", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + t.true(project instanceof Component, `Is an instance of the Component class`); +}); + +test("getNamespace", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + t.is(project.getNamespace(), "id1", + "Returned correct namespace"); +}); + +test("getSourcePath", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + t.is(project.getSourcePath(), path.join(componentAPath, "src"), + "Returned correct source path"); +}); + +test("getCachebusterSignatureType: Default", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + t.is(project.getCachebusterSignatureType(), "time", + "Returned correct default cachebuster signature type configuration"); +}); + +test("getCachebusterSignatureType: Configuration", async (t) => { + const {projectInput} = t.context; + projectInput.configuration.builder = { + cachebuster: { + signatureType: "hash" + } + }; + const project = await Specification.create(projectInput); + t.is(project.getCachebusterSignatureType(), "hash", + "Returned correct default cachebuster signature type configuration"); +}); + +test("Access project resources via reader: buildtime style", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + const reader = project.getReader(); + const resource = await reader.byPath("/resources/id1/manifest.json"); + t.truthy(resource, "Found the requested resource"); + t.is(resource.getPath(), "/resources/id1/manifest.json", "Resource has correct path"); +}); + +test("Access project resources via reader: flat style", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + const reader = project.getReader({style: "flat"}); + const resource = await reader.byPath("/manifest.json"); + t.truthy(resource, "Found the requested resource"); + t.is(resource.getPath(), "/manifest.json", "Resource has correct path"); +}); + +test("Access project resources via reader: runtime style", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + const reader = project.getReader({style: "runtime"}); + const resource = await reader.byPath("/resources/id1/manifest.json"); + t.truthy(resource, "Found the requested resource"); + t.is(resource.getPath(), "/resources/id1/manifest.json", "Resource has correct path"); +}); + +test("Access project resources via reader w/ builder excludes", async (t) => { + const {projectInput} = t.context; + const baselineProject = await Specification.create(projectInput); + + projectInput.configuration.builder = { + resources: { + excludes: ["**/manifest.json"] + } + }; + const excludesProject = await Specification.create(projectInput); + + // We now have two projects: One with excludes and one without + // Always compare the results of both to make sure a file is really excluded because of the + // configuration and not because of a typo or because of it's absence in the fixture + + t.is((await baselineProject.getReader({}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getReader({}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for default style"); + + t.is((await baselineProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for buildtime style"); + t.is((await excludesProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for buildtime style"); + + t.is((await baselineProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for dist style"); + t.is((await excludesProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for dist style"); + + t.is((await baselineProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for flat style"); + t.is((await excludesProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for flat style"); + + t.is((await baselineProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for runtime style"); + t.is((await excludesProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found excluded resource for runtime style"); +}); + +test("Access project resources via workspace w/ builder excludes", async (t) => { + const {projectInput} = t.context; + const baselineProject = await Specification.create(projectInput); + + projectInput.configuration.builder = { + resources: { + excludes: ["**/manifest.json"] + } + }; + const excludesProject = await Specification.create(projectInput); + + // We now have two projects: One with excludes and one without + // Always compare the results of both to make sure a file is really excluded because of the + // configuration and not because of a typo or because of it's absence in the fixture + + t.is((await baselineProject.getWorkspace().byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getWorkspace().byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for default style"); +}); + +test("Access project resources w/ absolute builder excludes", async (t) => { + const {projectInput} = t.context; + const baselineProject = await Specification.create(projectInput); + + projectInput.configuration.builder = { + resources: { + excludes: ["/resources/id1/manifest.json"] + } + }; + const excludesProject = await Specification.create(projectInput); + + // We now have two projects: One with excludes and one without + // Always compare the results of both to make sure a file is really excluded because of the + // configuration and not because of a typo or because of it's absence in the fixture + + t.is((await baselineProject.getReader({}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getReader({}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for default style"); + + t.is((await baselineProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for buildtime style"); + t.is((await excludesProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for buildtime style"); + + t.is((await baselineProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for dist style"); + t.is((await excludesProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for dist style"); + + t.is((await baselineProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for flat style"); + t.is((await excludesProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for flat style"); + + // Excludes are not applied for "runtime" style + t.is((await baselineProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for runtime style"); + t.is((await excludesProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found excluded resource for runtime style"); + + t.is((await baselineProject.getWorkspace().byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getWorkspace().byGlob("**/manifest.json")).length, 0, + "Did not find excluded resource for default style"); +}); + +test("Access project resources w/ relative builder excludes", async (t) => { + const {projectInput} = t.context; + const baselineProject = await Specification.create(projectInput); + + projectInput.configuration.builder = { + resources: { + excludes: ["manifest.json"] // Has no effect since component excludes must be absolute or use wildcards + } + }; + const excludesProject = await Specification.create(projectInput); + + // We now have two projects: One with excludes and one without + // Always compare the results of both to make sure a file is really excluded because of the + // configuration and not because of a typo or because of it's absence in the fixture + + t.is((await baselineProject.getReader({}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getReader({}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for default style"); + + t.is((await baselineProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for buildtime style"); + t.is((await excludesProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for buildtime style"); + + t.is((await baselineProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for dist style"); + t.is((await excludesProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for dist style"); + + t.is((await baselineProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for flat style"); + t.is((await excludesProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for flat style"); + + // Excludes are not applied for "runtime" style + t.is((await baselineProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for runtime style"); + t.is((await excludesProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found excluded resource for runtime style"); + + t.is((await baselineProject.getWorkspace().byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getWorkspace().byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for default style"); +}); + +test("Access project resources w/ incorrect builder excludes", async (t) => { + const {projectInput} = t.context; + const baselineProject = await Specification.create(projectInput); + + projectInput.configuration.builder = { + resources: { + excludes: ["/manifest.json"] + } + }; + const excludesProject = await Specification.create(projectInput); + + // We now have two projects: One with excludes and one without + // Always compare the results of both to make sure a file is really excluded because of the + // configuration and not because of a typo or because of it's absence in the fixture + + t.is((await baselineProject.getReader({}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getReader({}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for default style"); + + t.is((await baselineProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for buildtime style"); + t.is((await excludesProject.getReader({style: "buildtime"}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for buildtime style"); + + t.is((await baselineProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for dist style"); + t.is((await excludesProject.getReader({style: "dist"}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for dist style"); + + t.is((await baselineProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for flat style"); + t.is((await excludesProject.getReader({style: "flat"}).byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for flat style"); + + // Excludes are not applied for "runtime" style + t.is((await baselineProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for runtime style"); + t.is((await excludesProject.getReader({style: "runtime"}).byGlob("**/manifest.json")).length, 1, + "Found excluded resource for runtime style"); + + t.is((await baselineProject.getWorkspace().byGlob("**/manifest.json")).length, 1, + "Found resource in baseline project for default style"); + t.is((await excludesProject.getWorkspace().byGlob("**/manifest.json")).length, 1, + "Did not find excluded resource for default style"); +}); + +test("Modify project resources via workspace and access via flat and runtime readers", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + const workspace = project.getWorkspace(); + const workspaceResource = await workspace.byPath("/resources/id1/index.html"); + t.truthy(workspaceResource, "Found resource in workspace"); + + const newContent = (await workspaceResource.getString()).replace("Component A", "Some Name"); + workspaceResource.setString(newContent); + await workspace.write(workspaceResource); + + const flatReader = project.getReader({style: "flat"}); + const flatReaderResource = await flatReader.byPath("/index.html"); + t.truthy(flatReaderResource, "Found the requested resource byPath"); + t.is(flatReaderResource.getPath(), "/index.html", "Resource (byPath) has correct path"); + t.is(await flatReaderResource.getString(), newContent, "Found resource (byPath) has expected (changed) content"); + + const flatGlobResult = await flatReader.byGlob("**/index.html"); + t.is(flatGlobResult.length, 1, "Found the requested resource byGlob"); + t.is(flatGlobResult[0].getPath(), "/index.html", "Resource (byGlob) has correct path"); + t.is(await flatGlobResult[0].getString(), newContent, "Found resource (byGlob) has expected (changed) content"); + + const runtimeReader = project.getReader({style: "runtime"}); + const runtimeReaderResource = await runtimeReader.byPath("/resources/id1/index.html"); + t.truthy(runtimeReaderResource, "Found the requested resource byPath"); + t.is(runtimeReaderResource.getPath(), "/resources/id1/index.html", "Resource (byPath) has correct path"); + t.is(await runtimeReaderResource.getString(), newContent, "Found resource (byPath) has expected (changed) content"); + + const runtimeGlobResult = await runtimeReader.byGlob("**/index.html"); + t.is(runtimeGlobResult.length, 1, "Found the requested resource byGlob"); + t.is(runtimeGlobResult[0].getPath(), "/resources/id1/index.html", "Resource (byGlob) has correct path"); + t.is(await runtimeGlobResult[0].getString(), newContent, "Found resource (byGlob) has expected (changed) content"); +}); + + +test("Read and write resources outside of app namespace", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + const workspace = project.getWorkspace(); + + await workspace.write(createResource({ + path: "/resources/my-custom-bundle.js" + })); + + const buildtimeReader = project.getReader({style: "buildtime"}); + const buildtimeReaderResource = await buildtimeReader.byPath("/resources/my-custom-bundle.js"); + t.truthy(buildtimeReaderResource, "Found the requested resource byPath (buildtime)"); + t.is(buildtimeReaderResource.getPath(), "/resources/my-custom-bundle.js", + "Resource (byPath) has correct path (buildtime)"); + + const buildtimeGlobResult = await buildtimeReader.byGlob("**/my-custom-bundle.js"); + t.is(buildtimeGlobResult.length, 1, "Found the requested resource byGlob (buildtime)"); + t.is(buildtimeGlobResult[0].getPath(), "/resources/my-custom-bundle.js", + "Resource (byGlob) has correct path (buildtime)"); + + const flatReader = project.getReader({style: "flat"}); + const flatReaderResource = await flatReader.byPath("/resources/my-custom-bundle.js"); + t.falsy(flatReaderResource, "Resource outside of app namespace can't be read using flat reader"); + + const flatGlobResult = await flatReader.byGlob("**/my-custom-bundle.js"); + t.is(flatGlobResult.length, 0, "Resource outside of app namespace can't be found using flat reader"); + + const runtimeReader = project.getReader({style: "runtime"}); + const runtimeReaderResource = await runtimeReader.byPath("/resources/my-custom-bundle.js"); + t.truthy(runtimeReaderResource, "Found the requested resource byPath (runtime)"); + t.is(runtimeReaderResource.getPath(), "/resources/my-custom-bundle.js", + "Resource (byPath) has correct path (runtime)"); + + const runtimeGlobResult = await runtimeReader.byGlob("**/my-custom-bundle.js"); + t.is(runtimeGlobResult.length, 1, "Found the requested resource byGlob (runtime)"); + t.is(runtimeGlobResult[0].getPath(), "/resources/my-custom-bundle.js", + "Resource (byGlob) has correct path (runtime)"); +}); + +test("_configureAndValidatePaths: Default paths", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + + t.is(project._srcPath, "src", "Correct default path"); +}); + +test("_configureAndValidatePaths: Custom src directory", async (t) => { + const componentHPath = path.join(__dirname, "..", "..", "..", "fixtures", "component.h"); + const projectInput = { + id: "component.h.id", + version: "1.0.0", + modulePath: componentHPath, + configuration: { + specVersion: "3.1", + kind: "project", + type: "component", + metadata: {name: "component.h"}, + resources: { + configuration: { + paths: { + src: "webapp-properties.componentName" + } + } + } + } + }; + + const project = await Specification.create(projectInput); + + t.is(project._srcPath, "webapp-properties.componentName", "Correct path for src"); +}); + +test("_configureAndValidatePaths: src directory does not exist", async (t) => { + const {projectInput} = t.context; + projectInput.configuration.resources = { + configuration: { + paths: { + src: "does/not/exist" + } + } + }; + const err = await t.throwsAsync(Specification.create(projectInput)); + + t.is(err.message, "Unable to find source directory 'does/not/exist' in component project component.a"); +}); + +test("_getNamespaceFromManifestJson: No 'sap.app' configuration found", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + sinon.stub(project, "_getManifest").resolves({}); + + const error = await t.throwsAsync(project._getNamespaceFromManifestJson()); + t.is(error.message, "No sap.app/id configuration found in manifest.json of project component.a", + "Rejected with correct error message"); +}); + +test("_getNamespaceFromManifestJson: No component id in 'sap.app' configuration found", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + sinon.stub(project, "_getManifest").resolves({"sap.app": {}}); + + const error = await t.throwsAsync(project._getNamespaceFromManifestJson()); + t.is(error.message, "No sap.app/id configuration found in manifest.json of project component.a"); +}); + +test("_getNamespaceFromManifestJson: set namespace to id", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + sinon.stub(project, "_getManifest").resolves({"sap.app": {id: "my.id"}}); + + const namespace = await project._getNamespaceFromManifestJson(); + t.is(namespace, "my/id", "Returned correct namespace"); +}); + +test("_getNamespaceFromManifestAppDescVariant: No 'id' property found", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + sinon.stub(project, "_getManifest").resolves({}); + + const error = await t.throwsAsync(project._getNamespaceFromManifestAppDescVariant()); + t.is(error.message, `No "id" property found in manifest.appdescr_variant of project component.a`, + "Rejected with correct error message"); +}); + +test("_getNamespaceFromManifestAppDescVariant: set namespace to id", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + sinon.stub(project, "_getManifest").resolves({id: "my.id"}); + + const namespace = await project._getNamespaceFromManifestAppDescVariant(); + t.is(namespace, "my/id", "Returned correct namespace"); +}); + +test("_getNamespace: Correct fallback to manifest.appdescr_variant if manifest.json is missing", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + const _getManifestStub = sinon.stub(project, "_getManifest") + .onFirstCall().rejects({code: "ENOENT"}) + .onSecondCall().resolves({id: "my.id"}); + + const namespace = await project._getNamespace(); + t.is(namespace, "my/id", "Returned correct namespace"); + t.is(_getManifestStub.callCount, 2, "_getManifest called exactly twice"); + t.is(_getManifestStub.getCall(0).args[0], "/manifest.json", "_getManifest called for manifest.json first"); + t.is(_getManifestStub.getCall(1).args[0], "/manifest.appdescr_variant", + "_getManifest called for manifest.appdescr_variant in fallback"); +}); + +test("_getNamespace: Correct error message if fallback to manifest.appdescr_variant failed", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + const _getManifestStub = sinon.stub(project, "_getManifest") + .onFirstCall().rejects({code: "ENOENT"}) + .onSecondCall().rejects(new Error("EPON: Pony Error")); + + const error = await t.throwsAsync(project._getNamespace()); + t.is(error.message, "EPON: Pony Error", + "Rejected with correct error message"); + t.is(_getManifestStub.callCount, 2, "_getManifest called exactly twice"); + t.is(_getManifestStub.getCall(0).args[0], "/manifest.json", "_getManifest called for manifest.json first"); + t.is(_getManifestStub.getCall(1).args[0], "/manifest.appdescr_variant", + "_getManifest called for manifest.appdescr_variant in fallback"); +}); + +test("_getNamespace: Correct error message if fallback to manifest.appdescr_variant is not possible", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + const _getManifestStub = sinon.stub(project, "_getManifest") + .onFirstCall().rejects({message: "No such stable or directory: manifest.json", code: "ENOENT"}) + .onSecondCall().rejects({code: "ENOENT"}); // both files are missing + + const error = await t.throwsAsync(project._getNamespace()); + t.deepEqual(error.message, + "Could not find required manifest.json for project component.a: " + + "No such stable or directory: manifest.json", + "Rejected with correct error message"); + + t.is(_getManifestStub.callCount, 2, "_getManifest called exactly twice"); + t.is(_getManifestStub.getCall(0).args[0], "/manifest.json", "_getManifest called for manifest.json first"); + t.is(_getManifestStub.getCall(1).args[0], "/manifest.appdescr_variant", + "_getManifest called for manifest.appdescr_variant in fallback"); +}); + +test("_getNamespace: No fallback if manifest.json is present but failed to parse", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + const _getManifestStub = sinon.stub(project, "_getManifest") + .onFirstCall().rejects(new Error("EPON: Pony Error")); + + const error = await t.throwsAsync(project._getNamespace()); + t.is(error.message, "EPON: Pony Error", + "Rejected with correct error message"); + + t.is(_getManifestStub.callCount, 1, "_getManifest called exactly once"); + t.is(_getManifestStub.getCall(0).args[0], "/manifest.json", "_getManifest called for manifest.json only"); +}); + +test("_getManifest: reads correctly", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + + const content = await project._getManifest("/manifest.json"); + t.is(content._version, "1.1.0", "manifest.json content has been read"); +}); + +test("_getManifest: invalid JSON", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + + const byPathStub = sinon.stub().resolves({ + getString: async () => "no json" + }); + + project._getRawSourceReader = () => { + return { + byPath: byPathStub + }; + }; + + const error = await t.throwsAsync(project._getManifest("/some-manifest.json")); + t.regex(error.message, /^Failed to read \/some-manifest\.json for project component\.a: /, + "Rejected with correct error message"); + t.is(byPathStub.callCount, 1, "byPath got called once"); + t.is(byPathStub.getCall(0).args[0], "/some-manifest.json", "byPath got called with the correct argument"); +}); + +test.serial("_getManifest: File does not exist", async (t) => { + const {projectInput} = t.context; + const project = await Specification.create(projectInput); + + const error = await t.throwsAsync(project._getManifest("/does-not-exist.json")); + t.deepEqual(error.message, + "Failed to read /does-not-exist.json for project component.a: " + + "Could not find resource /does-not-exist.json in project component.a", + "Rejected with correct error message"); +}); + +test.serial("_getManifest: result is cached", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + + const byPathStub = sinon.stub().resolves({ + getString: async () => `{"pony": "no unicorn"}` + }); + + project._getRawSourceReader = () => { + return { + byPath: byPathStub + }; + }; + + const content = await project._getManifest("/some-manifest.json"); + t.deepEqual(content, {pony: "no unicorn"}, "Correct result on first call"); + + const content2 = await project._getManifest("/some-other-manifest.json"); + t.deepEqual(content2, {pony: "no unicorn"}, "Correct result on second call"); + + t.is(byPathStub.callCount, 2, "byPath got called exactly twice (and then cached)"); +}); + +test.serial("_getManifest: Caches successes and failures", async (t) => { + const {projectInput, sinon} = t.context; + const project = await Specification.create(projectInput); + + const getStringStub = sinon.stub() + .onFirstCall().rejects(new Error("EPON: Pony Error")) + .onSecondCall().resolves(`{"pony": "no unicorn"}`); + const byPathStub = sinon.stub().resolves({ + getString: getStringStub + }); + + project._getRawSourceReader = () => { + return { + byPath: byPathStub + }; + }; + + const error = await t.throwsAsync(project._getManifest("/some-manifest.json")); + t.deepEqual(error.message, + "Failed to read /some-manifest.json for project component.a: " + + "EPON: Pony Error", + "Rejected with correct error message"); + + const content = await project._getManifest("/some-other.manifest.json"); + t.deepEqual(content, {pony: "no unicorn"}, "Correct result on second call"); + + const error2 = await t.throwsAsync(project._getManifest("/some-manifest.json")); + t.deepEqual(error2.message, + "Failed to read /some-manifest.json for project component.a: " + + "EPON: Pony Error", + "From cache: Rejected with correct error message"); + + const content2 = await project._getManifest("/some-other.manifest.json"); + t.deepEqual(content2, {pony: "no unicorn"}, "From cache: Correct result on first call"); + + t.is(byPathStub.callCount, 2, + "byPath got called exactly twice (and then cached)"); +}); + +test("namespace: detect namespace from pom.xml via ${project.artifactId}", async (t) => { + const {componentHInput} = t.context; + componentHInput.configuration.resources.configuration.paths.src = "webapp-project.artifactId"; + const project = await Specification.create(componentHInput); + + t.is(project.getNamespace(), "component/h", + "namespace was successfully set since getJson provides the correct object structure"); +}); + +test("namespace: detect namespace from pom.xml via ${componentName} from properties", async (t) => { + const {componentHInput} = t.context; + componentHInput.configuration.resources.configuration.paths.src = "webapp-properties.componentName"; + const project = await Specification.create(componentHInput); + + t.is(project.getNamespace(), "component/h", + "namespace was successfully set since getJson provides the correct object structure"); +}); + +test("namespace: detect namespace from pom.xml via ${appId} from properties", async (t) => { + const {componentHInput} = t.context; + componentHInput.configuration.resources.configuration.paths.src = "webapp-properties.appId"; + + const error = await t.throwsAsync(Specification.create(componentHInput)); + t.deepEqual(error.message, "Failed to resolve namespace of project component.h: \"${appId}\"" + + " couldn't be resolved from maven property \"appId\" of pom.xml of project component.h"); +}); diff --git a/test/lib/validation/schema/__helper__/builder-bundleOptions.js b/test/lib/validation/schema/__helper__/builder-bundleOptions.js index 1ba0be8da..27278b974 100644 --- a/test/lib/validation/schema/__helper__/builder-bundleOptions.js +++ b/test/lib/validation/schema/__helper__/builder-bundleOptions.js @@ -1,17 +1,20 @@ +import SpecificationVersion from "../../../../../lib/specifications/SpecificationVersion.js"; + /** * Common test functionality for builder/bundles/bundleOptions section in config */ export default { /** - * Executes the tests for different kind of projects, e.g. "application", "library" + * Executes the tests for different kind of projects, e.g. "application", "component", "library" * * @param {Function} test ava test * @param {Function} assertValidation assertion function - * @param {string} type one of "application" and "library" + * @param {string} type one of "application", "component" and "library" */ defineTests: function(test, assertValidation, type) { - // Version specific tests - ["3.0"].forEach(function(specVersion) { + // Version specific tests (component type only became available with specVersion 3.1) + const range = type === "component" ? ">=3.1" : ">=3.0"; + SpecificationVersion.getVersionsForRange(range).forEach(function(specVersion) { test(`${type} (specVersion ${specVersion}): builder/bundles/bundleOptions`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/__helper__/customConfiguration.js b/test/lib/validation/schema/__helper__/customConfiguration.js index 356cce67b..607560658 100644 --- a/test/lib/validation/schema/__helper__/customConfiguration.js +++ b/test/lib/validation/schema/__helper__/customConfiguration.js @@ -1,41 +1,47 @@ +import SpecificationVersion from "../../../../../lib/specifications/SpecificationVersion.js"; + /** * Common test functionality for customConfiguration section in config */ export default { /** - * Executes the tests for different kind of projects, e.g. "application", "library", "theme-library" and "module" + * Executes the tests for different kind of projects, + * e.g. "application", "component", "library", "theme-library" and "module" * * @param {Function} test ava test * @param {Function} assertValidation assertion function * @param {string} type one of "project-shim", "server-middleware" "task", - * "application", "library", "theme-library" and "module" + * "application", "component", "library", "theme-library" and "module" * @param {object} additionalConfiguration additional configuration content */ defineTests: function(test, assertValidation, type, additionalConfiguration) { additionalConfiguration = additionalConfiguration || {}; - - // version specific tests for customConfiguration - test(`${type}: Invalid customConfiguration (specVersion 2.0)`, async (t) => { - await assertValidation(t, Object.assign({ - "specVersion": "2.0", - "type": type, - "metadata": { - "name": "my-" + type - }, - "customConfiguration": {} - }, additionalConfiguration), [ - { - dataPath: "", - keyword: "additionalProperties", - message: "should NOT have additional properties", - params: { - additionalProperty: "customConfiguration", + if (type !== "component") { // Component type only became available with specVersion 3.1 + // version specific tests for customConfiguration + test(`${type}: Invalid customConfiguration (specVersion 2.0)`, async (t) => { + await assertValidation(t, Object.assign({ + "specVersion": "2.0", + "type": type, + "metadata": { + "name": "my-" + type + }, + "customConfiguration": {} + }, additionalConfiguration), [ + { + dataPath: "", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "customConfiguration", + } } - } - ]); - }); + ]); + }); + } - ["2.6", "2.5", "2.4", "2.3", "2.2", "2.1"].forEach((specVersion) => { + // Component type only became available with specVersion 3.1 + const range = type === "component" ? ">=3.1" : ">=2.1"; + SpecificationVersion.getVersionsForRange(range).forEach((specVersion) => { test(`${type}: Valid customConfiguration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, Object.assign( { "specVersion": specVersion, diff --git a/test/lib/validation/schema/__helper__/extension.js b/test/lib/validation/schema/__helper__/extension.js index 69eb7d155..516bc889c 100644 --- a/test/lib/validation/schema/__helper__/extension.js +++ b/test/lib/validation/schema/__helper__/extension.js @@ -1,3 +1,4 @@ +import SpecificationVersion from "../../../../../lib/specifications/SpecificationVersion.js"; import customConfiguration from "./customConfiguration.js"; /** @@ -18,7 +19,7 @@ export default { customConfiguration.defineTests(test, assertValidation, type, additionalConfiguration); - ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { + SpecificationVersion.getVersionsForRange(">=2.0").forEach((specVersion) => { test(`kind: extension / type: ${type} basic (${specVersion})`, async (t) => { await assertValidation(t, Object.assign({ "specVersion": specVersion, @@ -67,7 +68,7 @@ export default { }); }); - ["2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { + SpecificationVersion.getVersionsForRange("2.0 - 2.6").forEach((specVersion) => { test(`kind: extension / type: ${type}: Invalid metadata.name (${specVersion})`, async (t) => { await assertValidation(t, Object.assign({ "specVersion": specVersion, @@ -86,7 +87,7 @@ export default { }); }); - ["3.0"].forEach((specVersion) => { + SpecificationVersion.getVersionsForRange(">=3.0").forEach((specVersion) => { test(`kind: extension / type: ${type}: Invalid metadata.name (${specVersion})`, async (t) => { await assertValidation(t, Object.assign({ "specVersion": specVersion, diff --git a/test/lib/validation/schema/__helper__/framework.js b/test/lib/validation/schema/__helper__/framework.js index d8d5ffd22..0e614b162 100644 --- a/test/lib/validation/schema/__helper__/framework.js +++ b/test/lib/validation/schema/__helper__/framework.js @@ -1,16 +1,21 @@ +import SpecificationVersion from "../../../../../lib/specifications/SpecificationVersion.js"; + /** * Common test functionality for framework section in config */ export default { /** - * Executes the tests for different types of kind project, e.g. "application", "library" and "theme-library" + * Executes the tests for different types of kind project, + * e.g. "application", "component", library" and "theme-library" * * @param {Function} test ava test * @param {Function} assertValidation assertion function - * @param {string} type one of "application", "library" and "theme-library" + * @param {string} type one of "application", "component", library" and "theme-library" */ defineTests: function(test, assertValidation, type) { - ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { + // Component type only became available with specVersion 3.1 + const range = type === "component" ? ">=3.1" : ">=2.0"; + SpecificationVersion.getVersionsForRange(range).forEach((specVersion) => { test(`${type} (specVersion ${specVersion}): framework configuration: OpenUI5`, async (t) => { const config = { "specVersion": specVersion, diff --git a/test/lib/validation/schema/__helper__/project.js b/test/lib/validation/schema/__helper__/project.js index 8d58ada78..c76b66cbf 100644 --- a/test/lib/validation/schema/__helper__/project.js +++ b/test/lib/validation/schema/__helper__/project.js @@ -1,3 +1,4 @@ +import SpecificationVersion from "../../../../../lib/specifications/SpecificationVersion.js"; import framework from "./framework.js"; import customConfiguration from "./customConfiguration.js"; import bundleOptions from "./builder-bundleOptions.js"; @@ -12,11 +13,11 @@ export default { * * @param {Function} test ava test * @param {Function} assertValidation assertion function - * @param {string} type one of "application", "library", "theme-library" and "module" + * @param {string} type one of "application", "component", "library", "theme-library" and "module" */ defineTests: function(test, assertValidation, type) { // framework tests - if (["application", "library", "theme-library"].includes(type)) { + if (["application", "library", "theme-library", "component"].includes(type)) { framework.defineTests(test, assertValidation, type); } @@ -24,12 +25,14 @@ export default { customConfiguration.defineTests(test, assertValidation, type); // builder.bundleOptions tests - if (["application", "library"].includes(type)) { + if (["application", "library", "component"].includes(type)) { bundleOptions.defineTests(test, assertValidation, type); } // version specific tests - ["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { + // Component type only became available with specVersion 3.1 + const range = type === "component" ? ">=3.1" : ">=2.0"; + SpecificationVersion.getVersionsForRange(range).forEach((specVersion) => { // tests for all kinds and version 2.0 and above test(`${type} (specVersion ${specVersion}): No metadata`, async (t) => { await assertValidation(t, { @@ -260,28 +263,32 @@ export default { }); }); - ["2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { - test(`${type} (specVersion ${specVersion}): Invalid metadata.name`, async (t) => { - await assertValidation(t, { - "specVersion": specVersion, - "type": type, - "metadata": { - "name": {} - } - }, [ - { - dataPath: "/metadata/name", - keyword: "type", - message: "should be string", - params: { - type: "string" + if (type !== "component") { + ["2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { + test(`${type} (specVersion ${specVersion}): Invalid metadata.name`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "type": type, + "metadata": { + "name": {} } - } - ]); + }, [ + { + dataPath: "/metadata/name", + keyword: "type", + message: "should be string", + params: { + type: "string" + } + } + ]); + }); }); - }); + } - ["3.0"].forEach((specVersion) => { + // Component type only became available with specVersion 3.1 + const v3Range = type === "component" ? ">=3.1" : ">=3.0"; + SpecificationVersion.getVersionsForRange(v3Range).forEach((specVersion) => { test(`${type} (specVersion ${specVersion}): Invalid metadata.name`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/specVersion/kind/extension.js b/test/lib/validation/schema/specVersion/kind/extension.js index 8fefe5bbe..54fbb1fdc 100644 --- a/test/lib/validation/schema/specVersion/kind/extension.js +++ b/test/lib/validation/schema/specVersion/kind/extension.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../lib/validation/ValidationError.js"; @@ -38,7 +39,8 @@ test.after.always((t) => { }; t.context.ajvCoverage.verify(thresholds); }); -["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { + +SpecificationVersion.getVersionsForRange(">=2.0").forEach((specVersion) => { test(`Type project-shim (${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/specVersion/kind/extension/project-shim.js b/test/lib/validation/schema/specVersion/kind/extension/project-shim.js index daff1b0bb..dfad329fe 100644 --- a/test/lib/validation/schema/specVersion/kind/extension/project-shim.js +++ b/test/lib/validation/schema/specVersion/kind/extension/project-shim.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; @@ -45,7 +46,7 @@ test.after.always((t) => { t.context.ajvCoverage.verify(thresholds); }); -["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { +SpecificationVersion.getVersionsForRange(">=2.0").forEach((specVersion) => { test(`kind: extension / type: project-shim (${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -127,7 +128,7 @@ test.after.always((t) => { }); }); -["3.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=3.0").forEach(function(specVersion) { test(`Invalid extension name (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/specVersion/kind/extension/server-middleware.js b/test/lib/validation/schema/specVersion/kind/extension/server-middleware.js index f887078eb..39b5efe6f 100644 --- a/test/lib/validation/schema/specVersion/kind/extension/server-middleware.js +++ b/test/lib/validation/schema/specVersion/kind/extension/server-middleware.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; @@ -45,7 +46,7 @@ test.after.always((t) => { t.context.ajvCoverage.verify(thresholds); }); -["3.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=3.0").forEach(function(specVersion) { test(`Invalid extension name (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/specVersion/kind/extension/task.js b/test/lib/validation/schema/specVersion/kind/extension/task.js index a9dfe9452..f2a6a1017 100644 --- a/test/lib/validation/schema/specVersion/kind/extension/task.js +++ b/test/lib/validation/schema/specVersion/kind/extension/task.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; @@ -45,7 +46,7 @@ test.after.always((t) => { t.context.ajvCoverage.verify(thresholds); }); -["3.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=3.0").forEach(function(specVersion) { test(`Invalid extension name (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/specVersion/kind/project.js b/test/lib/validation/schema/specVersion/kind/project.js index ba9d09ca5..42e088330 100644 --- a/test/lib/validation/schema/specVersion/kind/project.js +++ b/test/lib/validation/schema/specVersion/kind/project.js @@ -123,6 +123,27 @@ test("Type module (no kind)", async (t) => { }); }); +test("Type component", async (t) => { + await assertValidation(t, { + "specVersion": "3.1", + "kind": "project", + "type": "component", + "metadata": { + "name": "my-component" + } + }); +}); + +test("Type component (no kind)", async (t) => { + await assertValidation(t, { + "specVersion": "3.1", + "type": "component", + "metadata": { + "name": "my-component" + } + }); +}); + test("No type", async (t) => { await assertValidation(t, { "specVersion": "2.0", @@ -171,6 +192,7 @@ test("Invalid type", async (t) => { params: { allowedValues: [ "application", + "component", "library", "theme-library", "module", diff --git a/test/lib/validation/schema/specVersion/kind/project/application.js b/test/lib/validation/schema/specVersion/kind/project/application.js index 54cf855f5..ca005d415 100644 --- a/test/lib/validation/schema/specVersion/kind/project/application.js +++ b/test/lib/validation/schema/specVersion/kind/project/application.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; @@ -45,7 +46,7 @@ test.after.always((t) => { t.context.ajvCoverage.verify(thresholds); }); -["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.0").forEach(function(specVersion) { test(`Valid configuration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -435,7 +436,7 @@ test.after.always((t) => { keyword: "enum", message: "should be equal to one of the allowed values", params: { - allowedValues: ["3.0", "2.6", "2.5", "2.4"].includes(specVersion) ? [ + allowedValues: ["3.1", "3.0", "2.6", "2.5", "2.4"].includes(specVersion) ? [ "raw", "preload", "require", @@ -509,7 +510,7 @@ test.after.always((t) => { }); }); -["2.2", "2.1", "2.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange("2.0 - 2.2").forEach(function(specVersion) { test(`Unsupported builder/componentPreload/excludes configuration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -539,7 +540,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5", "2.4", "2.3"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.3").forEach(function(specVersion) { test(`application (specVersion ${specVersion}): builder/componentPreload/excludes`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -636,7 +637,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5", "2.4"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.4").forEach(function(specVersion) { // Unsupported cases for older spec-versions already tested via "allowedValues" comparison above test(`application (specVersion ${specVersion}): builder/bundles/bundleDefinition/sections/mode: bundleInfo`, async (t) => { @@ -664,7 +665,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.5").forEach(function(specVersion) { test(`application (specVersion ${specVersion}): builder/settings/includeDependency*`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -845,7 +846,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.6").forEach(function(specVersion) { test(`application (specVersion ${specVersion}): builder/minification/excludes`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -942,7 +943,7 @@ test.after.always((t) => { }); }); -["3.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=3.0").forEach(function(specVersion) { test(`Invalid project name (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/specVersion/kind/project/component.js b/test/lib/validation/schema/specVersion/kind/project/component.js new file mode 100644 index 000000000..3e8e604db --- /dev/null +++ b/test/lib/validation/schema/specVersion/kind/project/component.js @@ -0,0 +1,968 @@ +import test from "ava"; +import Ajv from "ajv"; +import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; +import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; +import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; +import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; +import project from "../../../__helper__/project.js"; + +async function assertValidation(t, config, expectedErrors = undefined) { + const validation = t.context.validator.validate({config, project: {id: "my-project"}}); + if (expectedErrors) { + const validationError = await t.throwsAsync(validation, { + instanceOf: ValidationError, + name: "ValidationError" + }); + validationError.errors.forEach((error) => { + delete error.schemaPath; + if (error.params && Array.isArray(error.params.errors)) { + error.params.errors.forEach(($) => { + delete $.schemaPath; + }); + } + }); + t.deepEqual(validationError.errors, expectedErrors); + } else { + await t.notThrowsAsync(validation); + } +} + +test.before((t) => { + t.context.validator = new Validator({Ajv, ajvErrors, schemaName: "ui5"}); + t.context.ajvCoverage = new AjvCoverage(t.context.validator.ajv, { + includes: ["schema/specVersion/kind/project/component.json"] + }); +}); + +test.after.always((t) => { + t.context.ajvCoverage.createReport("html", {dir: "coverage/ajv-project-component"}); + const thresholds = { + statements: 80, + branches: 75, + functions: 100, + lines: 80 + }; + t.context.ajvCoverage.verify(thresholds); +}); + +SpecificationVersion.getVersionsForRange(">=3.1").forEach(function(specVersion) { + test(`Valid configuration (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "kind": "project", + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "okay" + }, + "resources": { + "configuration": { + "propertiesFileSourceEncoding": "UTF-8", + "paths": { + "src": "/my/path" + } + } + }, + "builder": { + "resources": { + "excludes": [ + "/resources/some/project/name/test_results/**", + "/test-resources/**", + "!/test-resources/some/project/name/demo-app/**" + ] + }, + "bundles": [ + { + "bundleDefinition": { + "name": "sap-ui-custom.js", + "defaultFileTypes": [ + ".js" + ], + "sections": [ + { + "name": "my-raw-section", + "mode": "raw", + "filters": [ + "ui5loader-autoconfig.js" + ], + "resolve": true, + "resolveConditional": true, + "renderer": true, + "sort": true + }, + { + "mode": "provided", + "filters": [ + "ui5loader-autoconfig.js" + ], + "resolve": false, + "resolveConditional": false, + "renderer": false, + "sort": false, + "declareRawModules": true + } + ] + }, + "bundleOptions": { + "optimize": true, + "decorateBootstrapModule": true, + "addTryCatchRestartWrapper": true, + "usePredefineCalls": true + } + }, + { + "bundleDefinition": { + "name": "app.js", + "defaultFileTypes": [ + ".js" + ], + "sections": [ + { + "name": "some-app-preload", + "mode": "preload", + "filters": [ + "some/app/Component.js" + ], + "resolve": true, + "sort": true, + "declareRawModules": false + }, + { + "mode": "require", + "filters": [ + "ui5loader-autoconfig.js" + ], + "resolve": true + } + ] + }, + "bundleOptions": { + "optimize": true, + "numberOfParts": 3 + } + } + ], + "componentPreload": { + "paths": [ + "some/glob/**/pattern/Component.js", + "some/other/glob/**/pattern/Component.js" + ], + "namespaces": [ + "some/namespace", + "some/other/namespace" + ] + }, + "cachebuster": { + "signatureType": "hash" + }, + "customTasks": [ + { + "name": "custom-task-1", + "beforeTask": "replaceCopyright", + "configuration": { + "some-key": "some value" + } + }, + { + "name": "custom-task-2", + "afterTask": "custom-task-1", + "configuration": { + "color": "blue" + } + }, + { + "name": "custom-task-2", + "beforeTask": "not-valid", + "configuration": false + } + ] + }, + "server": { + "settings": { + "httpPort": 1337, + "httpsPort": 1443 + }, + "customMiddleware": [ + { + "name": "myCustomMiddleware", + "mountPath": "/myapp", + "afterMiddleware": "compression", + "configuration": { + "debug": true + } + }, + { + "name": "myCustomMiddleware-2", + "beforeMiddleware": "myCustomMiddleware", + "configuration": { + "debug": true + } + } + ] + } + }); + }); + + test(`Invalid resources configuration (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test" + }, + "resources": { + "configuration": { + "propertiesFileSourceEncoding": "FOO", + "paths": { + "app": "src", + "src": { + "path": "invalid" + } + }, + "notAllowed": true + }, + "notAllowed": true + } + }, [ + { + dataPath: "/resources", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "notAllowed", + } + }, + { + dataPath: "/resources/configuration", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "notAllowed", + } + }, + { + dataPath: "/resources/configuration/propertiesFileSourceEncoding", + keyword: "enum", + message: "should be equal to one of the allowed values", + params: { + allowedValues: [ + "UTF-8", + "ISO-8859-1" + ], + } + }, + { + dataPath: "/resources/configuration/paths", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "app", + } + }, + { + dataPath: "/resources/configuration/paths/src", + keyword: "type", + message: "should be string", + params: { + type: "string" + } + } + ]); + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test" + }, + "resources": { + "configuration": { + "paths": "src" + } + } + }, [ + { + dataPath: "/resources/configuration/paths", + keyword: "type", + message: "should be object", + params: { + type: "object" + } + } + ]); + }); + + test(`Invalid builder configuration (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + // jsdoc is not supported for type component + "jsdoc": { + "excludes": [ + "some/project/name/thirdparty/**" + ] + }, + "bundles": [ + { + "bundleDefinition": { + "name": "sap-ui-custom.js", + "defaultFileTypes": [ + ".js" + ], + "sections": [ + { + "name": true, + "mode": "raw", + "filters": [ + "ui5loader-autoconfig.js" + ], + "resolve": true, + "sort": true, + "declareModules": true + } + ] + }, + "bundleOptions": { + "optimize": true + } + }, + { + "bundleDefinition": { + "defaultFileTypes": [ + ".js", true + ], + "sections": [ + { + "filters": [ + "some/app/Component.js" + ], + "resolve": true, + "sort": true, + "declareRawModules": [] + }, + { + "mode": "provide", + "filters": "*", + "resolve": true + } + ] + }, + "bundleOptions": { + "optimize": "true", + "numberOfParts": "3", + "notAllowed": true + } + } + ], + "componentPreload": { + "path": "some/invalid/path", + "paths": "some/invalid/glob/**/pattern/Component.js", + "namespaces": "some/invalid/namespace", + }, + "libraryPreload": {} // Only supported for type library + } + }, [ + { + dataPath: "/builder", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "jsdoc" + } + }, + { + dataPath: "/builder", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "libraryPreload" + } + }, + { + dataPath: "/builder/bundles/0/bundleDefinition/sections/0", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "declareModules", + } + }, + { + dataPath: "/builder/bundles/0/bundleDefinition/sections/0/name", + keyword: "type", + message: "should be string", + params: { + type: "string", + } + }, + { + dataPath: "/builder/bundles/1/bundleDefinition", + keyword: "required", + message: "should have required property 'name'", + params: { + missingProperty: "name", + } + }, + { + dataPath: "/builder/bundles/1/bundleDefinition/defaultFileTypes/1", + keyword: "type", + message: "should be string", + params: { + type: "string", + } + }, + { + dataPath: "/builder/bundles/1/bundleDefinition/sections/0", + keyword: "required", + message: "should have required property 'mode'", + params: { + missingProperty: "mode", + } + }, + { + dataPath: "/builder/bundles/1/bundleDefinition/sections/0/declareRawModules", + keyword: "type", + message: "should be boolean", + params: { + type: "boolean", + } + }, + { + dataPath: "/builder/bundles/1/bundleDefinition/sections/1/mode", + keyword: "enum", + message: "should be equal to one of the allowed values", + params: { + allowedValues: ["3.1"].includes(specVersion) ? [ + "raw", + "preload", + "require", + "provided", + "bundleInfo" + ] : [ + "raw", + "preload", + "require", + "provided" + ] + } + }, + { + dataPath: "/builder/bundles/1/bundleDefinition/sections/1/filters", + keyword: "type", + message: "should be array", + params: { + type: "array", + } + }, + { + dataPath: "/builder/bundles/1/bundleOptions", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "notAllowed", + } + }, + { + dataPath: "/builder/bundles/1/bundleOptions/optimize", + keyword: "type", + message: "should be boolean", + params: { + type: "boolean", + } + }, + { + dataPath: "/builder/bundles/1/bundleOptions/numberOfParts", + keyword: "type", + message: "should be number", + params: { + type: "number", + } + }, + { + dataPath: "/builder/componentPreload", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "path", + } + }, + { + dataPath: "/builder/componentPreload/paths", + keyword: "type", + message: "should be array", + params: { + type: "array", + } + }, + { + dataPath: "/builder/componentPreload/namespaces", + keyword: "type", + message: "should be array", + params: { + type: "array", + } + } + ]); + }); + test(`component (specVersion ${specVersion}): builder/componentPreload/excludes`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "kind": "project", + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "componentPreload": { + "excludes": [ + "some/excluded/files/**", + "some/other/excluded/files/**" + ] + } + } + }); + }); + test(`Invalid builder/componentPreload/excludes configuration (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "componentPreload": { + "excludes": "some/excluded/files/**" + } + } + }, [ + { + dataPath: "/builder/componentPreload/excludes", + keyword: "type", + message: "should be array", + params: { + type: "array", + }, + }, + ]); + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "componentPreload": { + "excludes": [ + true, + 1, + {} + ], + "notAllowed": true + } + } + }, [ + { + dataPath: "/builder/componentPreload", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "notAllowed", + }, + }, + { + dataPath: "/builder/componentPreload/excludes/0", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/componentPreload/excludes/1", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/componentPreload/excludes/2", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + ]); + }); + test(`component (specVersion ${specVersion}): builder/bundles/bundleDefinition/sections/mode: bundleInfo`, + async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "kind": "project", + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "bundles": [{ + "bundleDefinition": { + "name": "my-bundle.js", + "sections": [{ + "name": "my-bundle-info", + "mode": "bundleInfo", + "filters": [] + }] + } + }] + } + }); + }); + test(`component (specVersion ${specVersion}): builder/settings/includeDependency*`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "kind": "project", + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "settings": { + "includeDependency": [ + "sap.a", + "sap.b" + ], + "includeDependencyRegExp": [ + ".ui.[a-z]+", + "^sap.[mf]$" + ], + "includeDependencyTree": [ + "sap.c", + "sap.d" + ] + } + } + }); + }); + test(`Invalid builder/settings/includeDependency* configuration (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "settings": { + "includeDependency": "a", + "includeDependencyRegExp": "b", + "includeDependencyTree": "c" + } + } + }, [ + { + dataPath: "/builder/settings/includeDependency", + keyword: "type", + message: "should be array", + params: { + type: "array", + }, + }, + { + dataPath: "/builder/settings/includeDependencyRegExp", + keyword: "type", + message: "should be array", + params: { + type: "array", + }, + }, + { + dataPath: "/builder/settings/includeDependencyTree", + keyword: "type", + message: "should be array", + params: { + type: "array", + }, + }, + ]); + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "settings": { + "includeDependency": [ + true, + 1, + {} + ], + "includeDependencyRegExp": [ + true, + 1, + {} + ], + "includeDependencyTree": [ + true, + 1, + {} + ], + "notAllowed": true + } + } + }, [ + { + dataPath: "/builder/settings", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "notAllowed", + }, + }, + { + dataPath: "/builder/settings/includeDependency/0", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependency/1", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependency/2", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependencyRegExp/0", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependencyRegExp/1", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependencyRegExp/2", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependencyTree/0", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependencyTree/1", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/settings/includeDependencyTree/2", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + ]); + }); + test(`component (specVersion ${specVersion}): builder/minification/excludes`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "kind": "project", + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "minification": { + "excludes": [ + "some/excluded/files/**", + "some/other/excluded/files/**" + ] + } + } + }); + }); + test(`Invalid builder/minification/excludes configuration (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "minification": { + "excludes": "some/excluded/files/**" + } + } + }, [ + { + dataPath: "/builder/minification/excludes", + keyword: "type", + message: "should be array", + params: { + type: "array", + }, + }, + ]); + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "com.sap.ui5.test", + "copyright": "yes" + }, + "builder": { + "minification": { + "excludes": [ + true, + 1, + {} + ], + "notAllowed": true + } + } + }, [ + { + dataPath: "/builder/minification", + keyword: "additionalProperties", + message: "should NOT have additional properties", + params: { + additionalProperty: "notAllowed", + }, + }, + { + dataPath: "/builder/minification/excludes/0", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/minification/excludes/1", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + { + dataPath: "/builder/minification/excludes/2", + keyword: "type", + message: "should be string", + params: { + type: "string", + }, + }, + ]); + }); + test(`Invalid project name (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "illegal/name" + } + }, [{ + dataPath: "/metadata/name", + keyword: "errorMessage", + message: `Not a valid project name. It must consist of lowercase alphanumeric characters, dash, underscore, and period only. Additionally, it may contain an npm-style package scope. For details, see: https://sap.github.io/ui5-tooling/stable/pages/Configuration/#name`, + params: { + errors: [{ + dataPath: "/metadata/name", + keyword: "pattern", + message: `should match pattern "^(?:@[0-9a-z-_.]+\\/)?[a-z][0-9a-z-_.]*$"`, + params: { + pattern: "^(?:@[0-9a-z-_.]+\\/)?[a-z][0-9a-z-_.]*$", + }, + }] + }, + }]); + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "a" + } + }, [{ + dataPath: "/metadata/name", + keyword: "errorMessage", + message: `Not a valid project name. It must consist of lowercase alphanumeric characters, dash, underscore, and period only. Additionally, it may contain an npm-style package scope. For details, see: https://sap.github.io/ui5-tooling/stable/pages/Configuration/#name`, + params: { + errors: [{ + dataPath: "/metadata/name", + keyword: "minLength", + message: "should NOT be shorter than 3 characters", + params: { + limit: 3, + }, + }] + }, + }]); + await assertValidation(t, { + "specVersion": specVersion, + "type": "component", + "metadata": { + "name": "a".repeat(81) + } + }, [{ + dataPath: "/metadata/name", + keyword: "errorMessage", + message: `Not a valid project name. It must consist of lowercase alphanumeric characters, dash, underscore, and period only. Additionally, it may contain an npm-style package scope. For details, see: https://sap.github.io/ui5-tooling/stable/pages/Configuration/#name`, + params: { + errors: [{ + dataPath: "/metadata/name", + keyword: "maxLength", + message: "should NOT be longer than 80 characters", + params: { + limit: 80, + }, + }] + }, + }]); + }); +}); + +project.defineTests(test, assertValidation, "component"); diff --git a/test/lib/validation/schema/specVersion/kind/project/library.js b/test/lib/validation/schema/specVersion/kind/project/library.js index 59c3a9649..baf248fa1 100644 --- a/test/lib/validation/schema/specVersion/kind/project/library.js +++ b/test/lib/validation/schema/specVersion/kind/project/library.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; @@ -45,7 +46,7 @@ test.after.always((t) => { t.context.ajvCoverage.verify(thresholds); }); -["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.0").forEach(function(specVersion) { test(`library (specVersion ${specVersion}): Valid configuration`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -401,7 +402,7 @@ test.after.always((t) => { keyword: "enum", message: "should be equal to one of the allowed values", params: { - allowedValues: ["3.0", "2.6", "2.5", "2.4"].includes(specVersion) ? [ + allowedValues: ["3.1", "3.0", "2.6", "2.5", "2.4"].includes(specVersion) ? [ "raw", "preload", "require", @@ -564,7 +565,7 @@ test.after.always((t) => { }); }); -["2.2", "2.1", "2.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange("2.0 - 2.2").forEach(function(specVersion) { test(`Unsupported builder/libraryPreload configuration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -616,7 +617,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5", "2.4", "2.3"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.3").forEach(function(specVersion) { test(`library (specVersion ${specVersion}): builder/libraryPreload/excludes`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -809,7 +810,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5", "2.4"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.4").forEach(function(specVersion) { // Unsupported cases for older spec-versions already tested via "allowedValues" comparison above test(`library (specVersion ${specVersion}): builder/bundles/bundleDefinition/sections/mode: bundleInfo`, async (t) => { @@ -837,7 +838,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.5").forEach(function(specVersion) { test(`library (specVersion ${specVersion}): builder/settings/includeDependency*`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -1018,7 +1019,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.6").forEach(function(specVersion) { test(`library (specVersion ${specVersion}): builder/minification/excludes`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -1115,7 +1116,7 @@ test.after.always((t) => { }); }); -["3.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=3.0").forEach(function(specVersion) { test(`Invalid project name (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/specVersion/kind/project/module.js b/test/lib/validation/schema/specVersion/kind/project/module.js index 4c3b4715c..e49b4769c 100644 --- a/test/lib/validation/schema/specVersion/kind/project/module.js +++ b/test/lib/validation/schema/specVersion/kind/project/module.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; @@ -45,7 +46,7 @@ test.after.always((t) => { t.context.ajvCoverage.verify(thresholds); }); -["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { +SpecificationVersion.getVersionsForRange(">=2.0").forEach((specVersion) => { test(`Valid configuration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -106,7 +107,7 @@ test.after.always((t) => { }); }); -["2.4", "2.3", "2.2", "2.1", "2.0"].forEach((specVersion) => { +SpecificationVersion.getVersionsForRange("2.0 - 2.4").forEach((specVersion) => { test(`No server configuration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -144,7 +145,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.5").forEach(function(specVersion) { test(`Server configuration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -349,7 +350,7 @@ test.after.always((t) => { }); }); -["3.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=3.0").forEach(function(specVersion) { test(`Invalid project name (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -417,4 +418,25 @@ test.after.always((t) => { }); }); +SpecificationVersion.getVersionsForRange(">=3.1").forEach(function(specVersion) { + test(`Builder resource excludes (specVersion ${specVersion})`, async (t) => { + await assertValidation(t, { + "specVersion": specVersion, + "kind": "project", + "type": "module", + "metadata": { + "name": "my-module" + }, + "builder": { + "resources": { + "excludes": [ + "/resources/some/project/name/test_results/**", + "!/test-resources/some/project/name/demo-app/**" + ] + } + } + }); + }); +}); + project.defineTests(test, assertValidation, "module"); diff --git a/test/lib/validation/schema/specVersion/kind/project/theme-library.js b/test/lib/validation/schema/specVersion/kind/project/theme-library.js index fb0273e1c..eff5bdbf0 100644 --- a/test/lib/validation/schema/specVersion/kind/project/theme-library.js +++ b/test/lib/validation/schema/specVersion/kind/project/theme-library.js @@ -1,6 +1,7 @@ import test from "ava"; import Ajv from "ajv"; import ajvErrors from "ajv-errors"; +import SpecificationVersion from "../../../../../../../lib/specifications/SpecificationVersion.js"; import AjvCoverage from "../../../../../../utils/AjvCoverage.js"; import {_Validator as Validator} from "../../../../../../../lib/validation/validator.js"; import ValidationError from "../../../../../../../lib/validation/ValidationError.js"; @@ -45,8 +46,7 @@ test.after.always((t) => { t.context.ajvCoverage.verify(thresholds); }); - -["3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.0").forEach(function(specVersion) { test(`Valid configuration (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -167,7 +167,7 @@ test.after.always((t) => { }); }); -["3.0", "2.6", "2.5"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=2.5").forEach(function(specVersion) { test(`theme-library (specVersion ${specVersion}): builder/settings/includeDependency*`, async (t) => { await assertValidation(t, { "specVersion": specVersion, @@ -348,7 +348,7 @@ test.after.always((t) => { }); }); -["3.0"].forEach(function(specVersion) { +SpecificationVersion.getVersionsForRange(">=3.0").forEach(function(specVersion) { test(`Invalid project name (specVersion ${specVersion})`, async (t) => { await assertValidation(t, { "specVersion": specVersion, diff --git a/test/lib/validation/schema/ui5.js b/test/lib/validation/schema/ui5.js index 433aed2a5..19d83cf2c 100644 --- a/test/lib/validation/schema/ui5.js +++ b/test/lib/validation/schema/ui5.js @@ -102,7 +102,7 @@ test("Invalid specVersion", async (t) => { message: `Unsupported "specVersion" Your UI5 CLI installation might be outdated. -Supported specification versions: "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0", "1.1", "1.0", "0.1" +Supported specification versions: "3.1", "3.0", "2.6", "2.5", "2.4", "2.3", "2.2", "2.1", "2.0", "1.1", "1.0", "0.1" For details, see: https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions`, params: { errors: [ @@ -112,6 +112,7 @@ For details, see: https://sap.github.io/ui5-tooling/pages/Configuration/#specifi message: "should be equal to one of the allowed values", params: { allowedValues: [ + "3.1", "3.0", "2.6", "2.5", @@ -144,6 +145,7 @@ test("Invalid type", async (t) => { params: { allowedValues: [ "application", + "component", "library", "theme-library", "module"