diff --git a/README.md b/README.md index 3ade02b..e50d9ea 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Adobe Experience Platform Tags is a next-generation tag management solution enab The extension validator helps extension developers validate that an extension package is well-structured. Namely, it verifies that: 1. The extension has a [manifest](https://experienceleague.adobe.com/docs/experience-platform/tags/extension-dev/manifest.html?lang=en) (`extension.json`) matching the expected structure. -2. All referenced directories and files exist at the specified locations within the extension directory. +2. All referenced directories and files exist at the specified locations within the extension directory. The manifest's `viewBasePath` must point to an existing directory (not a file, and not a missing path); at least one file must exist under that path. For more information about developing an extension for Tags, please visit our [extension development guide](https://experienceleague.adobe.com/docs/experience-platform/tags/extension-dev/overview.html?lang=en). @@ -45,6 +45,49 @@ const error = validate(require('./extension.json')); console.log(error); ``` +### Using the validator in a browser or bundled app + +Browser (or other non-Node) consumers should use the `@adobe/reactor-validator/validate` subpath so they do not pull in Node-only code. The default export (`@adobe/reactor-validator`) scans the filesystem and is for Node only. + +The `validate` function has the signature `(extensionDescriptor, fileList)`: + +- **extensionDescriptor:** The parsed extension manifest (e.g. from `extension.json`). +- **fileList:** An array of relative path strings for every file in the extension package (e.g. from a folder picker or zip). The caller is responsible for gathering this list; the validator only checks that required paths are present. +- **Returns:** `undefined` if valid, or an error string on the first problem found. + +```javascript +import validate from '@adobe/reactor-validator/validate'; + +const error = validate(manifestJson, Array.from(filesMap.keys())); +if (error) { + console.error(error); +} +``` + +**Webpack alias for `ajv`:** The `validate` module depends on `ajv-draft-04`, which in turn requires `ajv` and resolves internal paths (e.g. `ajv/dist/core`). If your app does not list `ajv` as a direct dependency, add a resolve alias so the bundler uses the validator's `ajv`: + +- Resolve via an **exported** path (e.g. `@adobe/reactor-validator/validate`), not `package.json`, to avoid `ERR_PACKAGE_PATH_NOT_EXPORTED`. +- Alias to the **package directory** of `ajv`, not to `ajv.js`, so subpaths resolve and Watchpack ENOTDIR errors are avoided. + +In your webpack config: + +```javascript +const path = require('path'); + +// Inside module.exports or your config object: +resolve: { + alias: { + 'ajv': path.join( + path.dirname(path.dirname(require.resolve('@adobe/reactor-validator/validate'))), + 'node_modules', + 'ajv' + ) + } +} +``` + +Alternatively, add `ajv` as a direct dependency in your project with the current version used here so the alias is unnecessary. However, this is very brittle. This project reserves the right to use a different version of `ajv` at any time. + ## Contributing Contributions are welcomed! Read the [Contributing Guide](CONTRIBUTING.md) for more information. diff --git a/lib/gatherFilesInNodeEnvironment.js b/lib/gatherFilesInNodeEnvironment.js new file mode 100644 index 0000000..d9cb15c --- /dev/null +++ b/lib/gatherFilesInNodeEnvironment.js @@ -0,0 +1,44 @@ +/*************************************************************************************** + * (c) 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + ****************************************************************************************/ + +// Recursively scans a directory and returns an array of relative file path strings. +// Node-only -- uses fs and path. Not suitable for browser environments. +// +// Example: for an extension layout like +// extension.json +// src/view/configuration.html +// src/view/events/click.html +// src/lib/main.js +// the returned array is e.g. +// ['extension.json', 'src/view/configuration.html', 'src/view/events/click.html', 'src/lib/main.js'] + +'use strict'; +var fs = require('fs'); +var pathUtil = require('path'); + +var gatherFilesInNodeEnvironment = function(dir, root, result) { + root = root || dir; + result = result || []; + var entries = fs.readdirSync(dir); + entries.forEach(function(entry) { + var fullPath = pathUtil.join(dir, entry); + var relPath = pathUtil.relative(root, fullPath); + if (fs.statSync(fullPath).isDirectory()) { + gatherFilesInNodeEnvironment(fullPath, root, result); + } else { + result.push(relPath); + } + }); + return result; +}; + +module.exports = gatherFilesInNodeEnvironment; diff --git a/lib/index.js b/lib/index.js index 4910385..f9c0ea7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,134 +10,18 @@ * governing permissions and limitations under the License. ****************************************************************************************/ -'use strict'; - -var fs = require('fs'); -var pathUtil = require('path'); -var Ajv = require("ajv-draft-04") -var addAJVFormats = require("ajv-formats") - -var isFile = function(path) { - try { - return fs.statSync(path).isFile(); - } catch (e) { - return false; - } -}; - -var isDir = function(path) { - try { - return fs.statSync(path).isDirectory(); - } catch (e) { - return false; - } -}; - -var stripQueryAndAnchor = function(path) { - path = path.split('?').shift(); - path = path.split('#').shift(); - return path; -}; - -var validateJsonStructure = function(extensionDescriptor) { - var platform = extensionDescriptor.platform; - - if (!platform) { - return 'the required property "platform" is missing.'; - } - - var extensionDescriptorSchema = - require('@adobe/reactor-turbine-schemas/schemas/extension-package-' + platform + '.json'); - - var ajv = new Ajv({ - schemaId: 'auto', - strict: false - }); - addAJVFormats(ajv); - - if (!ajv.validate(extensionDescriptorSchema, extensionDescriptor)) { - return ajv.errorsText(); - } -}; - -var validateViewBasePath = function(extensionDescriptor) { - var absViewBasePath = pathUtil.resolve( - process.cwd(), - extensionDescriptor.viewBasePath - ); - - if (!isDir(absViewBasePath)) { - return absViewBasePath + ' is not a directory.'; - } -}; - -var validateFiles = function(extensionDescriptor) { - var paths = []; - var platform = extensionDescriptor.platform; - - if (!platform) { - return 'the required property "platform" is missing.'; - } - - if (extensionDescriptor.configuration) { - paths.push(pathUtil.resolve( - process.cwd(), - extensionDescriptor.viewBasePath, - stripQueryAndAnchor(extensionDescriptor.configuration.viewPath) - )); - } - - if (extensionDescriptor.main) { - paths.push(pathUtil.resolve( - process.cwd(), - extensionDescriptor.main - )); - } - - ['events', 'conditions', 'actions', 'dataElements'].forEach(function(featureType) { - var features = extensionDescriptor[featureType]; - - if (features) { - features.forEach(function(feature) { - if (feature.viewPath) { - paths.push(pathUtil.resolve( - process.cwd(), - extensionDescriptor.viewBasePath, - stripQueryAndAnchor(feature.viewPath) - )); - } - - if (platform === 'web') { - paths.push(pathUtil.resolve( - process.cwd(), - feature.libPath - )); - } - }); - } - }); - - for (var i = 0; i < paths.length; i++) { - var path = paths[i]; - if (!isFile(path)) { - return path + ' is not a file.'; - } - } -}; +// Main entry point for Node consumers. Scans the current working directory for files, +// then delegates to the portable validate function for schema and file validation. +// Returns undefined if valid, or an error string if not. +'use strict'; +var validate = require('./validate'); +var gatherFilesInNodeEnvironment = require('./gatherFilesInNodeEnvironment'); module.exports = function(extensionDescriptor) { - var validators = [ - validateJsonStructure, - validateViewBasePath, - validateFiles - ]; - - for (var i = 0; i < validators.length; i++) { - var error = validators[i](extensionDescriptor); - - if (error) { - return 'An error was found in your extension.json: ' + error; - } + var fileList = gatherFilesInNodeEnvironment(process.cwd()); + var error = validate(extensionDescriptor, fileList); + if (error) { + return 'An error was found in your extension.json: ' + error; } }; diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..ee02cd4 --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,28 @@ +/*************************************************************************************** + * (c) 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + ****************************************************************************************/ + +// Validates an extension descriptor against its JSON schema and a provided file list. +// Portable -- no Node-specific APIs. Safe to use in browser environments. +// Accepts (extensionDescriptor, fileList) where fileList is an array of relative path strings. +// Returns undefined if valid, or an error string on the first problem encountered. + +'use strict'; +var validateSchema = require('./validateSchema'); +var validateFiles = require('./validateFiles'); + +module.exports = function(extensionDescriptor, fileList) { + var error = validateSchema(extensionDescriptor); + if (error) return error; + + error = validateFiles(extensionDescriptor, fileList); + if (error) return error; +}; diff --git a/lib/validateFiles.js b/lib/validateFiles.js new file mode 100644 index 0000000..8a9d945 --- /dev/null +++ b/lib/validateFiles.js @@ -0,0 +1,89 @@ +/*************************************************************************************** + * (c) 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + ****************************************************************************************/ + +// Validates that files referenced by an extension descriptor exist in a provided file list. +// Portable -- no Node-specific APIs. Safe to use in browser environments. +// Accepts (extensionDescriptor, fileList) where fileList is an array of relative path strings. +// Returns undefined if valid, or an error string if not. + +'use strict'; + +var stripQueryAndAnchor = function(path) { + return path.split('?').shift().split('#').shift(); +}; + +var joinPath = function() { + return [].slice.call(arguments).filter(Boolean).join('/').replace(/\/+/g, '/'); +}; + +var validateViewBasePath = function(extensionDescriptor, fileSet, fileList) { + var viewBasePath = extensionDescriptor.viewBasePath; + if (!viewBasePath) return; + + var viewBaseNorm = viewBasePath.replace(/\/+$/, ''); + if (fileSet.has(viewBaseNorm)) { + return 'The referenced viewBasePath ' + viewBasePath + ' is either not a directory or is empty.'; + } + + var hasFileUnderPath = fileList.some(function(p) { + return p === viewBaseNorm || p.indexOf(viewBaseNorm + '/') === 0; + }); + if (!hasFileUnderPath) { + return 'The referenced viewBasePath ' + viewBasePath + ' is either not a directory or is empty.'; + } +}; + +var validateFileList = function(extensionDescriptor, fileSet) { + var paths = []; + var platform = extensionDescriptor.platform; + var viewBase = extensionDescriptor.viewBasePath; + + if (!platform) { + return 'the required property "platform" is missing.'; + } + + if (extensionDescriptor.main) { + paths.push(extensionDescriptor.main); + } + + if (extensionDescriptor.configuration) { + paths.push(joinPath(viewBase, stripQueryAndAnchor(extensionDescriptor.configuration.viewPath))); + } + + ['events', 'conditions', 'actions', 'dataElements'].forEach(function(type) { + var features = extensionDescriptor[type]; + if (features) { + features.forEach(function(feature) { + if (feature.viewPath) { + paths.push(joinPath(viewBase, stripQueryAndAnchor(feature.viewPath))); + } + if (platform === 'web') { + paths.push(feature.libPath); + } + }); + } + }); + + for (var i = 0; i < paths.length; i++) { + if (!fileSet.has(paths[i])) { + return paths[i] + ' is not a file.'; + } + } +}; + +module.exports = function(extensionDescriptor, fileList) { + var fileSet = new Set(fileList); + var error = validateViewBasePath(extensionDescriptor, fileSet, fileList); + if (error) return error; + error = validateFileList(extensionDescriptor, fileSet); + if (error) return error; +}; diff --git a/lib/validateSchema.js b/lib/validateSchema.js new file mode 100644 index 0000000..01ee3db --- /dev/null +++ b/lib/validateSchema.js @@ -0,0 +1,43 @@ +/*************************************************************************************** + * (c) 2017 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + ****************************************************************************************/ + +// Validates an extension descriptor object against the JSON schema for its platform. +// Portable -- no Node-specific APIs. Safe to use in browser environments. +// Returns undefined if valid, or an error string if not. + +'use strict'; +var Ajv = require("ajv-draft-04"); +var addAJVFormats = require("ajv-formats"); + +var schemas = { + web: require('@adobe/reactor-turbine-schemas/schemas/extension-package-web.json'), + edge: require('@adobe/reactor-turbine-schemas/schemas/extension-package-edge.json'), + mobile: require('@adobe/reactor-turbine-schemas/schemas/extension-package-mobile.json') +}; + +var validateJsonStructure = function(extensionDescriptor) { + var platform = extensionDescriptor.platform; + if (!platform) { + return 'the required property "platform" is missing.'; + } + var extensionDescriptorSchema = schemas[platform]; + if (!extensionDescriptorSchema) { + return 'unknown platform "' + platform + '".'; + } + var ajv = new Ajv({ schemaId: 'auto', strict: false }); + addAJVFormats(ajv); + if (!ajv.validate(extensionDescriptorSchema, extensionDescriptor)) { + return ajv.errorsText(); + } +}; + +module.exports = validateJsonStructure; diff --git a/package-lock.json b/package-lock.json index 811df17..d4ea8ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@adobe/reactor-validator", - "version": "2.5.0", + "version": "3.0.0-beta.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@adobe/reactor-validator", - "version": "2.5.0", + "version": "3.0.0-beta.5", "license": "Apache-2.0", "dependencies": { "@adobe/reactor-turbine-schemas": "^10.8.0", @@ -79,6 +79,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1462,14 +1463,16 @@ ] }, "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "peer": true, "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -1722,6 +1725,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -2190,6 +2194,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2310,9 +2330,10 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -2365,9 +2386,9 @@ } }, "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, "license": "MIT" }, @@ -3191,9 +3212,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -3353,13 +3374,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3677,14 +3698,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, "node_modules/pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", @@ -4099,9 +4112,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -4235,14 +4248,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", diff --git a/package.json b/package.json index c528f6f..8a83363 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { "name": "@adobe/reactor-validator", - "version": "2.5.0", + "version": "3.0.0-beta.5", "description": "Validates a Tags extension package.", "main": "lib/index.js", + "exports": { + ".": "./lib/index.js", + "./validate": "./lib/validate.js" + }, "bin": { "reactor-validator": "bin/index.js" }, diff --git a/tests/validate/validate.test.js b/tests/validate/validate.test.js new file mode 100644 index 0000000..290426e --- /dev/null +++ b/tests/validate/validate.test.js @@ -0,0 +1,74 @@ +/*************************************************************************************** + * (c) 2017 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + ****************************************************************************************/ + +// Tests for the portable validate subpath export (validate.js). + +const validate = require('../../lib/validate'); +const { fromJS } = require('immutable'); +const webManifest = require('../web/extension.json'); +const edgeManifest = require('../edge/extension.json'); + +describe('portable validate', () => { + describe('schema validation', () => { + it('returns undefined for a valid web descriptor', () => { + var fileList = ['src/view/.gitkeep']; + expect(validate(fromJS(webManifest).toJS(), fileList)).toBeUndefined(); + }); + + it('returns undefined for a valid edge descriptor', () => { + var fileList = ['src/view/.gitkeep']; + expect(validate(fromJS(edgeManifest).toJS(), fileList)).toBeUndefined(); + }); + + it('returns an error for an invalid descriptor', () => { + var bad = fromJS(webManifest).toJS(); + delete bad.name; + var fileList = ['src/view/.gitkeep']; + expect(typeof validate(bad, fileList)).toBe('string'); + }); + + it('returns an error for unknown platform', () => { + var bad = fromJS(webManifest).toJS(); + bad.platform = 'unknown'; + var fileList = ['src/view/.gitkeep']; + expect(validate(bad, fileList)).toBe('unknown platform "unknown".'); + }); + }); + + describe('file validation', () => { + it('returns undefined when viewBasePath is a directory (has files under it)', () => { + var manifest = fromJS(webManifest).toJS(); + var fileList = ['src/view/.gitkeep']; + expect(validate(manifest, fileList)).toBeUndefined(); + }); + + it('returns an error when viewBasePath is a file', () => { + var manifest = fromJS(webManifest).toJS(); + manifest.viewBasePath = 'src/view/config.html'; + var fileList = ['src/view/config.html']; + expect(validate(manifest, fileList)).toBe('The referenced viewBasePath src/view/config.html is either not a directory or is empty.'); + }); + + it('returns an error when a required file is missing', () => { + var manifest = fromJS(webManifest).toJS(); + manifest.main = 'src/lib/main.js'; + var fileList = ['src/view/.gitkeep']; + expect(validate(manifest, fileList)).toContain('is not a file'); + }); + + it('returns an error when viewBasePath is set but no files exist under it', () => { + var manifest = fromJS(webManifest).toJS(); + var fileList = ['extension.json']; + expect(validate(manifest, fileList)).toBe('The referenced viewBasePath src/view/ is either not a directory or is empty.'); + }); + }); +});