This is an automated email from the ASF dual-hosted git repository.

erisu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-browser.git


The following commit(s) were added to refs/heads/master by this push:
     new d8b667d  feat!: use PluginManager (#163)
d8b667d is described below

commit d8b667da8066b35114c1cb8b08e81dd64fc7207c
Author: エリス <[email protected]>
AuthorDate: Sun Jul 12 12:38:23 2026 +0900

    feat!: use PluginManager (#163)
    
    * 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]


Reply via email to