This is an automated email from the ASF dual-hosted git repository. erisu pushed a commit to branch feat/use-common-plugin-manager in repository https://gitbox.apache.org/repos/asf/cordova-browser.git
commit e5cb8e5d435a87ffb3a9add1e3ed9a77b0af9c55 Author: Erisu <[email protected]> AuthorDate: Fri Jul 10 19:23:40 2026 +0900 feat!: plugin manager * Cleaned plugin add and remove APIs to use cordova-common PluginManager. * Updated handlers API signature to match other platforms. * PluginManager to handle writting the plugin module. * Updated Tests --- lib/Api.js | 253 ++++----------------- lib/BrowserProject.js | 81 +++++++ lib/browser_handler.js | 127 ++++------- test/browser_handler.spec.js | 139 ++++++----- .../fixtures/fakeBrowserPlatformProject/config.xml | 34 +++ .../fakeBrowserPlatformProject/www/index.html | 28 +++ test/fixtures/fakePlugin/assets/ServiceWorker.txt | 20 ++ test/fixtures/fakePlugin/assets/folder/file1.txt | 20 ++ test/fixtures/fakePlugin/assets/folder/file2.txt | 20 ++ test/fixtures/fakePlugin/assets/reformat1.txt | 20 ++ test/fixtures/fakePlugin/assets/reformat2.txt | 20 ++ test/fixtures/fakePlugin/plugin.xml | 35 +++ 12 files changed, 429 insertions(+), 368 deletions(-) diff --git a/lib/Api.js b/lib/Api.js index e253303..521d272 100644 --- a/lib/Api.js +++ b/lib/Api.js @@ -26,7 +26,6 @@ const path = require('node:path'); const fs = require('node:fs'); const { - ActionStack, ConfigChanges: { PlatformMunger }, ConfigParser, CordovaLogger, @@ -35,9 +34,11 @@ const { FileUpdater, PlatformJson, PluginInfoProvider, + PluginManager, xmlHelpers } = require('cordova-common'); +const BrowserProject = require('./BrowserProject'); const { createProject } = require('./create'); const VERSION = require('../package').version; @@ -75,7 +76,6 @@ class Api { if (!dirExists(this.root) || !dirExists(path.join(this.root, 'cordova'))) { throw new CordovaError(`The provided path "${this.root}" is not a valid browser project.`); } - this._handler = require('./browser_handler'); this.locations = { platformRootDir, @@ -286,238 +286,63 @@ class Api { } async addPlugin (pluginInfo, installOptions) { - // console.log(new Error().stack); if (!pluginInfo) { - return Promise.reject( - new Error('The parameter is incorrect. The first parameter should be valid PluginInfo instance') - ); + throw new Error('The parameter is incorrect. The first parameter should be valid PluginInfo instance'); } + const project = BrowserProject.getProjectFile(this.root); + installOptions = installOptions || {}; installOptions.variables = installOptions.variables || {}; - // CB-10108 platformVersion option is required for proper plugin installation - installOptions.platformVersion = installOptions.platformVersion || - this.getPlatformInfo().version; - - const actions = new ActionStack(); - const projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root); - - // gather all files needs to be handled during install - pluginInfo.getFilesAndFrameworks(this.platform) - .concat(pluginInfo.getAssets(this.platform)) - .concat(pluginInfo.getJsModules(this.platform)) - .forEach(item => { - actions.push(actions.createAction( - this._getInstaller(item.itemType, item, pluginInfo.dir, pluginInfo.id, installOptions, projectFile), - this._getUninstaller(item.itemType, item, pluginInfo.dir, pluginInfo.id, installOptions, projectFile) - )); - }); - - // run through the action stack - return actions.process(this.platform, this.root) - .then(() => { - if (projectFile) { - projectFile.write(); - } - - // Add PACKAGE_NAME variable into vars - if (!installOptions.variables.PACKAGE_NAME) { - installOptions.variables.PACKAGE_NAME = this._handler.package_name(this.root); - } - - this._munger - // Ignore passed `is_top_level` option since platform itself doesn't know - // anything about managing dependencies - it's responsibility of caller. - .add_plugin_changes(pluginInfo, installOptions.variables, /* is_top_level= */true, /* should_increment= */true) - .save_all(); - - const targetDir = installOptions.usePlatformWww - ? this.getPlatformInfo().locations.platformWww - : this.getPlatformInfo().locations.www; - this.#addModulesInfo(pluginInfo, targetDir); - }); - } - - async removePlugin (plugin, uninstallOptions) { - // console.log("NotImplemented :: browser-platform:Api:removePlugin ",plugin, uninstallOptions); - - uninstallOptions = uninstallOptions || {}; // CB-10108 platformVersion option is required for proper plugin installation - uninstallOptions.platformVersion = uninstallOptions.platformVersion || + installOptions.platformVersion = installOptions.platformVersion || this.getPlatformInfo().version; - const actions = new ActionStack(); - const projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root); - - // queue up plugin files - plugin.getFilesAndFrameworks(this.platform) - .concat(plugin.getAssets(this.platform)) - .concat(plugin.getJsModules(this.platform)) - .forEach(item => { - actions.push(actions.createAction( - this._getUninstaller(item.itemType, item, plugin.dir, plugin.id, uninstallOptions, projectFile), - this._getInstaller(item.itemType, item, plugin.dir, plugin.id, uninstallOptions, projectFile) - )); - }); - - // run through the action stack - return actions.process(this.platform, this.root) - .then(() => { - if (projectFile) { - projectFile.write(); - } - - this._munger - // Ignore passed `is_top_level` option since platform itself doesn't know - // anything about managing dependencies - it's responsibility of caller. - .remove_plugin_changes(plugin, /* is_top_level= */true) - .save_all(); - - const targetDir = uninstallOptions.usePlatformWww - ? this.getPlatformInfo().locations.platformWww - : this.getPlatformInfo().locations.www; - - this.#removeModulesInfo(plugin, targetDir); - // Remove stale plugin directory - // TODO: this should be done by plugin files uninstaller - fs.rmSync( - path.resolve(this.root, 'Plugins', plugin.id), - { force: true, recursive: true } - ); - }); - } - - _getInstaller (type, item, plugin_dir, plugin_id, options, project) { - const installer = this._handler[type]; - - if (!installer) { - console.log(`unrecognized type ${type}`); - } else { - const wwwDest = options.usePlatformWww - ? this.getPlatformInfo().locations.platformWww - : this._handler.www_dir(this.root); - - if (type === 'asset') { - installer.install(item, plugin_dir, wwwDest); - } else if (type === 'js-module') { - installer.install(item, plugin_dir, plugin_id, wwwDest); - } else { - installer.install(item, plugin_dir, this.root, plugin_id, options, project); - } + // Add PACKAGE_NAME variable into vars + if (!installOptions.variables.PACKAGE_NAME) { + installOptions.variables.PACKAGE_NAME = project.getPackageName(this.root); } - } - _getUninstaller (type, item, plugin_dir, plugin_id, options, project) { - const installer = this._handler[type]; + await PluginManager.get(this.platform, this.locations, project) + .addPlugin(pluginInfo, installOptions); - if (!installer) { - console.log(`browser plugin uninstall: unrecognized type, skipping : ${type}`); - } else { - const wwwDest = options.usePlatformWww - ? this.getPlatformInfo().locations.platformWww - : this._handler.www_dir(this.root); - - if (['asset', 'js-module'].includes(type)) { - return installer.uninstall(item, wwwDest, plugin_id); - } else { - return installer.uninstall(item, this.root, plugin_id, options, project); - } - } + return true; } - /** - * Removes the specified modules from list of installed modules and updates - * platform_json and cordova_plugins.js on disk. - * - * @param {PluginInfo} plugin PluginInfo instance for plugin, which modules - * needs to be added. - * @param {String} targetDir The directory, where updated cordova_plugins.js - * should be written to. - */ - #addModulesInfo (plugin, targetDir) { - const installedModules = this._platformJson.root.modules || []; - const installedPaths = installedModules.map(installedModule => installedModule.file); - - const modulesToInstall = plugin.getJsModules(this.platform) - .filter(moduleToInstall => !installedPaths.includes(moduleToInstall.file)) - .map(moduleToInstall => { - const moduleName = plugin.id + '.' + (moduleToInstall.name || moduleToInstall.src.match(/([^/]+)\.js/)[1]); - const obj = { - file: ['plugins', plugin.id, moduleToInstall.src].join('/'), - id: moduleName, - pluginId: plugin.id - }; - if (moduleToInstall.clobbers.length > 0) { - obj.clobbers = moduleToInstall.clobbers.map(o => o.target); - } - if (moduleToInstall.merges.length > 0) { - obj.merges = moduleToInstall.merges.map(o => o.target); - } - if (moduleToInstall.runs) { - obj.runs = true; - } - - return obj; - }); - - this._platformJson.root.modules = installedModules.concat(modulesToInstall); - if (!this._platformJson.root.plugin_metadata) { - this._platformJson.root.plugin_metadata = {}; + async removePlugin (pluginInfo, uninstallOptions) { + if (!pluginInfo) { + throw new Error('The parameter is incorrect. The first parameter should be valid PluginInfo instance'); } - this._platformJson.root.plugin_metadata[plugin.id] = plugin.version; - this.#writePluginModules(targetDir); - this._platformJson.save(); - } - - /** - * Fetches all installed modules, generates cordova_plugins contents and writes - * it to file. - * - * @param {String} targetDir Directory, where write cordova_plugins.js to. - * Ususally it is either <platform>/www or <platform>/platform_www - * directories. - */ - #writePluginModules (targetDir) { - // Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js - let final_contents = 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {\n'; - final_contents += `module.exports = ${JSON.stringify(this._platformJson.root.modules, null, ' ')};\n`; - final_contents += 'module.exports.metadata = \n'; - final_contents += '// TOP OF METADATA\n'; - final_contents += JSON.stringify(this._platformJson.root.plugin_metadata || {}, null, ' ') + '\n'; - final_contents += '// BOTTOM OF METADATA\n'; - final_contents += '});'; // Close cordova.define. - - fs.mkdirSync(targetDir, { recursive: true }); - fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf-8'); - } + const project = BrowserProject.getProjectFile(this.root); - /** - * Removes the specified modules from list of installed modules and updates - * platform_json and cordova_plugins.js on disk. - * - * @param {PluginInfo} plugin PluginInfo instance for plugin, which modules - * needs to be removed. - * @param {String} targetDir The directory, where updated cordova_plugins.js - * should be written to. - */ - #removeModulesInfo (plugin, targetDir) { - const installedModules = this._platformJson.root.modules || []; - const modulesToRemove = plugin.getJsModules(this.platform) - .map(jsModule => ['plugins', plugin.id, jsModule.src].join('/')); - - const updatedModules = installedModules.filter( - installedModule => !modulesToRemove.includes(installedModule.file) - ); + uninstallOptions = uninstallOptions || {}; + uninstallOptions.variables = uninstallOptions.variables || {}; - this._platformJson.root.modules = updatedModules; - if (this._platformJson.root.plugin_metadata) { - delete this._platformJson.root.plugin_metadata[plugin.id]; + // Add PACKAGE_NAME variable into vars + if (!uninstallOptions.variables.PACKAGE_NAME) { + uninstallOptions.variables.PACKAGE_NAME = project.getPackageName(this.root); } - this.#writePluginModules(targetDir); - this._platformJson.save(); + await PluginManager.get(this.platform, this.locations, project) + .removePlugin(pluginInfo, uninstallOptions) + .then( + () => { + const targetDir = uninstallOptions.usePlatformWww + ? this.getPlatformInfo().locations.platformWww + : this.getPlatformInfo().locations.www; + + // Remove stale plugin directory + // TODO: this should be done by plugin files uninstaller + fs.rmSync( + path.resolve(targetDir, 'plugins', pluginInfo.id), + { force: true, recursive: true } + ); + } + ); + + return true; } async build (buildOptions) { diff --git a/lib/BrowserProject.js b/lib/BrowserProject.js new file mode 100644 index 0000000..b14fb53 --- /dev/null +++ b/lib/BrowserProject.js @@ -0,0 +1,81 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +const fs = require('node:fs'); +const path = require('node:path'); +const pluginHandlers = require('./browser_handler'); + +let projectFileCache = {}; + +class BrowserProject { + constructor (projectDir) { + this._dirty = false; + this.projectDir = projectDir; + this.platformWww = path.join(this.projectDir, 'platform_www'); + this.www = path.join(this.projectDir, 'www'); + } + + /** + * Reads the package name out of the project's config.xml + * + * @param {String} projectDir The absolute path to the directory containing the project + * @return {String} The name of the package + */ + getPackageName (projectDir) { + // this method should the id from root config.xml => <widget id=xxx + let pkgName = 'io.cordova.hellocordova'; + const widget_id_regex = /(?:<widget\s+id=['"])(\S+)(?:['"])/; + + const configPath = path.join(projectDir, 'config.xml'); + if (fs.existsSync(configPath)) { + const configStr = fs.readFileSync(configPath, 'utf8'); + const res = configStr.match(widget_id_regex); + if (res && res.length > 1) { + pkgName = res[1]; + } + } + return pkgName; + } + + getInstaller (type) { + return pluginHandlers.getInstaller(type); + } + + getUninstaller (type) { + return pluginHandlers.getUninstaller(type); + } + + static getProjectFile (projectDir) { + if (!projectFileCache[projectDir]) { + projectFileCache[projectDir] = new BrowserProject(projectDir); + } + + return projectFileCache[projectDir]; + } + + static purgeCache (projectDir) { + if (projectDir) { + delete projectFileCache[projectDir]; + } else { + projectFileCache = {}; + } + } +} + +module.exports = BrowserProject; diff --git a/lib/browser_handler.js b/lib/browser_handler.js index fb786bb..604738b 100644 --- a/lib/browser_handler.js +++ b/lib/browser_handler.js @@ -22,101 +22,48 @@ const fs = require('node:fs'); const { events } = require('cordova-common'); -module.exports = { - www_dir: function (project_dir) { - return path.join(project_dir, 'www'); - }, - package_name: function (project_dir) { - // this method should the id from root config.xml => <widget id=xxx - // return common.package_name(project_dir, this.www_dir(project_dir)); - // console.log('package_name called with ' + project_dir); - let pkgName = 'io.cordova.hellocordova'; - const widget_id_regex = /(?:<widget\s+id=['"])(\S+)(?:['"])/; - - const configPath = path.join(project_dir, 'config.xml'); - if (fs.existsSync(configPath)) { - const configStr = fs.readFileSync(configPath, 'utf8'); - const res = configStr.match(widget_id_regex); - if (res && res.length > 1) { - pkgName = res[1]; - } - } - return pkgName; - }, +const handlers = { 'js-module': { - install: function (jsModule, plugin_dir, plugin_id, www_dir) { + install: function (obj, plugin, project, options) { + const destRoot = options && options.usePlatformWww + ? project.platformWww + : project.www; // Copy the plugin's files into the www directory. - const moduleSource = path.resolve(plugin_dir, jsModule.src); + const moduleSource = path.resolve(plugin.dir, obj.src); // Get module name based on existing 'name' attribute or filename // Must use path.extname/path.basename instead of path.parse due to CB-9981 - const moduleName = plugin_id + '.' + (jsModule.name || path.basename(jsModule.src, path.extname(jsModule.src))); - + const moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname(obj.src))); // Read in the file, prepend the cordova.define, and write it back out. let scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM + if (moduleSource.match(/.*\.json$/)) { scriptContent = `module.exports = ${scriptContent}`; } scriptContent = `cordova.define("${moduleName}", function(require, exports, module) { ${scriptContent}\n});\n`; - const moduleDestination = path.resolve(www_dir, 'plugins', plugin_id, jsModule.src); + const moduleDestination = path.resolve(destRoot, 'plugins', plugin.id, obj.src); + fs.mkdirSync(path.dirname(moduleDestination), { recursive: true }); fs.writeFileSync(moduleDestination, scriptContent, 'utf-8'); }, - uninstall: function (jsModule, www_dir, plugin_id) { - const pluginRelativePath = path.join('plugins', plugin_id, jsModule.src); - // common.removeFileAndParents(www_dir, pluginRelativePath); - console.log(`js-module uninstall called : ${pluginRelativePath}`); - } - }, - 'source-file': { - install: function (obj, plugin_dir, project_dir, plugin_id, options) { - // var dest = path.join(obj.targetDir, path.basename(obj.src)); - // common.copyFile(plugin_dir, obj.src, project_dir, dest); - console.log('install called'); - }, - uninstall: function (obj, project_dir, plugin_id, options) { - // var dest = path.join(obj.targetDir, path.basename(obj.src)); - // common.removeFile(project_dir, dest); - console.log('uninstall called'); - } - }, - 'header-file': { - install: function (obj, plugin_dir, project_dir, plugin_id, options) { - events.emit('verbose', 'header-fileinstall is not supported for browser'); - }, - uninstall: function (obj, project_dir, plugin_id, options) { - events.emit('verbose', 'header-file.uninstall is not supported for browser'); - } - }, - 'resource-file': { - install: function (obj, plugin_dir, project_dir, plugin_id, options) { - events.emit('verbose', 'resource-file.install is not supported for browser'); - }, - uninstall: function (obj, project_dir, plugin_id, options) { - events.emit('verbose', 'resource-file.uninstall is not supported for browser'); - } - }, - framework: { - install: function (obj, plugin_dir, project_dir, plugin_id, options) { - events.emit('verbose', 'framework.install is not supported for browser'); - }, - uninstall: function (obj, project_dir, plugin_id, options) { - events.emit('verbose', 'framework.uninstall is not supported for browser'); - } - }, - 'lib-file': { - install: function (obj, plugin_dir, project_dir, plugin_id, options) { - events.emit('verbose', 'lib-file.install is not supported for browser'); - }, - uninstall: function (obj, project_dir, plugin_id, options) { - events.emit('verbose', 'lib-file.uninstall is not supported for browser'); + uninstall: function (obj, plugin, project, options) { + const destRoot = options && options.usePlatformWww + ? project.platformWww + : project.www; + const pluginRelativePath = path.join('plugins', plugin.id, obj.src); + + fs.rmSync(path.join(destRoot, pluginRelativePath), { force: true }); } }, asset: { - install: function (asset, plugin_dir, wwwDest) { - const src = path.join(plugin_dir, asset.src); - const dest = path.join(wwwDest, asset.target); + install: function (obj, plugin, project, options) { + const destRoot = options && options.usePlatformWww + ? project.platformWww + : project.www; + const src = path.join(plugin.dir, obj.src); + const dest = path.join(destRoot, obj.target); const destDir = path.parse(dest).dir; + if (destDir !== '' && !fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } @@ -127,9 +74,29 @@ module.exports = { fs.cpSync(src, dest, { force: true }); } }, - uninstall: function (asset, wwwDest, plugin_id) { - fs.rmSync(path.join(wwwDest, asset.target), { recursive: true, force: true }); - fs.rmSync(path.join(wwwDest, 'plugins', plugin_id), { recursive: true, force: true }); + uninstall: function (obj, plugin, project, options) { + const destRoot = options && options.usePlatformWww + ? project.platformWww + : project.www; + + fs.rmSync(path.join(destRoot, obj.target), { recursive: true, force: true }); + fs.rmSync(path.join(destRoot, 'plugins', plugin.id), { recursive: true, force: true }); } } }; + +module.exports.getInstaller = function (type) { + if (handlers[type] && handlers[type].install) { + return handlers[type].install; + } + + events.emit('verbose', '<' + type + '> is not supported for browser plugins'); +}; + +module.exports.getUninstaller = function (type) { + if (handlers[type] && handlers[type].uninstall) { + return handlers[type].uninstall; + } + + events.emit('verbose', '<' + type + '> is not supported for browser plugins'); +}; diff --git a/test/browser_handler.spec.js b/test/browser_handler.spec.js index 84e4b8d..ca95513 100644 --- a/test/browser_handler.spec.js +++ b/test/browser_handler.spec.js @@ -17,112 +17,103 @@ under the License. */ -const { describe, it, mock } = require('node:test'); +const { describe, it, mock, before } = require('node:test'); const assert = require('node:assert'); const fs = require('node:fs'); const path = require('node:path'); +const tmp = require('tmp'); +const { PluginInfo } = require('cordova-common'); -const browser_handler = require('../lib/browser_handler'); +const BrowserHandler = require('../lib/browser_handler'); +const BrowserProject = require('../lib/BrowserProject'); + +tmp.setGracefulCleanup(); + +function makeTempDir () { + const tempdir = tmp.dirSync({ unsafeCleanup: true }); + return path.join(tempdir.name, `cordova-browser-browser-handler-test-${Date.now()}`); +} describe('Asset install tests', () => { - const asset = { - itemType: 'asset', - src: path.join('someSrc', 'ServiceWorker.js'), - target: 'ServiceWorker.js' - }; - const assetWithPath = { - itemType: 'asset', - src: path.join('someSrc', 'reformat.js'), - target: path.join('js', 'deepdown', 'reformat.js') - }; - const assetWithPath2 = { - itemType: 'asset', - src: path.join('someSrc', 'reformat.js'), - target: path.join('js', 'deepdown', 'reformat2.js') - }; - - const plugin_dir = 'pluginDir'; - const wwwDest = 'dest'; - - it('if src is a directory, should be called with cpSync recursive force', () => { - const cpSyncSpy = mock.method(fs, 'cpSync', () => {}); - const mkdirSyncSpy = mock.method(fs, 'mkdirSync', () => {}); - mock.method(fs, 'statSync', () => ({ - isDirectory: () => true - })); - - browser_handler.asset.install(asset, plugin_dir, wwwDest); + let pluginInfo; + let browserProject; + + before(() => { + const testDir = makeTempDir(); + fs.cpSync(path.join(__dirname, 'fixtures', 'fakeBrowserPlatformProject'), testDir, { recursive: true }); + pluginInfo = new PluginInfo(path.join(__dirname, 'fixtures', 'fakePlugin')); + browserProject = new BrowserProject(testDir); + }); - assert.ok( - mkdirSyncSpy.mock.calls.some(({ arguments: args }) => ( - args[0] === wwwDest && - args[1]?.recursive === true - )) - ); + it('if src is a file, should call cpSync with force', async () => { + const cpSyncSpy = mock.method(fs, 'cpSync'); + const mkdirSyncSpy = mock.method(fs, 'mkdirSync'); + const assets = pluginInfo.getAssets('browser'); + const asset = assets.find(a => a.src.includes('ServiceWorker.txt')); + + const install = BrowserHandler.getInstaller('asset'); + await install(asset, pluginInfo, browserProject, {}); + assert.strictEqual(mkdirSyncSpy.mock.calls.length, 0); assert.ok( cpSyncSpy.mock.calls.some(({ arguments: args }) => ( - typeof args[0] === 'string' && - args[1] === path.join('dest', asset.target) && - args[2]?.recursive === true && + args[0].endsWith(path.join('fakePlugin', 'assets', 'ServiceWorker.txt')) && + args[1].endsWith(path.join('www', 'ServiceWorker.txt')) && args[2]?.force === true )) ); }); - it('if src is not a directory and asset has no path, should be called with cp, -f', () => { - const cpSyncSpy = mock.method(fs, 'cpSync', () => {}); + it('if the target directory path does not exist, should call mkdirSync & cpSync', async () => { + const cpSyncSpy = mock.method(fs, 'cpSync'); const mkdirSyncSpy = mock.method(fs, 'mkdirSync'); - mock.method(fs, 'existsSync', () => true); - mock.method(fs, 'statSync', () => ({ - isDirectory: () => false - })); + const assets = pluginInfo.getAssets('browser'); + const asset1 = assets.find(a => a.src.includes('reformat1.txt')); - browser_handler.asset.install(asset, plugin_dir, wwwDest); + const install = BrowserHandler.getInstaller('asset'); + await install(asset1, pluginInfo, browserProject, {}); - assert.strictEqual(mkdirSyncSpy.mock.calls.length, 0); // not was called + assert.strictEqual(mkdirSyncSpy.mock.calls.length, 1); assert.ok( cpSyncSpy.mock.calls.some(({ arguments: args }) => ( - args[0] === path.join('pluginDir', asset.src) && - args[1] === path.join('dest', asset.target) && + args[0].endsWith(path.join('fakePlugin', 'assets', 'reformat1.txt')) && + args[1].endsWith(path.join('www', 'js', 'deepdown', 'reformat1.txt')) && args[2]?.force === true )) ); - }); - it('if src is not a directory and asset has a path, should be called with cp, -f', () => { - /* - Test that a dest directory gets created if it does not exist - */ - const cpSyncSpy = mock.method(fs, 'cpSync', () => {}); - const mkdirSyncSpy = mock.method(fs, 'mkdirSync'); - mock.method(fs, 'statSync', () => ({ - isDirectory: () => false - })); - mock.method(fs, 'existsSync', () => false); - - browser_handler.asset.install(assetWithPath, plugin_dir, wwwDest); + // Test another upload + const asset2 = assets.find(a => a.src.includes('reformat2.txt')); + await install(asset2, pluginInfo, browserProject, {}); + // Should still be 1 + assert.strictEqual(mkdirSyncSpy.mock.calls.length, 1); assert.ok( - mkdirSyncSpy.mock.calls.some(({ arguments: args }) => ( - args[0] === path.join('dest', 'js', 'deepdown') && - args[1]?.recursive === true + cpSyncSpy.mock.calls.some(({ arguments: args }) => ( + args[0].endsWith(path.join('fakePlugin', 'assets', 'reformat2.txt')) && + args[1].endsWith(path.join('www', 'js', 'deepdown', 'reformat2.txt')) && + args[2]?.force === true )) ); + }); + it('if src is a directory, it should call cpSync with force and recursive', async () => { + const cpSyncSpy = mock.method(fs, 'cpSync'); + const mkdirSyncSpy = mock.method(fs, 'mkdirSync'); + const assets = pluginInfo.getAssets('browser'); + const asset = assets.find(a => a.src.includes('folder')); + + const install = BrowserHandler.getInstaller('asset'); + await install(asset, pluginInfo, browserProject, {}); + + assert.strictEqual(mkdirSyncSpy.mock.calls.length, 0); assert.ok( cpSyncSpy.mock.calls.some(({ arguments: args }) => ( - args[0] === path.join('pluginDir', assetWithPath.src) && - args[1] === path.join('dest', assetWithPath.target) && - args[2]?.force === true + args[0].endsWith(path.join('fakePlugin', 'assets', 'folder')) && + args[1].endsWith(path.join('www', 'folder')) && + args[2]?.force === true && + args[2]?.recursive === true )) ); - - /* - Now test that a second call to the same dest folder skips mkdir because the first asset call should have created it. - */ - mock.method(fs, 'existsSync', () => true); - browser_handler.asset.install(assetWithPath2, plugin_dir, wwwDest); - assert.strictEqual(mkdirSyncSpy.mock.calls.length, 1); // not called again }); }); diff --git a/test/fixtures/fakeBrowserPlatformProject/config.xml b/test/fixtures/fakeBrowserPlatformProject/config.xml new file mode 100644 index 0000000..c3eede9 --- /dev/null +++ b/test/fixtures/fakeBrowserPlatformProject/config.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<widget xmlns="http://www.w3.org/ns/widgets" + xmlns:cdv="http://cordova.apache.org/ns/1.0" + id="org.apache.cordova.hellocordova" + version="1.0.0"> + <name>Hello Cordova</name> + <description> + A sample Apache Cordova application that responds to the deviceready event. + </description> + <author email="[email protected]" href="https://cordova.apache.org"> + Apache Cordova Team + </author> + <content src="index.html" /> + <allow-intent href="http://*/*" /> + <allow-intent href="https://*/*" /> +</widget> diff --git a/test/fixtures/fakeBrowserPlatformProject/www/index.html b/test/fixtures/fakeBrowserPlatformProject/www/index.html new file mode 100644 index 0000000..003848b --- /dev/null +++ b/test/fixtures/fakeBrowserPlatformProject/www/index.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" /> + <title>Fake Project</title> + </head> + <body> + </body> +</html> diff --git a/test/fixtures/fakePlugin/assets/ServiceWorker.txt b/test/fixtures/fakePlugin/assets/ServiceWorker.txt new file mode 100644 index 0000000..5949aff --- /dev/null +++ b/test/fixtures/fakePlugin/assets/ServiceWorker.txt @@ -0,0 +1,20 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +// empty test file diff --git a/test/fixtures/fakePlugin/assets/folder/file1.txt b/test/fixtures/fakePlugin/assets/folder/file1.txt new file mode 100644 index 0000000..5949aff --- /dev/null +++ b/test/fixtures/fakePlugin/assets/folder/file1.txt @@ -0,0 +1,20 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +// empty test file diff --git a/test/fixtures/fakePlugin/assets/folder/file2.txt b/test/fixtures/fakePlugin/assets/folder/file2.txt new file mode 100644 index 0000000..5949aff --- /dev/null +++ b/test/fixtures/fakePlugin/assets/folder/file2.txt @@ -0,0 +1,20 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +// empty test file diff --git a/test/fixtures/fakePlugin/assets/reformat1.txt b/test/fixtures/fakePlugin/assets/reformat1.txt new file mode 100644 index 0000000..5949aff --- /dev/null +++ b/test/fixtures/fakePlugin/assets/reformat1.txt @@ -0,0 +1,20 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +// empty test file diff --git a/test/fixtures/fakePlugin/assets/reformat2.txt b/test/fixtures/fakePlugin/assets/reformat2.txt new file mode 100644 index 0000000..5949aff --- /dev/null +++ b/test/fixtures/fakePlugin/assets/reformat2.txt @@ -0,0 +1,20 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +// empty test file diff --git a/test/fixtures/fakePlugin/plugin.xml b/test/fixtures/fakePlugin/plugin.xml new file mode 100644 index 0000000..32207b4 --- /dev/null +++ b/test/fixtures/fakePlugin/plugin.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + id="cordova-plugin-fake-plugin" + version="1.0.0"> + <name>Fake Plugin</name> + <description>Cordova Fake Plugin</description> + <license>Apache 2.0</license> + <keywords>cordova</keywords> + + <!-- Test Case 1: Copies to www dir --> + <asset src="assets/ServiceWorker.txt" target="ServiceWorker.txt" /> + <!-- Test Case 2: Makes js/deepdown directory then copies file --> + <asset src="assets/reformat1.txt" target="js/deepdown/reformat1.txt" /> + <asset src="assets/reformat2.txt" target="js/deepdown/reformat2.txt" /> + <!-- Test Case 3: recursive copies --> + <asset src="assets/folder" target="folder" /> +</plugin> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
