Repository: apache/cordova-electron Branch: master Commit: a8e732d66a7f Files: 99 Total size: 451.8 KB Directory structure: gitextract_002anttn/ ├── .asf.yaml ├── .eslintignore ├── .eslintrc.yml ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── BUG_REPORT.md │ │ ├── FEATURE_REQUEST.md │ │ └── SUPPORT_QUESTION.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── ci.yml │ └── release-audit.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .ratignore ├── CONTRIBUTING.md ├── DOCUMENTATION.md ├── LICENSE ├── NOTICE ├── README.md ├── RELEASENOTES.md ├── bin/ │ └── templates/ │ ├── cordova/ │ │ ├── Api.js │ │ ├── defaults.xml │ │ ├── version │ │ └── version.bat │ └── platform_www/ │ ├── cdv-electron-main.js │ ├── cdv-electron-preload.js │ ├── cdv-electron-settings.json │ ├── cdv-reserved-scheme.json │ └── config.xml ├── cordova-js-src/ │ ├── confighelper.js │ ├── exec.js │ └── platform.js ├── lib/ │ ├── Api.js │ ├── ManifestJsonParser.js │ ├── PackageJsonParser.js │ ├── SettingJsonParser.js │ ├── build/ │ │ ├── base.json │ │ ├── darwin.json │ │ ├── linux.json │ │ └── win32.json │ ├── build.js │ ├── check_reqs.js │ ├── clean.js │ ├── create.js │ ├── handler.js │ ├── parser.js │ ├── prepare.js │ ├── run.js │ └── util.js ├── licence_checker.yml ├── package.json └── tests/ └── spec/ ├── coverage.json ├── fixtures/ │ ├── test-app-with-electron-plugin/ │ │ ├── config.xml │ │ ├── package.json │ │ ├── platforms/ │ │ │ └── electron/ │ │ │ ├── config.xml │ │ │ ├── cordova/ │ │ │ │ ├── Api.js │ │ │ │ ├── defaults.xml │ │ │ │ ├── version │ │ │ │ └── version.bat │ │ │ ├── electron.json │ │ │ ├── platform_www/ │ │ │ │ └── config.xml │ │ │ └── www/ │ │ │ ├── config.xml │ │ │ ├── cordova_plugins.js │ │ │ └── package.json │ │ ├── plugins/ │ │ │ └── cordova-plugin-sample/ │ │ │ ├── package.json │ │ │ ├── plugin.xml │ │ │ ├── src/ │ │ │ │ └── electron/ │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ └── www/ │ │ │ └── sample.js │ │ └── www/ │ │ └── index.html │ ├── test-browser-plugin/ │ │ ├── plugin.xml │ │ └── www/ │ │ └── plugin.js │ ├── test-config-1.xml │ ├── test-config-2.xml │ ├── test-config-custom-scheme.xml │ ├── test-config-empty.xml │ ├── test-config-no-author-custom-email.xml │ ├── test-non-electron-plugin/ │ │ └── plugin.xml │ ├── testapp/ │ │ ├── config.xml │ │ └── cordova/ │ │ └── .gitkeep │ ├── testplugin/ │ │ ├── plugin.xml │ │ ├── src/ │ │ │ └── electron/ │ │ │ └── sample.json │ │ └── www/ │ │ └── plugin.js │ └── testplugin-empty-jsmodule/ │ ├── plugin.xml │ └── www/ │ └── MyTestPlugin2.js ├── unit/ │ └── lib/ │ ├── Api.spec.js │ ├── ManifestJsonParser.spec.js │ ├── PackageJsonParser.spec.js │ ├── SettingJsonParser.spec.js │ ├── build.spec.js │ ├── clean.spec.js │ ├── create.spec.js │ ├── handler.spec.js │ ├── parser.spec.js │ ├── prepare.spec.js │ ├── run.spec.js │ └── util.spec.js └── unit.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .asf.yaml ================================================ # 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. notifications: commits: commits@cordova.apache.org issues: issues@cordova.apache.org pullrequests_status: issues@cordova.apache.org pullrequests_comment: issues@cordova.apache.org github: rulesets: - name: "Default Branch Protection" type: branch branches: includes: - "~DEFAULT_BRANCH" - "release/*" - "rel/*" excludes: [] bypass_teams: - root restrict_deletion: true restrict_force_push: true ================================================ FILE: .eslintignore ================================================ # 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. bin/templates/platform_www/cordova.js tests/spec/fixtures/test-app-with-electron-plugin/ ================================================ FILE: .eslintrc.yml ================================================ # 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. root: true extends: '@cordova/eslint-config/node' overrides: - files: [tests/**/*.js] extends: '@cordova/eslint-config/node-tests' - files: [cordova-js-src/**/*.js] extends: '@cordova/eslint-config/browser' - files: [bin/templates/platform_www/cdv-electron-main.js] rules: standard/no-callback-literal: off ================================================ FILE: .gitattributes ================================================ # 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. * text eol=lf # source code *.php text *.css text *.sass text *.scss text *.less text *.styl text *.js text *.coffee text *.json text *.htm text *.html text *.xml text *.svg text *.txt text *.ini text *.inc text *.pl text *.rb text *.py text *.scm text *.sql text *.sh text *.bat text # templates *.ejs text *.hbt text *.jade text *.haml text *.hbs text *.dot text *.tmpl text *.phtml text # server config .htaccess text # git config .gitattributes text .gitignore text .gitconfig text # code analysis config .jshintrc text .jscsrc text .jshintignore text .csslintrc text # misc config *.yaml text *.yml text .editorconfig text # build config *.npmignore text *.bowerrc text # Heroku Procfile text .slugignore text # Documentation *.md text LICENSE text AUTHORS text # ## These files are binary and should be left untouched # # (binary is a macro for -text -diff) *.png binary *.jpg binary *.jpeg binary *.gif binary *.ico binary *.mov binary *.mp4 binary *.mp3 binary *.flv binary *.fla binary *.swf binary *.gz binary *.zip binary *.7z binary *.ttf binary *.eot binary *.woff binary *.pyc binary *.pdf binary ================================================ FILE: .github/ISSUE_TEMPLATE/BUG_REPORT.md ================================================ --- name: 🐛 Bug Report about: If something isn't working as expected. --- # Bug Report ## Problem ### What is expected to happen? ### What does actually happen? ## Information ### Command or Code ### Environment, Platform, Device ### Version information ## Checklist - [ ] I searched for existing GitHub issues - [ ] I updated all Cordova tooling to most recent version - [ ] I included all the necessary information above ================================================ FILE: .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md ================================================ --- name: 🚀 Feature Request about: A suggestion for a new functionality --- # Feature Request ## Motivation Behind Feature ## Feature Description ## Alternatives or Workarounds ================================================ FILE: .github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md ================================================ --- name: 💬 Support Question about: If you have a question, please check out our Slack or StackOverflow! --- Apache Cordova uses GitHub Issues as a feature request and bug tracker _only_. For usage and support questions, please check out the resources below. Thanks! --- You can get answers to your usage and support questions about **Apache Cordova** on: * Slack Community Chat: https://cordova.slack.com (you can sign-up at http://slack.cordova.io/) * StackOverflow: https://stackoverflow.com/questions/tagged/cordova using the tag `cordova` --- If you are using a tool that uses Cordova internally, like e.g. Ionic, check their support channels: * **Ionic Framework** * [Ionic Community Forum](https://forum.ionicframework.com/) * [Ionic Worldwide Slack](https://ionicworldwide.herokuapp.com/) * **PhoneGap** * [PhoneGap Developer Community](https://forums.adobe.com/community/phonegap) ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Issue Type - [ ] Bug Report - [ ] Feature Request - [ ] Support Question ## Description ## Information ### Command or Code ### Environment, Platform, Device ### Version information ## Checklist - [ ] I searched for already existing GitHub issues about this - [ ] I updated all Cordova tooling to their most recent version - [ ] I included all the necessary information above ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### Platforms affected ### Motivation and Context ### Description ### Testing ### Checklist - [ ] I've run the tests to see all new and existing tests pass - [ ] I added automated test coverage as appropriate for this change - [ ] Commit is prefixed with `(platform)` if this change only applies to one platform (e.g. `(android)`) - [ ] If this Pull Request resolves an issue, I linked to the issue in the text above (and used the correct [keyword to close issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/)) - [ ] I've updated the documentation if necessary ================================================ FILE: .github/workflows/ci.yml ================================================ # 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. name: Node CI on: push: branches-ignore: - 'dependabot/**' pull_request: branches: - '*' permissions: contents: read security-events: write jobs: test: name: NodeJS ${{ matrix.node-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: node-version: [20.x, 22.x, 24.x] os: [ubuntu-latest, windows-latest, macos-15] steps: - uses: actions/checkout@v6 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - name: Environment Information run: | node --version npm --version # "bin/templates/platform_www/cordova.js" is ignored because it is a generated file. # It contains mixed content from the npm package "cordova-js" and "./cordova-js-src". # The report might not be resolvable because of the external package. # If the report is related to this repository, it would be detected when scanning "./cordova-js-src". - uses: github/codeql-action/init@v4 with: languages: javascript queries: security-and-quality config: | paths-ignore: - coverage - node_modules - bin/templates/platform_www/cordova.js - name: npm install and test run: npm cit env: CI: true - uses: github/codeql-action/analyze@v4 # v4.6.0 - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 if: success() with: name: ${{ runner.os }} node.js ${{ matrix.node-version }} token: ${{ secrets.CORDOVA_CODECOV_TOKEN }} fail_ci_if_error: false ================================================ FILE: .github/workflows/release-audit.yml ================================================ # 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. name: Release Auditing on: push: branches-ignore: - 'dependabot/**' pull_request: branches: - '*' permissions: contents: read jobs: test: name: Audit Licenses runs-on: ubuntu-latest steps: # Checkout project - uses: actions/checkout@v6 # Check license headers (v2.0.0) - uses: erisu/apache-rat-action@46fb01ce7d8f76bdcd7ab10e7af46e1ea95ca01c # Setup environment with node - uses: actions/setup-node@v6 with: node-version: 24 # Install node packages - name: npm install packages run: npm ci # Check node package licenses (v2.0.1) - uses: erisu/license-checker-action@99cffa11264fe545fd0baa6c13bca5a00ae608f2 with: license-config: 'licence_checker.yml' include-asf-category-a: true ================================================ FILE: .gitignore ================================================ # 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. # System files .DS_Store # Editors .vscode # Testing, code coverage, and linting .nyc_output coverage temp # Logging logs *.log npm-debug.log* # others node_modules # Built files during packaging bin/templates/platform_www/cordova.js ================================================ FILE: .npmignore ================================================ # 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. # Config related files .* # Testing, code coverage, and linting coverage tests temp # Logging logs *.log npm-debug.log* # others cordova-js-src ================================================ FILE: .npmrc ================================================ # 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. registry=https://registry.npmjs.org ================================================ FILE: .ratignore ================================================ # 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. .git/ coverage/ node_modules/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Apache Cordova Anyone can contribute to Cordova. And we need your contributions. There are multiple ways to contribute: report bugs, improve the docs, and contribute code. For instructions on this, start with the [contribution overview](http://cordova.apache.org/contribute/). The details are explained there, but the important items are: - Check for Github issues that corresponds to your contribution and link or create them if necessary. - Run the tests so your patch doesn't break existing functionality. We look forward to your contributions! ================================================ FILE: DOCUMENTATION.md ================================================ # Cordova Electron Electron is a framework that uses web technologies (HTML, CSS, and JS) to build cross-platform desktop applications. - [Cordova Electron](#cordova-electron) - [System Requirements](#system-requirements) - [Linux](#linux) - [Mac](#mac) - [Windows](#windows) - [Quick Start](#quick-start) - [Create a Project](#create-a-project) - [Preview a Project](#preview-a-project) - [Build a Project](#build-a-project) - [Customizing the Application's Icon](#customizing-the-applications-icon) - [Customizing the Application's Window Options](#customizing-the-applications-window-options) - [How to Set the Window's Default Size](#how-to-set-the-windows-default-size) - [How to Disable the Window From Being Resizable](#how-to-disable-the-window-from-being-resizable) - [How to Make the Window Fullscreen](#how-to-make-the-window-fullscreen) - [How to Support Node.js and Electron APIs](#how-to-support-nodejs-and-electron-apis) - [Customizing BrowserWindow Instance Method](#customizing-browserwindow-instance-method) - [Load a local HTML file using relative path from the `{project_dir}/www` directory](#load-a-local-html-file-using-relative-path-from-the-project_dirwww-directory) - [Load a local HTML using full path](#load-a-local-html-using-full-path) - [Load a remote URL](#load-a-remote-url) - [Customizing the Electron's Main Process](#customizing-the-electrons-main-process) - [Bundling Node Modules](#bundling-node-modules) - [Cordova Package Handling](#cordova-package-handling) - [DevTools](#devtools) - [Debugging the Application's Main Process](#debugging-the-applications-main-process) - [Enable Developer Tool Exrtensions (Chrome Extensions)](#enable-developer-tool-exrtensions-chrome-extensions) - [Build Configurations](#build-configurations) - [Default Build Configurations](#default-build-configurations) - [Customizing Build Configurations](#customizing-build-configurations) - [Adding a `package`](#adding-a-package) - [Setting the Package `arch`](#setting-the-package-arch) - [Multi-Platform Build Support](#multi-platform-build-support) - [Signing Configurations](#signing-configurations) - [macOS Signing](#macos-signing) - [Windows Signing](#windows-signing) - [Linux Signing](#linux-signing) - [Plugins](#plugins) ## System Requirements ### Linux - **Python** version 2.7.x. It is recommended to check your Python version since some distributions like CentOS 6.x still use Python 2.6.x. ### Mac - **Python** version 2.7.x with support for TLS 1.2. - **Xcode**, the IDE for macOS, comes bundled with necessary software development tools to code signing and compiling native code for macOS. Version 8.2.1 or higher. - **RedHat Build Support** - **Homebrew**, one of the available macOS package managers, is used for installing additional tools and dependencies. Homebrew is needed to install RPM packaging dependencies. [**Brew Install Step**](https://brew.sh/) - **RPM**, a standard package manager for multiple Linux distributions, is the tool used for creating the Linux RPM package. To install this tool, use the following [**Homebrew**](https://brew.sh/) command: ```bash brew install rpm ``` ### Windows - **Python** version 2.7.10 or higher. - **PowerShell**, for Windows 7 users, must be at version 3.0 or greater for [app signing](https://www.electron.build/code-signing#windows). - **[Debugging Tools for Windows](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/)** is a toolkit for enhancing debug capabilities. It is recommended to install with the **Windows SDK 10.0.15063.468**. ## Quick Start ### Create a Project ```bash npm i -g cordova cordova create sampleApp cd sampleApp cordova platform add electron ``` _Notice: If using Cordova CLI prior to version 9.x, you will need to use the `cordova-electron` argument instead of `electron` for any command that requires the platform's name. For example:_ ```bash cordova platform add cordova-electron cordova run cordova-electron ``` ### Preview a Project It is not necessary to build the Electron application for previewing. Since the building process can be slow, it is recommended to pass in the `--nobuild` flag to disable the build process when previewing. ```bash cordova run electron --nobuild ``` ### Build a Project **Debug Builds** ```bash cordova build electron cordova build electron --debug ``` **Release Builds** ```bash cordova build electron --release ``` ## Customizing the Application's Icon Customized icon(s) can be declared with the `` element(s) in the `config.xml` file. There are two types of icons that can be defined, the application icon and the package installer icon. These icons should be defined in the Electron's platform node ``. One icon can be used for the application and installer, but this icon should be at least **512x512** pixels to work across all operating systems. _Notice: If a customized icon is not provided, the Apache Cordova default icons are used._ _Notice: macOS does not display custom icons when using `cordova run`. It defaults to the Electron's icon._ ```xml ``` You can supply unique icons for the application and installer by setting the `target` attribute. As mentioned above, the installer image should be **512x512** pixels to work across all platforms. ```xml ``` For devices that support high-DPI resolutions, such as Apple's Retina display, you can create a set of images with the same base filename but suffix with its multiplier. For example, if the base image's filename `icon.png` and is the standard resolution, then `icon@2x.png` will be treated as a high-resolution image that with a DPI doubled from the base. - icon.png (256px x 256px) - icon@2x.png (512px x 512px) If you want to support displays with different DPI densities at the same time, you can put images with different sizes in the same folder and use the filename without DPI suffixes. For example: ```xml ``` ## Customizing the Application's Window Options Electron provides many options to manipulate the [`BrowserWindow`](https://electronjs.org/docs/api/browser-window). This section will cover how to configure a few basic options. For a full list of options, please see the [Electron's Docs - BrowserWindow Options](https://electronjs.org/docs/api/browser-window#new-browserwindowoptions). Working with a Cordova project, it is recommended to create an Electron settings file within the project's root directory, and set its the relative path in the preference option `ElectronSettingsFilePath`, in the `config.xml` file. **Example `config.xml`:** ```xml ``` To override or set any BrowserWindow options or supply arguments to the loadURL method (BrowserWindow instance method), in this file the options are added to the `browserWindow` or `browserWindowInstance` property accordingly. **Example `res/electron/settings.json`:** ```json { "browserWindow": { ... }, "browserWindowInstance": { ... } } ``` ### How to Set the Window's Default Size By default, the `width` is set to **800** and the `height` set to **600**. This can be overridden by setting the `width` and `height` property. **Example:** ```json { "browserWindow": { "width": 1024, "height": 768 } } ``` ### How to Disable the Window From Being Resizable Setting the `resizable` flag property, you can disable the user's ability to resize your application's window. **Example:** ```json { "browserWindow": { "resizable": false } } ``` ### How to Make the Window Fullscreen Using the `fullscreen` flag property, you can force the application to launch in fullscreen. **Example:** ```json { "browserWindow": { "fullscreen": true } } ``` ### How to Support Node.js and Electron APIs Set the `nodeIntegration` flag property to `true`. By default, this property flag is set to `false` to support popular libraries that insert symbols with the same names that Node.js and Electron already uses. > You can read more about this at Electron docs: [I can not use jQuery/RequireJS/Meteor/AngularJS in Electron](https://electronjs.org/docs/faq#i-can-not-use-jqueryrequirejsmeteorangularjs-in-electron). **Example:** ```json { "browserWindow": { "webPreferences": { "nodeIntegration": false } } } ``` ### Customizing BrowserWindow Instance Method Objects created with `new BrowserWindow` have instance methods, one of such is `loadURL`. By default, `loadURL` loads a local HTML file which should be defined in `config.xml` under `content` tag. The `content` tag value can be a remote address (e.g. `http://`) or a path to a local HTML file using the `file://` protocol. For Cordova Electron only: It is possible to override this option from the Electron settings file which additionally provides more options. > Learn more about [loadURL - BrowserWindow Instance Method](https://electronjs.org/docs/api/browser-window#winloadurlurl-options). #### Load a local HTML file using relative path from the `{project_dir}/www` directory To override the local HTML file, place your HTML file anywhere in the `{project_dir}/www` directory and define the path in the Electron settings file. **Example** ```json "browserWindowInstance": { "loadURL": { "url": "custom.html" } } ``` #### Load a local HTML using full path To override the local HTML file using a full path, define the location of the local HTML file in the Electron settings file. **Example** ```json "browserWindowInstance": { "loadURL": { "url": "file://{full_path}/index.html" } } ``` #### Load a remote URL To load a remote address, define the `url` in the Electron settings file. **Example** ```json "browserWindowInstance": { "loadURL": { "url": "https://cordova.apache.org" } } ``` It is also possible to supply additional parameters using the [optional] `options` argument. **Example** ```json "browserWindowInstance": { "loadURL": { "url": "https://cordova.apache.org", "options": { "extraHeaders": "Content-Type: text/html" } } } ``` > For more information refer to [Electron documentation](https://electronjs.org/docs/api/browser-window#winloadurlurl-options). ## Customizing the Electron's Main Process If it is necessary to customize the Electron's main process beyond the scope of the Electron's configuration settings, changes can be added directly to the `cdv-electron-main.js` file located in `{PROJECT_ROOT_DIR}/platform/electron/platform_www/`. This is the application's main process. > ❗ However, it is not recommended to modify this file as the upgrade process for `cordova-electron` is to remove the old platform before adding the new version. ## Bundling Node Modules Supporting node modules with your application is possible by bundling them with your app. Installing modules, with npm, as a dependency of the Cordova project will automatically bundle them with your app. Below, is example instructions on how to bundle and enable the use of `lodash`. 1. Create a project using the steps from "[Create a Project](#create-a-project)". 2. Install `lodash` ```bash npm i -S loash ``` 3. Enable Node.js support by following the "[How to Support Node.js and Electron APIs](#how-to-support-nodejs-and-electron-apis)" 4. Import `lodash` in your application using `require` ```javascript const _ = require('lodash'); ``` ### Cordova Package Handling By default, all Cordova packages are currently installed as `dependencies` of the project. It is recommended that all Cordova packages are defined as `devDependencies` in the `package.json` file. It is safe to move them manually. Packages defined as a dependency will be bundled with the application and can increase the built application's size. ## DevTools The `--release` and `--debug` flags control the visibility of the DevTools. DevTools are shown by default on **Debug Builds** (`without a flag` or with `--debug`). If you want to hide the DevTools pass in the `--release` flag when building or running the application. > Note: DevTools can be closed or opened manually with the debug build. ## Debugging the Application's Main Process If you need to debug the application's main process, you can do so by enabling the inspector with the Election's `inspect` or `inspect-brk` flags. As these flags are provided by Electron, you will need to separate the Cordova flags from Electron flags with an additional `--` separator. For example: ```shell cordova run electron --nobuild --debug -- --inspect-brk=5858 ``` ## Enable Developer Tool Exrtensions (Chrome Extensions) To enable a devtool extension, for a debug build, add the `devToolsExtension` collection to the Cordova Electron Settings file (`ElectronSettingsFilePath`). For example: ```json { "devToolsExtension": [ "VUEJS_DEVTOOLS" ] } ``` Below is a list of pre-provided devtools that can be added. - `EMBER_INSPECTOR` - `REACT_DEVELOPER_TOOLS` - `BACKBONE_DEBUGGER` - `JQUERY_DEBUGGER` - `ANGULARJS_BATARANG` - `VUEJS_DEVTOOLS` - `REDUX_DEVTOOLS` - `REACT_PERF` - `CYCLEJS_DEVTOOL` - `APOLLO_DEVELOPER_TOOLS` - `MOBX_DEVTOOLS` If there are any devtools or extensions you wish to use that are avaiable in the Chrome App Store, you can add them by provided the extension's app ID. **Note:** The developer tools & extensions are not installed on a release build. **Example:** ```json { "browserWindow": { "width": 1024, "height": 768 } } ``` ## Build Configurations ### Default Build Configurations By default, with no additional configuration, `cordova build electron` will build default packages for the host operating system (OS) that triggers the command. Below, are the list of default packages for each OS. **Linux** | Package | Arch | | ------- | :---: | | tar.gz | x64 | **Mac** | Package | Arch | | ------- | :---: | | dmg | x64 | | zip | x64 | **Windows** | Package | Arch | | ------- | :---: | | nsis | x64 | ### Customizing Build Configurations If for any reason you would like to customize the build configurations, modifications are placed within the `build.json` file located in the project's root directory. E.g. `{PROJECT_ROOT_DIR}/build.json`. This file contains all build configurations for all platforms (Android, Electron, iOS, Windows). **Example Config Structure** ```json { "electron": {} } ``` Since the Electron framework is for creating cross-platform applications, multiple configurations are required for each OS build. The `electron` node, in the `build.json` file, contains three properties that separate the build configurations for each OS. **Example Config Structure with Each Platform** ```json { "electron": { "linux": {}, "mac": {}, "windows": {} } } ``` Each OS node contains properties that are used to identify what package to generate and how to sign. **OS Properties:** - `package` is an array of package formats that will be generated. - `arch` is an array of architectures that each package is built for. - `signing` is an object that contains signing information. See [Signing Configurations](#signing-configurations) for more information. Any properties that are undefined will fallback to default values. #### Adding a `package` The `package` property is an array list of packages to be outputted. If the property is defined, the default packages are not used unless added. The order of the packages has no importance. The configuration example below will generate `tar.gz`, `dmg` and `zip` packages for macOS. ```json { "electron": { "mac": { "package": [ "dmg", "tar.gz", "zip" ] } } } ``` **Available Packages by Operating System** | Package Type | Linux | macOS | Windows | | ------------ | :-----: | :--------------: | :------------------------: | | default | - | `dmg`
`zip` | - | | dmg | - | ✅ | - | | mas | - | ✅ | - | | mas-dev | | ✅ | - | | pkg | - | ✅ | - | | 7z | ✅ | ✅ | ✅ | | zip | ✅ | ✅ | ✅ | | tar.xz | ✅ | ✅ | ✅ | | tar.lz | ✅ | ✅ | ✅ | | tar.gz | ✅ | ✅ | ✅ | | tar.bz2 | ✅ | ✅ | ✅ | | dir | ✅ | ✅ | ✅ | | nsis | - | - | ✅ | | nsis-web | - | - | ✅ | | portable | - | - | ✅ | | appx | - | - | ✅ **[1]** | | msi | - | - | ✅ | | AppImage | ✅ | - | - | | snap | ✅ | - | - | | deb | ✅ | - | - | | rpm | ✅ | - | - | | freebsd | ✅ | - | - | | pacman | ✅ | - | - | | p5p | ✅ | - | - | | apk | ✅ | - | - | - **[1]** Only Window 10 can build AppX packages. #### Setting the Package `arch` The `arch` property is an array list of architectures that each package is built for. When the property is defined, the default arch is not used unless added. > ❗ Not all architectures are available for every operating system. Please review the [Electron Releases](https://github.com/electron/electron/releases/) to identify valid combinations. For example, macOS (Darwin) only supports x64. **Available Arch** - ia32 - x64 - armv71 - arm64 The example above will generate an `x64` `dmg` package. ```json { "electron": { "mac": { "package": [ "dmg" ], "arch": [ "x64" ] } } } ``` ### Multi-Platform Build Support > ❗ Not all platforms support this feature and may have limitations. Building for multiple platforms on a single operating system may possible but has limitations. It is recommended that the builder's operating system (host OS) matches with the platform that is being built. The matrix below shows each host OS and for which platforms they are capable of building applications. | Host **[1]** | Linux | Mac | Window | | ----------------------- | :-----: | :-----: | :-------------------------: | | Linux | ✅ | | ❗ **[2]** | | Mac **[3]** | ✅ | ✅ | ❗ **[2]** | | Window | | | ✅ | **Limitations:** - **[1]** If the app contains native dependency, it can only be compiled on the targeted platform. - **[2]** Linux and macOS are unable to build Windows Appx packages for Windows Store. - **[3]** [All required system dependencies, except rpm, will be downloaded automatically on demand. RPM can be installed with brew. (macOS Sierra 10.12+)](https://www.electron.build/multi-platform-build#macos) The example below enables multi-platform build for all OS and uses the default build configurations. ```json { "electron": { "linux": {}, "mac": {}, "windows": {} } } ``` ## Signing Configurations ### macOS Signing There are three types of signing targets. (`debug`, `release`, and `store`). Each section has the following properties: | key | description | | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | entitlements | String path value to entitlements file. | | entitlementsInherit | String path value to the entitlements file which inherits the security settings. | | identity | String value of the name of the certificate. | | [requirements](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/RequirementLang/RequirementLang.html) | String path value of requirements file.

❗ This is not available for the `mas` (store) signing configurations. | | provisioningProfile | String path value of the provisioning profile. | **Example Config:** ```json { "electron": { "mac": { "package": [ "dmg", "mas", "mas-dev" ], "signing": { "release": { "identity": "APACHE CORDOVA (TEAMID)", "provisioningProfile": "release.mobileprovision" } } } } } ``` For macOS signing, there are a few exceptions to how the signing information is used. By default, all packages with the exception of `mas` and `mas-dev`, use the `debug` and `release` signing configurations. Using the example config above, let's go over some use cases to better understand the exceptions. **Use Case 1:** ```bash cordova build electron --debug ``` The command above will: - Generate a `dmg` build and `mas-dev` build using the `debug` signing configurations. - Ignore the `mas` target package. *Use Case 2:* ```bash cordova build electron --release ``` The command above will: - Generate a `dmg` build using the `release` config. - Generate a `mas` build using the `store` config. - Ignore the `mas-dev` target package. ### Windows Signing The signing information is comprised of two types. (`debug`, `release`). Each section has the following properties: | key | description | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | certificateFile | String path to the certificate file. | | certificatePassword | String value of the certificate file's password.

**Alternative**: The password can be set on the environment variable `CSC_KEY_PASSWORD`. | | certificateSubjectName | String value of the signing certificate's subject.

❗ Required for EV Code Signing and requires Windows | | certificateSha1 | String value of the SHA1 hash of the signing certificate.

❗ Requires Windows | | signingHashAlgorithms | Collection of singing algorithms to be used. (`sha1`, `sha256`)

❗ AppX builds only support `sha256` | | additionalCertificateFile | String path to the additional certificate files. | **Example Config:** ```json { "electron": { "windows": { "package": [ "nsis" ], "signing": { "release": { "certificateFile": "path_to_files", "certificatePassword": "password" } } } } } ``` ### Linux Signing There are not signing requirements for Linux builds. ## Plugins All browser-based plugins are usable with the Electron platform. When adding a plugin, if the plugin supports both the `electron` and `browser` platform, the `electron` portion will be used. If the plugin misses `electron` but contains the `browser` implementation, it will fall back on the `browser` implementation. Internally, Electron is using Chromium (Chrome) as its web view. Some plugins may have conditions written specifically for each different browser. In this case, it may affect the behavior of what is intended. Since Electron may support feature that the browser does not, these plugins would possibly need to be updated for the `electron` platform. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Apache Cordova Licensed 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. ================================================ FILE: NOTICE ================================================ Apache Cordova Copyright 2012-2020 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). ================================================ FILE: README.md ================================================ # Cordova Electron [Electron](https://electronjs.org) is a framework that uses web technologies (HTML, CSS, and JS) to build cross-platform desktop applications. ## Platform Objectives - Build Desktop Applications (Linux, macOS, and Windows) - Sign Applications for Release ## Usage ### Cordova CLI ```shell $ npm install -g cordova@latest $ cordova create helloworld $ cd helloworld $ cordova platform add electron $ cordova run electron ``` ## Documentation For more documentation, please refer to the [DOCUMENTATION.md](https://github.com/apache/cordova-electron/blob/master/DOCUMENTATION.md) file. ## Contributions The Apache Cordova team would like to thank [Ibby Hadeed](https://www.npmjs.com/~ihadeed) for transferring the [`cordova-electron`](https://www.npmjs.com/package/cordova-electron) [npm](https://npmjs.com) package name to Apache Cordova. Thanks! ================================================ FILE: RELEASENOTES.md ================================================ ## Release Notes for Cordova Electron ### 4.0.0 (Mar 05, 2024) This release requires the environment to have **Node.js** `18.0.0` or higher. It is recommended to use the current LTS, which is `20.11.1` at the time of this release. Project Dependencies: * `cordova-common@^5.0.0` * `electron@^29.0.0` * `electron-builder@^24.12.0` * `electron-devtools-installer@^3.2.0` * `execa@^5.1.1` * `fs-extra@^11.2.0` Electron App Stack: * [Electron](https://www.electronjs.org/blog/electron-29-0) 29.0.0 * Chromium 122.0.6261.39 * Node v20.9.0 * V8 12.2 **Breaking:** * [GH-263](https://github.com/apache/cordova-electron/pull/263) feat!: bump `electron@^29.0.0` w/ supporting changes * [GH-266](https://github.com/apache/cordova-electron/pull/266) fix!: remove extra array wrapper from passed arguments * [GH-264](https://github.com/apache/cordova-electron/pull/264) npm(dep)!: bump `electron-builder@^24.12.0`, bump node engine requirement & CI * Updates Node Engine Requirement `>= 18.0.0` * [GH-271](https://github.com/apache/cordova-electron/pull/271) dep!: bump `fs-extra@^11.2.0` * [GH-265](https://github.com/apache/cordova-electron/pull/265) npm(dep)!: bump other dependencies **Others:** * [GH-232](https://github.com/apache/cordova-electron/pull/232) dep(npm): bump dev dependencies * [GH-270](https://github.com/apache/cordova-electron/pull/270) chore: rebuilt `package-lock.json` **CI:** * [GH-268](https://github.com/apache/cordova-electron/pull/268) ci: downgrade codecov action dependency to v3 * [GH-267](https://github.com/apache/cordova-electron/pull/267) ci: add node 20.x & use latest action dependencies * [GH-236](https://github.com/apache/cordova-electron/pull/236) ci: update github action workflow ### 3.1.0 (May 25, 2022) **Features:** * [GH-230](https://github.com/apache/cordova-electron/pull/230) feat: support custom **Electron** version * [GH-228](https://github.com/apache/cordova-electron/pull/228) feat: bump `electron@^14.2.9` **Fixes:** * [GH-229](https://github.com/apache/cordova-electron/pull/229) fix: `npm` 8 does not install plugin dependencies **Chores:** * [GH-231](https://github.com/apache/cordova-electron/pull/231) chore(npm): bump all dependencies to latest minor/path **Other Changes:** * [GH-226](https://github.com/apache/cordova-electron/pull/226) dep(npm): bump `@cordova/eslint-config@^4.0.0` w/ fix * [GH-221](https://github.com/apache/cordova-electron/pull/221) dep(npm): bump `minimist` from 1.2.5 to 1.2.6 * [GH-207](https://github.com/apache/cordova-electron/pull/207) doc: `mas-dev` is macOS package ### 3.0.0 (Sep 01, 2021) #### Environment Updates This release requires the environment to have **Node.js** `12.0.0` or higher. It is recommended to use the current LTS, which is `14.17.6` at the time of this release. #### Project Dependencies * `cordova-common@^4.0.2` * `electron@14.0.0` * `electron-builder@^22.11.7` * `electron-devtools-installer@^3.2.0` * `execa@^5.1.1` * `fs-extra@^10.0.0` #### Electron App Stack * [Electron](https://www.electronjs.org/blog/electron-14-0) 14.0.0 * Chromium 93.0.4577.58 * Node v14.17.0 * V8 v9.3 #### Breaking Changes * [GH-205](https://github.com/apache/cordova-electron/pull/205) feat!(Api): remove unused locations data * [GH-203](https://github.com/apache/cordova-electron/pull/203) feat!(electron): bump to `14.0.0` * [GH-202](https://github.com/apache/cordova-electron/pull/202) feat!: remove old VERSION file * [GH-199](https://github.com/apache/cordova-electron/pull/199) feat!: update node support * [GH-198](https://github.com/apache/cordova-electron/pull/198) feat!(dependencies): update other packages * `execa@5.1.1` * `fs-extra@10.0.0` * `jasmine@3.9.0` * [GH-197](https://github.com/apache/cordova-electron/pull/197) feat!(dependencies): bump **Electron** packages * `electron-builder@22.11.7` * `electron-devtools-installer@3.2.0` * [GH-175](https://github.com/apache/cordova-electron/pull/175) breaking: add plugin support #### Features * [GH-200](https://github.com/apache/cordova-electron/pull/200) feat: update supported platform options * [GH-184](https://github.com/apache/cordova-electron/pull/184) feat: forward **Electron**'s process `stdio` to terminal #### Fixes * [GH-183](https://github.com/apache/cordova-electron/pull/183) fix(npm): change prepack script to prepare * [GH-180](https://github.com/apache/cordova-electron/pull/180) fix(windows): **Electron** window not displaying * [GH-182](https://github.com/apache/cordova-electron/pull/182) fix: restrict deep merging on reserved keys * [GH-172](https://github.com/apache/cordova-electron/pull/172) fix(pkg): typo in field "`keywords`" * [GH-169](https://github.com/apache/cordova-electron/pull/169) fix(Api): do not depend on globals #### Refactor Changes * [GH-181](https://github.com/apache/cordova-electron/pull/181) refactor: use class static #### Chores * [GH-201](https://github.com/apache/cordova-electron/pull/201) chore(asf-license): add to header * [GH-171](https://github.com/apache/cordova-electron/pull/171) chore: clean up `package.json` #### Test & Other Changes * [GH-194](https://github.com/apache/cordova-electron/pull/194) build: build `cordova.js` on `prepare` * [GH-204](https://github.com/apache/cordova-electron/pull/204) test: cleanup and remove unneeded code * [GH-90](https://github.com/apache/cordova-electron/pull/90) test(create): fix, clean up & extend ### 2.0.0 (Sep 17, 2020) #### Environment Updates This release requires the environment to have **NodeJS** `10.0.0` or higher. It is recommended to use the current LTS, which is `12.18.4` at the time of this release. #### Project Dependencies * `cordova-common@^4.0.2` * `electron@10.1.2` * `electron-builder@^22.8.1` * `electron-devtools-installer@^3.1.1` * `execa@^4.0.3` * `fs-extra@^9.0.1` #### Electron App Stack * Electron 10.1.2 * Chromium 85.0.4183.98 * Node v12.16.3 * V8 v8.5 #### Breaking Changes * [GH-162](https://github.com/apache/cordova-electron/pull/162) breaking: use platform config parser * [GH-152](https://github.com/apache/cordova-electron/pull/152) breaking: bump `electron` & `electron-builder` * [GH-151](https://github.com/apache/cordova-electron/pull/151) breaking: bump `cordova-common@4.0.1` * [GH-145](https://github.com/apache/cordova-electron/pull/145) breaking(`npm`): update dependencies * [GH-142](https://github.com/apache/cordova-electron/pull/142) breaking: restructure the platform lib code * [GH-138](https://github.com/apache/cordova-electron/pull/138) breaking: remove platform-centered workflow * [GH-69](https://github.com/apache/cordova-electron/pull/69) breaking: drop `node` 6 and 8 support #### Features * [GH-160](https://github.com/apache/cordova-electron/pull/160) feat: install devtool extensions for debug builds * [GH-154](https://github.com/apache/cordova-electron/pull/154) feature: support custom `scheme` & `hostname` * [GH-148](https://github.com/apache/cordova-electron/pull/148) feat: support **Electron** arguments on run command * [GH-112](https://github.com/apache/cordova-electron/pull/112) feat: move ci to gh-actions * [GH-81](https://github.com/apache/cordova-electron/pull/81) feat: Support Loading Local HTML Files or Remote URL in the `BrowserWindow` #### Refactor * [GH-156](https://github.com/apache/cordova-electron/pull/156) refactor: remove more platform-centered files & update code * [GH-153](https://github.com/apache/cordova-electron/pull/153) refactor: cleanup unused code * [GH-129](https://github.com/apache/cordova-electron/pull/129) refactor (`create`): simplify project creation * [GH-124](https://github.com/apache/cordova-electron/pull/124) refactor: transform `for` * [GH-123](https://github.com/apache/cordova-electron/pull/123) refactor: transform `template` strings * [GH-122](https://github.com/apache/cordova-electron/pull/122) refactor: transform `object` shorthand * [GH-121](https://github.com/apache/cordova-electron/pull/121) refactor: transform `arrow` functions & `arrow` returns * [GH-120](https://github.com/apache/cordova-electron/pull/120) refactor: transform `var` to `let`/`const` * [GH-116](https://github.com/apache/cordova-electron/pull/116) refactor: remove `shelljs` and update tests * [GH-118](https://github.com/apache/cordova-electron/pull/118) refator: replace `shelljs`/`spawn` with `execa` * [GH-113](https://github.com/apache/cordova-electron/pull/113) refactor: `eslint` setup #### Fix * [GH-158](https://github.com/apache/cordova-electron/pull/158) fix(build): format `top-level` key for `nsis-web` * [GH-136](https://github.com/apache/cordova-electron/pull/136) fix(npm-script): prepack #### Chore, CI, & Test * [GH-168](https://github.com/apache/cordova-electron/pull/168) chore: bump dependencies & related usage * [GH-165](https://github.com/apache/cordova-electron/pull/165) chore: bump dependencies to latest * [GH-164](https://github.com/apache/cordova-electron/pull/164) chore: bump **Electron** related dependencies * [GH-147](https://github.com/apache/cordova-electron/pull/147) chore: various cleanup * [GH-144](https://github.com/apache/cordova-electron/pull/144) chore(npm): bump `@cordova/eslint-config@^3.0.0` w/ lint fix * [GH-125](https://github.com/apache/cordova-electron/pull/125) chore: configure app rel dependencies as abs paths * [GH-117](https://github.com/apache/cordova-electron/pull/117) chore: update **Electron** dependencies * [GH-128](https://github.com/apache/cordova-electron/pull/128) chore: update `package.json` * [GH-114](https://github.com/apache/cordova-electron/pull/114) chore: update `jasmine` dependency * [GH-110](https://github.com/apache/cordova-electron/pull/110) chore: bump version to 2.0.0-dev * [GH-96](https://github.com/apache/cordova-electron/pull/96) chore: fix typo * [GH-67](https://github.com/apache/cordova-electron/pull/67) chore: update development dependencies * [GH-68](https://github.com/apache/cordova-electron/pull/68) chore: bump **Electron** dependencies * chore(asf): update git notification settings * Update CONTRIBUTING.md * [GH-157](https://github.com/apache/cordova-electron/pull/157) ci: add node 14 to workflow * [GH-146](https://github.com/apache/cordova-electron/pull/146) ci: update workflow * [GH-141](https://github.com/apache/cordova-electron/pull/141) test (node-12.16.x): fix failures caused by shebang and rewire lint * [GH-131](https://github.com/apache/cordova-electron/pull/131) test: refactor with minor fixes & improvements ### 1.1.1 (Aug 20, 2019) * [GH-94](https://github.com/apache/cordova-electron/pull/94) chore: rebuilt `package-lock.json` for audit fix * [GH-79](https://github.com/apache/cordova-electron/pull/79) fix: filter icons only matching requirements * [GH-89](https://github.com/apache/cordova-electron/pull/89) fix: prepare missing dependencies failure * [GH-86](https://github.com/apache/cordova-electron/pull/86) refactor: improve create test spec speed * [GH-85](https://github.com/apache/cordova-electron/pull/85) fix: use `spyOn` for process global var ### 1.1.0 (Jun 28, 2019) * [GH-77](https://github.com/apache/cordova-electron/pull/77) fix: display correct package version in CLI * [GH-76](https://github.com/apache/cordova-electron/pull/76) Append Overridable/Top-Level per Platform Options * [GH-75](https://github.com/apache/cordova-electron/pull/75) fix: Typo in Build: `Singning` -> `Signing` * [GH-71](https://github.com/apache/cordova-electron/pull/71) Update `devDependencies` * [GH-66](https://github.com/apache/cordova-electron/pull/66) Bump dependency `fs-extra@^8.0.1` * [GH-70](https://github.com/apache/cordova-electron/pull/70) Bump `cordova-common@^3.2.0` * [GH-65](https://github.com/apache/cordova-electron/pull/65) Fixed `package.json` version to `1.1.0-dev` * [GH-62](https://github.com/apache/cordova-electron/pull/62) Fix Bundle Feature's Project Path Issue & Warning Output * [GH-58](https://github.com/apache/cordova-electron/pull/58) Set Author Name and Email to `package.json` * [GH-54](https://github.com/apache/cordova-electron/pull/54) Added Bundle Node Module Support * [GH-57](https://github.com/apache/cordova-electron/pull/57) Remove Maintainer Option from Linux Build JSON * [GH-59](https://github.com/apache/cordova-electron/pull/59) Allow Users to Set Linux App Category * [GH-51](https://github.com/apache/cordova-electron/pull/51) Append package top-level key options * [GH-48](https://github.com/apache/cordova-electron/pull/48) Implement Splash Screen handling * [GH-61](https://github.com/apache/cordova-electron/pull/61) Updated `DOCUMENTATION.md` * [GH-53](https://github.com/apache/cordova-electron/pull/53) Add Node.js 12 to CI Services * [GH-42](https://github.com/apache/cordova-electron/pull/42) Update the `config.xml` path in the XHR load eventListener * [GH-45](https://github.com/apache/cordova-electron/pull/45) Append Installer Icon to User Build Settings for Custom Builds * [GH-43](https://github.com/apache/cordova-electron/pull/43) Update `README`.md * [GH-41](https://github.com/apache/cordova-electron/pull/41) Update System Requirements in `DOCUMENTATION.md` ### 1.0.2 (Mar 15, 2019) * [GH-40](https://github.com/apache/cordova-electron/pull/40) Remove `temp` Dir After `create.spec.js` Test * [GH-39](https://github.com/apache/cordova-electron/pull/39) Added Missing License Header in `util.js` * [GH-38](https://github.com/apache/cordova-electron/pull/38) Support User Defined **Electron** Settings * [GH-37](https://github.com/apache/cordova-electron/pull/37) Remove `nodeIntegration` Warning by Setting Default to `false` * [GH-36](https://github.com/apache/cordova-electron/pull/36) Rename **Electron** Main Entry File * [GH-35](https://github.com/apache/cordova-electron/pull/35) Refactor `build.js` and Include Test Coverage * [GH-32](https://github.com/apache/cordova-electron/pull/32) Improve `temp` Folder Cleanup in `Api.spec.js` * [GH-33](https://github.com/apache/cordova-electron/pull/33) Update `cordova run` to Work with Pre-Cordova 9.x CLI ### 1.0.1 (Mar 04, 2019) Version bump with no change. ### 1.0.0 (Feb 25, 2019) * [GH-30](https://github.com/apache/cordova-electron/pull/30) Asset Install Fix from Mobilespec Report * [GH-29](https://github.com/apache/cordova-electron/pull/29) Correct File Header Licenses for Cordova JS Compile CMD * [GH-28](https://github.com/apache/cordova-electron/pull/28) Fix Scope Issue with Code Refactor * [GH-27](https://github.com/apache/cordova-electron/pull/27) Build Script Improvements * [GH-24](https://github.com/apache/cordova-electron/pull/24) Remove Unused and Unreachable Code * [GH-21](https://github.com/apache/cordova-electron/pull/21) Apply Missing Apache License Header * [GH-25](https://github.com/apache/cordova-electron/pull/25) Updated `Api.spec.js` Async Tests to Return Promise * [GH-22](https://github.com/apache/cordova-electron/pull/22) Add tests to `Api.spec.js` * [GH-23](https://github.com/apache/cordova-electron/pull/23) Test `module.exports.prepare` * [GH-19](https://github.com/apache/cordova-electron/pull/19) Adding New Test Specs * [GH-20](https://github.com/apache/cordova-electron/pull/20) Fix Audit Report For High Severity Vulnerability * [GH-18](https://github.com/apache/cordova-electron/pull/18) Refactor `prepare.js` and Increase Test Coverage * [GH-17](https://github.com/apache/cordova-electron/pull/17) Unit Test Improvements & Refactor (Create, Api, Handler, Parser) * [GH-16](https://github.com/apache/cordova-electron/pull/16) Prepare & Enable Travis CI Testing * [GH-15](https://github.com/apache/cordova-electron/pull/15) Implement SettingsJson Class Tests and Update Documentation * [GH-13](https://github.com/apache/cordova-electron/pull/13) Implement Electron Custom App Icons Functionality * [GH-10](https://github.com/apache/cordova-electron/pull/10) Added NPM Ignore * [GH-11](https://github.com/apache/cordova-electron/pull/11) Updated `README.md` * [GH-9](https://github.com/apache/cordova-electron/pull/9) Electron Build Improvements * [GH-8](https://github.com/apache/cordova-electron/pull/8) Create CDV Electron Settings JSON * [GH-7](https://github.com/apache/cordova-electron/pull/7) Electron Platform Release Preparation (Cordova 9) * [GH-6](https://github.com/apache/cordova-electron/pull/6) Cleanup Bin Files * [GH-5](https://github.com/apache/cordova-electron/pull/5) Updated Correct Version Information * [GH-4](https://github.com/apache/cordova-electron/pull/4) Electron Major Improvements & Feature Support * [GH-1](https://github.com/apache/cordova-electron/pull/1) First Draft Release * Add GitHub Pull Request Template * Added NPM Install Step * Added Run Command Support ================================================ FILE: bin/templates/cordova/Api.js ================================================ /* 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. */ try { module.exports = require('cordova-electron'); } catch (error) { module.exports = require('../../../lib/Api'); } ================================================ FILE: bin/templates/cordova/defaults.xml ================================================ ================================================ FILE: bin/templates/cordova/version ================================================ #!/usr/bin/env node /* 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 Api = require('./Api'); console.log(Api.version()); ================================================ FILE: bin/templates/cordova/version.bat ================================================ :: 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. @ECHO OFF SET script_path="%~dp0version" IF EXIST %script_path% ( node %script_path% %* ) ELSE ( ECHO. ECHO ERROR: Could not find 'version' script in 'cordova' folder, aborting...>&2 EXIT /B 1 ) ================================================ FILE: bin/templates/platform_www/cdv-electron-main.js ================================================ /* 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 { cordova } = require('./package.json'); // Module to control application life, browser window and tray. const { app, BrowserWindow, protocol, ipcMain, net } = require('electron'); // Electron settings from .json file. const cdvElectronSettings = require('./cdv-electron-settings.json'); const reservedScheme = require('./cdv-reserved-scheme.json'); const devTools = cdvElectronSettings.browserWindow.webPreferences.devTools ? require('electron-devtools-installer') : false; const scheme = cdvElectronSettings.scheme; const hostname = cdvElectronSettings.hostname; const isFileProtocol = scheme === 'file'; /** * The base url path. * E.g: * When scheme is defined as "file" the base path is "file://path-to-the-app-root-directory" * When scheme is anything except "file", for example "app", the base path will be "app://localhost" * The hostname "localhost" can be changed but only set when scheme is not "file" */ const basePath = (() => isFileProtocol ? `file://${__dirname}` : `${scheme}://${hostname}`)(); if (reservedScheme.includes(scheme)) throw new Error(`The scheme "${scheme}" can not be registered. Please use a non-reserved scheme.`); if (!isFileProtocol) { protocol.registerSchemesAsPrivileged([ { scheme, privileges: { standard: true, secure: true } } ]); } // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWindow; function createWindow () { // Create the browser window. let appIcon; if (fs.existsSync(path.join(__dirname, 'img/app.png'))) { appIcon = path.join(__dirname, 'img/app.png'); } else if (fs.existsSync(path.join(__dirname, 'img/icon.png'))) { appIcon = path.join(__dirname, 'img/icon.png'); } else { appIcon = path.join(__dirname, 'img/logo.png'); } const browserWindowOpts = Object.assign({}, cdvElectronSettings.browserWindow, { icon: appIcon }); browserWindowOpts.webPreferences.preload = path.join(app.getAppPath(), 'cdv-electron-preload.js'); browserWindowOpts.webPreferences.contextIsolation = true; // @todo review if using default "sandbox" is possible. When enabled, "Unable to load preload script:" error occurs. // Other require statements also fails. browserWindowOpts.webPreferences.sandbox = false; mainWindow = new BrowserWindow(browserWindowOpts); // Load a local HTML file or a remote URL. const cdvUrl = cdvElectronSettings.browserWindowInstance.loadURL.url; const loadUrl = cdvUrl.includes('://') ? cdvUrl : `${basePath}/${cdvUrl}`; const loadUrlOpts = Object.assign({}, cdvElectronSettings.browserWindowInstance.loadURL.options); mainWindow.loadURL(loadUrl, loadUrlOpts); // Open the DevTools. if (cdvElectronSettings.browserWindow.webPreferences.devTools) { mainWindow.webContents.openDevTools(); } // Emitted when the window is closed. mainWindow.on('closed', () => { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null; }); } function configureProtocol () { // `protocol.handle` was added in Electron 25.0 and replaced the deprecated // `protocol.{register,intercept}{String,Buffer,Stream,Http,File}Protocol`. if (protocol.handle) { // If using Electron 25.0+ protocol.handle(scheme, request => { const url = request.url.substr(basePath.length + 1); const fileUrl = `file://${path.normalize(path.join(__dirname, url))}`; return net.fetch(fileUrl); }); } else if (protocol.registerFileProtocol) { // If using Electron 24.x and older protocol.registerFileProtocol(scheme, (request, cb) => { const url = request.url.substr(basePath.length + 1); cb({ path: path.normalize(path.join(__dirname, url)) }); // eslint-disable-line n/no-callback-literal }); protocol.interceptFileProtocol('file', (_, cb) => { cb(null); }); } else { // Cant configure if missing `protocol.handle` and `protocol.registerFileProtocol`... console.info('Unable to configure the protocol.'); } } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', () => { if (!isFileProtocol) { configureProtocol(); } if (devTools && cdvElectronSettings.devToolsExtension) { const extensions = cdvElectronSettings.devToolsExtension.map(id => devTools[id] || id); devTools.default(extensions) // default = install extension .then((name) => console.log(`Added Extension: ${name}`)) .catch((err) => console.log('An error occurred: ', err)); } createWindow(); }); // Quit when all windows are closed. app.on('window-all-closed', () => { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { if (!isFileProtocol) { configureProtocol(); } createWindow(); } }); ipcMain.handle('cdv-plugin-exec', async (_, serviceName, action, ...args) => { if (cordova && cordova.services && cordova.services[serviceName]) { const plugin = require(cordova.services[serviceName]); return plugin[action] ? plugin[action](...args) : Promise.reject(new Error(`The action "${action}" for the requested plugin service "${serviceName}" does not exist.`)); } else { return Promise.reject(new Error(`The requested plugin service "${serviceName}" does not exist have native support.`)); } }); // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. ================================================ FILE: bin/templates/platform_www/cdv-electron-preload.js ================================================ /* 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 { contextBridge, ipcRenderer } = require('electron'); const { cordova } = require('./package.json'); contextBridge.exposeInMainWorld('_cdvElectronIpc', { exec: (success, error, serviceName, action, args) => { return ipcRenderer.invoke('cdv-plugin-exec', serviceName, action, args) .then( success, error ); }, hasService: (serviceName) => cordova && cordova.services && cordova.services[serviceName] }); ================================================ FILE: bin/templates/platform_www/cdv-electron-settings.json ================================================ { "browserWindow": { "height": 600, "webPreferences":{ "devTools": true, "nodeIntegration": false }, "width": 800 } } ================================================ FILE: bin/templates/platform_www/cdv-reserved-scheme.json ================================================ [ "about", "data", "dav", "dns", "example", "ftp", "geo", "http", "https", "im", "imap", "info", "ldap", "mailto", "news", "nfs", "pkcs11", "pop", "reload", "rtsp", "s3", "service", "session", "sftp", "shttp", "sip", "sips", "sms", "svn", "tag", "tel", "telnet", "tftp", "tip", "turn", "turns", "tv", "vnc", "ws", "wss", "xmpp" ] ================================================ FILE: bin/templates/platform_www/config.xml ================================================ ================================================ FILE: cordova-js-src/confighelper.js ================================================ /* * * 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. * */ let config; function Config (xhr) { function loadPreferences (xhr) { const parser = new DOMParser(); const doc = parser.parseFromString(xhr.responseText, 'application/xml'); const preferences = doc.getElementsByTagName('preference'); return Array.prototype.slice.call(preferences); } this.xhr = xhr; this.preferences = loadPreferences(this.xhr); } function readConfig (success, error) { let xhr; // eslint-disable-line prefer-const if (typeof config !== 'undefined') { success(config); } function fail (msg) { console.error(msg); if (error) { error(msg); } } const xhrStatusChangeHandler = function () { if (xhr.readyState === 4) { if (xhr.status === 200 || xhr.status === 304 || xhr.status === 0 /* file:// */) { config = new Config(xhr); success(config); } else { fail('[Electron][cordova.js][xhrStatusChangeHandler] Could not XHR config.xml: ' + xhr.statusText); } } }; xhr = new XMLHttpRequest(); xhr.addEventListener('load', xhrStatusChangeHandler); try { xhr.open('get', 'config.xml', true); xhr.send(); } catch (e) { fail('[Electron][cordova.js][readConfig] Could not XHR config.xml: ' + JSON.stringify(e)); } } /** * Reads a preference value from config.xml. * Returns preference value or undefined if it does not exist. * @param {String} preferenceName Preference name to read */ Config.prototype.getPreferenceValue = function getPreferenceValue (preferenceName) { const preferenceItem = this.preferences && this.preferences.filter(function (item) { return item.attributes.name && item.attributes.name.value === preferenceName; }); if (preferenceItem && preferenceItem[0] && preferenceItem[0].attributes && preferenceItem[0].attributes.value) { return preferenceItem[0].attributes.value.value; } }; exports.readConfig = readConfig; ================================================ FILE: cordova-js-src/exec.js ================================================ /* * * 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. * */ /* global require, module, console */ const cordova = require('cordova'); const execProxy = require('cordova/exec/proxy'); /** * Execute a cordova command. It is up to the native side whether this action * is synchronous or asynchronous. The native side can return: * Synchronous: PluginResult object as a JSON string * Asynchronous: Empty string "" * If async, the native side will cordova.callbackSuccess or cordova.callbackError, * depending upon the result of the action. * * @param {Function} success The success callback * @param {Function} fail The fail callback * @param {String} service The name of the service to use * @param {String} action Action to be run in cordova * @param {String[]} [args] Zero or more arguments to pass to the method */ module.exports = function (success, fail, service, action, args) { if (window._cdvElectronIpc.hasService(service)) { // Electron based plugin support window._cdvElectronIpc.exec(success, fail, service, action, args); } else { // Fall back for browser based plugin support... const proxy = execProxy.get(service, action); args = args || []; if (proxy) { const callbackId = service + cordova.callbackId++; if (typeof success === 'function' || typeof fail === 'function') { cordova.callbacks[callbackId] = { success, fail }; } try { // callbackOptions param represents additional optional parameters command could pass back, like keepCallback or // custom callbackId, for example {callbackId: id, keepCallback: true, status: cordova.callbackStatus.JSON_EXCEPTION } const onSuccess = function (result, callbackOptions) { callbackOptions = callbackOptions || {}; let callbackStatus; // covering both undefined and null. // strict null comparison was causing callbackStatus to be undefined // and then no callback was called because of the check in cordova.callbackFromNative // see CB-8996 Mobilespec app hang on windows if (callbackOptions.status !== undefined && callbackOptions.status !== null) { callbackStatus = callbackOptions.status; } else { callbackStatus = cordova.callbackStatus.OK; } cordova.callbackSuccess(callbackOptions.callbackId || callbackId, { status: callbackStatus, message: result, keepCallback: callbackOptions.keepCallback || false }); }; const onError = function (err, callbackOptions) { callbackOptions = callbackOptions || {}; let callbackStatus; // covering both undefined and null. // strict null comparison was causing callbackStatus to be undefined // and then no callback was called because of the check in cordova.callbackFromNative // note: status can be 0 if (callbackOptions.status !== undefined && callbackOptions.status !== null) { callbackStatus = callbackOptions.status; } else { callbackStatus = cordova.callbackStatus.OK; } cordova.callbackError(callbackOptions.callbackId || callbackId, { status: callbackStatus, message: err, keepCallback: callbackOptions.keepCallback || false }); }; proxy(onSuccess, onError, args); } catch (e) { console.log('Exception calling native with command :: ' + service + ' :: ' + action + ' ::exception=' + e); } } else { console.log('Error: exec proxy not found for :: ' + service + ' :: ' + action); if (typeof fail === 'function') { fail('Missing Command Error'); } } } }; ================================================ FILE: cordova-js-src/platform.js ================================================ /* * * 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. * */ module.exports = { id: 'electron', cordovaVersion: '4.2.0', // cordova-js bootstrap: function () { const modulemapper = require('cordova/modulemapper'); const channel = require('cordova/channel'); modulemapper.clobbers('cordova/exec/proxy', 'cordova.commandProxy'); channel.onNativeReady.fire(); document.addEventListener('visibilitychange', function () { if (document.hidden) { channel.onPause.fire(); } else { channel.onResume.fire(); } }); // End of bootstrap } }; ================================================ FILE: lib/Api.js ================================================ /* 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. */ /* this file is found by cordova-lib when you attempt to 'cordova platform add PATH' where path is this repo. */ const path = require('node:path'); const fs = require('node:fs'); const { ActionStack, ConfigChanges: { PlatformMunger }, CordovaError, CordovaLogger, ConfigParser, events: selfEvents, PlatformJson, PluginInfoProvider } = require('cordova-common'); const { getPackageJson } = require('./util'); const Parser = require('./parser'); function setupEvents (externalEventEmitter) { if (externalEventEmitter) { // This will make the platform internal events visible outside selfEvents.forwardEventsTo(externalEventEmitter); return externalEventEmitter; } // There is no logger if external emitter is not present, // so attach a console logger CordovaLogger.get().subscribe(selfEvents); return selfEvents; } class Api { constructor (platform, platformRootDir, events) { this.platform = 'electron'; // The path to the platform root directory must always be provided. if (!platformRootDir || !fs.existsSync(platformRootDir)) { throw new CordovaError('The path to the platform root directory was undefined or invalid.'); } this.root = platformRootDir; this.events = setupEvents(events); this.parser = new Parser(this.root); this.handler = require('./handler'); this.locations = { platformRootDir, root: this.root, www: path.join(this.root, 'www'), res: path.join(this.root, 'res'), platformWww: path.join(this.root, 'platform_www'), configXml: path.join(this.root, 'config.xml'), defaultConfigXml: path.join(this.root, 'cordova/defaults.xml'), build: path.join(this.root, 'build'), buildRes: path.join(this.root, 'build-res'), cache: path.join(this.root, 'cache') }; this._platformJson = PlatformJson.load(this.root, this.platform); this._pluginInfoProvider = new PluginInfoProvider(); this._munger = new PlatformMunger(this.platform, this.root, this._platformJson, this._pluginInfoProvider); } getPlatformInfo () { return { locations: this.locations, root: this.root, name: this.platform, version: Api.version(), projectConfig: this.config }; } prepare (cordovaProject, options) { // Use Cordova Electron's Common Dependency cordovaProject.projectConfig = new ConfigParser(cordovaProject.projectConfig.path); return require('./prepare').prepare.call(this, cordovaProject, options); } addPlugin (pluginInfo, installOptions) { if (!pluginInfo) { return Promise.reject(new Error('Missing plugin info parameter. The first parameter should contain a valid PluginInfo instance.')); } 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(); let platform = this.platform; if (!pluginInfo.getPlatformsArray().includes(platform)) { // if `cordova-electron` is not defined in plugin.xml, `browser` is used instead. platform = 'browser'; } // gather all files needs to be handled during install pluginInfo.getFilesAndFrameworks(platform) .concat(pluginInfo.getAssets(platform)) .concat(pluginInfo.getJsModules(platform)) .forEach((item) => { actions.push(actions.createAction( this._getInstaller(item.itemType), [item, pluginInfo.dir, pluginInfo.id, installOptions], this._getUninstaller(item.itemType), [item, pluginInfo.dir, pluginInfo.id, installOptions])); }); // run through the action stack return actions.process(platform, this.root) .then(() => { // 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(platform, pluginInfo, targetDir); }); } removePlugin (pluginInfo, uninstallOptions) { if (!pluginInfo) { return Promise.reject(new Error('Missing plugin info parameter. The first parameter should contain a valid PluginInfo instance.')); } uninstallOptions = uninstallOptions || {}; // CB-10108 platformVersion option is required for proper plugin installation uninstallOptions.platformVersion = uninstallOptions.platformVersion || this.getPlatformInfo().version; const actions = new ActionStack(); let platform = this.platform; if (!pluginInfo.getPlatformsArray().includes(platform)) { // if `cordova-electron` is not defined in plugin.xml, `browser` is used instead. platform = 'browser'; } // queue up plugin files pluginInfo.getFilesAndFrameworks(platform) .concat(pluginInfo.getAssets(platform)) .concat(pluginInfo.getJsModules(platform)) .forEach((item) => { actions.push(actions.createAction( this._getUninstaller(item.itemType), [item, pluginInfo.dir, pluginInfo.id, uninstallOptions], this._getInstaller(item.itemType), [item, pluginInfo.dir, pluginInfo.id, uninstallOptions])); }); // run through the action stack return actions.process(platform, this.root) .then(() => { 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(pluginInfo, /* is_top_level= */true) .save_all(); const targetDir = uninstallOptions.usePlatformWww ? this.getPlatformInfo().locations.platformWww : this.getPlatformInfo().locations.www; this._removeModulesInfo(pluginInfo, targetDir); // Remove stale plugin directory // @todo this should be done by plugin files uninstaller fs.rmSync(path.resolve(this.root, 'Plugins', pluginInfo.id), { recursive: true, force: true }); }); } _getInstaller (type) { return (item, plugin_dir, plugin_id, options, project) => { const installer = this.handler[type]; if (!installer) { this.events.emit('warn', `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); } } }; } _getUninstaller (type) { return (item, plugin_dir, plugin_id, options, project) => { const installer = this.handler[type]; if (!installer) { this.events.emit('warn', `electron 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'].indexOf(type) > -1) { return installer.uninstall(item, wwwDest, plugin_id); } else { return installer.uninstall(item, plugin_dir, this.root, plugin_id, options, project); } } }; } /** * 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 (platform, plugin, targetDir) { const installedModules = this._platformJson.root.modules || []; const installedPaths = installedModules.map((installedModule) => installedModule.file); const modulesToInstall = plugin.getJsModules(platform) .filter((moduleToInstall) => installedPaths.indexOf(moduleToInstall.file) === -1) .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 = {}; } 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 /www or /platform_www * directories. */ _writePluginModules (targetDir) { // Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js const final_contents = `cordova.define('cordova/plugin_list', function (require, exports, module) { module.exports = ${JSON.stringify(this._platformJson.root.modules, null, ' ')}; module.exports.metadata = // TOP OF METADATA ${JSON.stringify(this._platformJson.root.plugin_metadata || {}, null, ' ')} // BOTTOM OF METADATA });`; fs.mkdirSync(targetDir, { recursive: true }); fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf8'); } /** * 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('/')); /* eslint no-useless-escape : 0 */ const updatedModules = installedModules .filter((installedModule) => modulesToRemove.indexOf(installedModule.file) === -1); this._platformJson.root.modules = updatedModules; if (this._platformJson.root.plugin_metadata) { delete this._platformJson.root.plugin_metadata[plugin.id]; } this._writePluginModules(targetDir); this._platformJson.save(); } build (buildOptions) { return require('./build').run.call(this, buildOptions, this); } run (runOptions) { return require('./run').run.call(this, runOptions); } clean (cleanOptions) { return require('./clean').run(cleanOptions); } requirements () { return require('./check_reqs').run(); } static version () { const packageJson = getPackageJson(); return packageJson.version; } /** * @todo create projectInstance and fulfill promise with it. */ static updatePlatform () { return Promise.resolve(); } static createPlatform (dest, config, options, events) { if (!config) throw new CordovaError('An Electron platform can not be created with a missing config argument.'); events = setupEvents(events); const name = config.name(); const id = config.packageName(); try { // we create the project using our scripts in this platform return require('./create').createProject(dest, id, name, options).then(() => new Api(null, dest, events)); } catch (e) { events.emit('error', 'createPlatform is not callable from the electron project API.'); } } } module.exports = Api; ================================================ FILE: lib/ManifestJsonParser.js ================================================ /* 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'); class ManifestJsonParser { constructor (wwwDir) { this.path = path.join(wwwDir, 'manifest.json'); this.www = wwwDir; this.manifest = { background_color: '#FFF', display: 'standalone', orientation: 'any', start_url: 'index.html' }; } configureIcons (config) { /** * given a tag like this : * * * configParser returns icons that look like this : * { * src: 'res/ios/icon.png', * target: undefined, * density: 'mdpi', * platform: null, * width: 57, * height: 57 * } * * manifest expects them to be like this : * { * "src": "images/touch/icon-128x128.png", * "type": "image/png", * "sizes": "128x128" * } */ const icons = config.getStaticResources(this.platform, 'icon') .map((icon) => ({ src: icon.src, type: 'image/png', sizes: `${icon.width}x${icon.height}` })); if (icons.length) this.manifest.icons = icons; return this; } configureOrientation (config) { // orientation // const oriPref = config.getGlobalPreference('Orientation'); if (oriPref === 'landscape' || oriPref === 'portrait') { this.manifest.orientation = oriPref; } return this; } configureStartUrl (config) { // get start_url const contentNode = config.doc.find('content'); if (contentNode) this.manifest.start_url = contentNode.attrib.src; return this; } configureThemeColor (config) { if (this.manifest.start_url) { const startUrlPath = path.join(this.www, this.manifest.start_url); if (fs.existsSync(startUrlPath)) { // fetch start url file content and parse for theme-color. const contents = fs.readFileSync(startUrlPath, 'utf8'); const result = /]*name="theme-color")\s[^>]*content="([^>]*)"/i.exec(contents); // If theme-color exists, the value is in index 1. if (result && result.length >= 2) this.manifest.theme_color = result[1]; } } if (this.manifest.start_url && !this.manifest.theme_color) { const themeColor = config.getGlobalPreference('StatusBarBackgroundColor'); if (themeColor) this.manifest.theme_color = themeColor; } return this; } configure (config) { if (config) { if (config.name()) this.manifest.name = config.name(); if (config.shortName()) this.manifest.short_name = config.shortName(); if (config.packageName()) this.manifest.version = config.packageName(); if (config.description()) this.manifest.description = config.description(); if (config.author()) this.manifest.author = config.author(); this.configureIcons(config) .configureOrientation(config) .configureStartUrl(config) .configureThemeColor(config); } return this; } write () { fs.writeFileSync(this.path, JSON.stringify(this.manifest, null, 2), 'utf8'); } } module.exports = ManifestJsonParser; ================================================ FILE: lib/PackageJsonParser.js ================================================ /* 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 { getPackageJson } = require('./util'); class PackageJsonParser { constructor (wwwDir, projectRootDir) { // make sure www dir exists if (!fs.existsSync(wwwDir)) { fs.mkdirSync(wwwDir, { recursive: true }); } this.path = path.join(wwwDir, 'package.json'); if (!fs.existsSync(this.path)) { fs.writeFileSync(this.path, '{}', 'utf8'); } // Electron App Package this.package = JSON.parse(fs.readFileSync(this.path, 'utf8') || '{}'); // Force settings that are not allowed to change. this.package.main = 'cdv-electron-main.js'; this.www = wwwDir; this.projectRootDir = projectRootDir; } configure (config) { if (config) { this.package.name = config.packageName() || 'io.cordova.hellocordova'; this.package.displayName = config.name() || 'HelloCordova'; this.package.version = config.version() || '1.0.0'; this.package.description = config.description() || 'A sample Apache Cordova application that responds to the deviceready event.'; this.configureHomepage(config); this.configureLicense(config); if (config.doc.find('author').attrib.email) { this.package.author = { name: config.author() || 'Apache Cordova Team', email: config.doc.find('author').attrib.email }; } else { this.package.author = config.author() || 'Apache Cordova Team'; } } return this; } static _orderObject (obj) { const ordered = {}; Object.keys(obj).sort().forEach(key => { ordered[key] = obj[key]; }); return ordered; } enableDevTools (enable = false) { const pkgJson = getPackageJson(); const devToolsDependency = 'electron-devtools-installer'; if (enable) { if (!this.package.dependencies) { this.package.dependencies = {}; } this.package.dependencies[devToolsDependency] = pkgJson.dependencies[devToolsDependency]; this.package.dependencies = PackageJsonParser._orderObject(this.package.dependencies); } else if ( this.package.dependencies && this.package.dependencies[devToolsDependency] ) { delete this.package.dependencies[devToolsDependency]; } return this; } configureHomepage (config) { this.package.homepage = (config.doc.find('author') && config.doc.find('author').attrib.href) || 'https://cordova.io'; } configureLicense (config) { this.package.license = (config.doc.find('license') && config.doc.find('license').text && config.doc.find('license').text.trim()) || 'Apache-2.0'; } write () { fs.writeFileSync(this.path, JSON.stringify(this.package, null, 2), 'utf8'); } } module.exports = PackageJsonParser; ================================================ FILE: lib/SettingJsonParser.js ================================================ /* 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 { deepMerge } = require('./util'); class SettingJsonParser { constructor (wwwDir) { this.path = path.join(wwwDir, 'cdv-electron-settings.json'); this.package = require(this.path); } configure (config, options, userElectronSettingsPath) { // Set loadURL path from config.xml or fallback to index.html const contentNode = config.doc.find('content'); const contentSrc = (contentNode && contentNode.attrib.src) || 'index.html'; const scheme = config.getPreference('scheme', 'electron'); const hostname = config.getPreference('hostname', 'electron'); this.package.browserWindowInstance = { loadURL: { url: contentSrc } }; this.package.scheme = scheme || 'file'; this.package.hostname = hostname || 'localhost'; // Apply user settings ontop of defaults. if (userElectronSettingsPath) { const userElectronSettings = require(userElectronSettingsPath); this.package = deepMerge(this.package, userElectronSettings); } if (options) { this.package.browserWindow.webPreferences.devTools = !options.release; } return this; } write () { fs.writeFileSync(this.path, JSON.stringify(this.package, null, 2), 'utf8'); } } module.exports = SettingJsonParser; ================================================ FILE: lib/build/base.json ================================================ { "config": { "appId": "${APP_ID}", "productName": "${APP_TITLE}", "directories": { "app": "${APP_WWW_DIR}", "buildResources": "${APP_BUILD_RES_DIR}", "output": "${APP_BUILD_DIR}" }, "electronVersion": "${ELECTRON_INSTALLED_VERSION}", "electronDownload": { "version": "${ELECTRON_INSTALLED_VERSION}" } } } ================================================ FILE: lib/build/darwin.json ================================================ { "mac": [], "config": { "mac": { "type": "${BUILD_TYPE}", "icon": "${APP_INSTALLER_ICON}", "target": [ { "target": "dmg", "arch": [ "x64" ] }, { "target": "zip", "arch": [ "x64" ] } ] } } } ================================================ FILE: lib/build/linux.json ================================================ { "linux": [], "config": { "linux": { "icon": "${APP_INSTALLER_ICON}", "target": [ { "target": "tar.gz", "arch": [ "x64" ] } ] } } } ================================================ FILE: lib/build/win32.json ================================================ { "win": [], "config": { "win": { "icon": "${APP_INSTALLER_ICON}", "target": [ { "target": "nsis", "arch": [ "x64" ] } ] } } } ================================================ FILE: lib/build.js ================================================ /* 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 events = require('cordova-common').events; const { deepMerge, getInstalledElectronVersion } = require('./util'); const PLATFORM_MAPPING = { linux: 'linux', mac: 'darwin', windows: 'win32', win: 'win32' }; const TARGET_TOP_LEVEL_KEY_MAPPING = { 'nsis-web': 'nsisWeb' }; class ElectronBuilder { constructor (buildOptions, api) { this.api = api; this.isDevelopment = buildOptions.debug; this.buildConfig = buildOptions && buildOptions.buildConfig && fs.existsSync(buildOptions.buildConfig) ? require(buildOptions.buildConfig) : false; } configure () { this.buildSettings = this.configureUserBuildSettings() .configureBuildSettings(); // Replace the templated placeholders with the project defined settings into the buildSettings. this.injectProjectConfigToBuildSettings(); return this; } configureUserBuildSettings () { if (this.buildConfig && this.buildConfig.electron) { let userBuildSettings = {}; for (const platform in this.buildConfig.electron) { if (platform !== 'mac' && platform !== 'linux' && platform !== 'windows') continue; const platformConfigs = this.buildConfig.electron[platform]; /** * In this scenario, the user has added a valid platform to build for but has not provided any custom build configurations. * This will fetch thew platform's default build configuration. * Each platform's default build configurations are located in the "./build/" directory. */ if (Object.keys(platformConfigs).length === 0) { userBuildSettings = deepMerge(userBuildSettings, this.fetchPlatformDefaults(PLATFORM_MAPPING[platform])); continue; } /** * Validate that the platform configuration properties provided are valid. * Any invalid property will be warned and iggnored. * If there is there are no valid properties */ if (!this.__validateUserPlatformBuildSettings(platformConfigs)) { throw Error(`The platform "${platform}" contains an invalid property. Valid properties are: package, arch, signing`); } // Electron uses "win" as it's key, not "windows", so we will update here. We use windows in our settings for clarity. this.__formatAppendUserSettings( (platform === 'windows' ? 'win' : platform), platformConfigs, userBuildSettings ); this.___overridablePerPlatformOptions( (platform === 'windows' ? 'win' : platform), platformConfigs, userBuildSettings ); } this.userBuildSettings = userBuildSettings; } return this; } __formatAppendUserSettings (platform, platformConfigs, userBuildSettings) { // Add the platform at the root level to trigger build. userBuildSettings[platform] = []; // Add the config placeholder for build configurations (only add once if missing) if (!userBuildSettings.config) userBuildSettings.config = {}; userBuildSettings.config[platform] = { target: [] }; // Apply custom app installer icon when the user is using a custom build configuration // eslint-disable-next-line no-template-curly-in-string userBuildSettings.config[platform].icon = '${APP_INSTALLER_ICON}'; // Only macOS has a build type distinction. (development or distribution) // eslint-disable-next-line no-template-curly-in-string if (platform === 'mac') userBuildSettings.config[platform].type = '${BUILD_TYPE}'; if (platformConfigs.package) { platformConfigs.package.forEach((target) => { if (target === 'mas') { userBuildSettings.config.mas = {}; } if (typeof target === 'object' && Object.keys(target).length !== 0) { const targetKey = Object.keys(target)[0]; const rootKey = TARGET_TOP_LEVEL_KEY_MAPPING[targetKey] || targetKey; userBuildSettings.config[rootKey] = target[targetKey]; target = targetKey; } /** * The target of arch values are not validated as electron-builder will handle this. * If the arch value is missing, 64-bit will be defaulted. */ userBuildSettings.config[platform].target.push({ target, arch: platformConfigs.arch || ['x64'] }); }); } else { /** * We will fetch and use the defaults when a package type is not identified. * If the arch value is identified, we will update each default package with the correct arch. */ const platformDefaults = this.fetchPlatformDefaults(PLATFORM_MAPPING[platform]); const platformTargetPackages = platformDefaults.config[platform].target; if (platformConfigs.arch) { platformTargetPackages.forEach((pkg, i) => { platformTargetPackages[i].arch = platformConfigs.arch; }); } userBuildSettings.config[platform].target = platformTargetPackages; } if (platformConfigs.signing) { this.__appendUserSigning(platform, platformConfigs.signing, userBuildSettings); } } ___overridablePerPlatformOptions (platform, platformConfigs, userBuildSettings) { const PLATFORM_TOP_LEVEL_OPTIONS = { allPlatforms: [ 'appId', 'artifactName', 'asar', 'compression', 'detectUpdateChannel', 'electronUpdaterCompatibility', 'extraFiles', 'extraResources', 'fileAssociations', 'files', 'forceCodeSigning', 'generateUpdatesFilesForAllChannels', 'icon', 'publish', 'releaseInfo' ], linux: [ 'category', 'description', 'desktop', 'executableName', 'maintainer', 'mimeTypes', 'synopsis', 'vendor' ], mac: [ 'binaries', 'bundleShortVersion', 'bundleVersion', 'category', 'darkModeSupport', 'electronLanguages', 'extendInfo', 'extraDistFiles', 'gatekeeperAssess', 'hardenedRuntime', 'helperBundleId', 'minimumSystemVersion' ], win: [ 'legalTrademarks', 'publisherName', 'requestedExecutionLevel', 'rfc3161TimeStampServer', 'signAndEditExecutable', 'signDlls', 'timeStampServer', 'verifyUpdateCodeSignature' ] }; for (const option in platformConfigs) { if ( PLATFORM_TOP_LEVEL_OPTIONS.allPlatforms.includes(option) || PLATFORM_TOP_LEVEL_OPTIONS[platform].includes(option) ) { userBuildSettings.config[platform][option] = platformConfigs[option]; } } } __appendUserSigning (platform, signingConfigs, userBuildSettings) { if (platform === 'linux') { events.emit('warn', 'The provided signing information for the Linux platform is ignored. Linux does not support signing.'); return this; } const config = this.isDevelopment ? signingConfigs.debug : signingConfigs.release; if (platform === 'mac' && config) { this.__appendMacUserSigning(config, userBuildSettings.config.mac); } const masConfig = this.isDevelopment ? null : (signingConfigs.store || null); if (platform === 'mac' && masConfig) { // Requirements is not available for mas. if (masConfig.requirements) delete masConfig.requirements; this.__appendMacUserSigning(masConfig, userBuildSettings.config.mas); } if (platform === 'win' && config) { this.__appendWindowsUserSigning(config, userBuildSettings.config.win); } } __validateUserPlatformBuildSettings (platformConfigs) { return !!( platformConfigs.package || platformConfigs.arch || platformConfigs.signing ); } __appendMacUserSigning (config, buildConfigs) { if (config.identity || process.env.CSC_LINK || process.env.CSC_NAME) buildConfigs.identity = config.identity || process.env.CSC_LINK || process.env.CSC_NAME; const entitlements = config.entitlements; if (entitlements && fs.existsSync(entitlements)) { buildConfigs.entitlements = entitlements; } else if (entitlements) { events.emit('warn', `The provided entitlements file does not exist in the given path => ${entitlements}`); } const entitlementsInherit = config.entitlementsInherit; if (entitlementsInherit && fs.existsSync(entitlementsInherit)) { buildConfigs.entitlementsInherit = entitlementsInherit; } else if (entitlementsInherit) { events.emit('warn', `The provided entitlements inherit file does not exist in the given path => ${entitlementsInherit}`); } const requirements = config.requirements; if (requirements && fs.existsSync(requirements)) { buildConfigs.requirements = requirements; } else if (requirements) { events.emit('warn', `The provided requirements file does not exist in the given path => ${requirements}`); } const provisioningProfile = config.provisioningProfile; if (provisioningProfile && fs.existsSync(provisioningProfile)) { buildConfigs.provisioningProfile = provisioningProfile; } else if (provisioningProfile) { events.emit('warn', `The provided provisioning profile does not exist in the given path => ${provisioningProfile}`); } } __appendWindowsUserSigning (config, buildConfigs) { const certificateFile = config.certificateFile; if (certificateFile && fs.existsSync(certificateFile)) { buildConfigs.certificateFile = certificateFile; if (config.certificatePassword || process.env.CSC_KEY_PASSWORD) buildConfigs.certificatePassword = config.certificatePassword || process.env.CSC_KEY_PASSWORD; } else if (certificateFile) { events.emit('warn', `The provided certificate file does not exist in the given path => ${certificateFile}`); } if (config.certificateSubjectName) buildConfigs.certificateSubjectName = config.certificateSubjectName; if (config.certificateSha1) buildConfigs.certificateSha1 = config.certificateSha1; if (config.signingHashAlgorithms) buildConfigs.signingHashAlgorithms = config.signingHashAlgorithms; const additionalCertificateFile = config.additionalCertificateFile; if (additionalCertificateFile && fs.existsSync(additionalCertificateFile)) { buildConfigs.additionalCertificateFile = additionalCertificateFile; } else if (additionalCertificateFile) { events.emit('warn', `The provided addition certificate file does not exist in the given path => ${additionalCertificateFile}`); } } configureBuildSettings () { const baseConfig = require(path.resolve(__dirname, './build/base.json')); const platformConfig = this.userBuildSettings || this.fetchPlatformDefaults(process.platform); return deepMerge(baseConfig, platformConfig); } injectProjectConfigToBuildSettings () { // const isDevelopment = false; const packageJson = require(path.join(this.api.locations.www, 'package.json')); const userConfig = { APP_ID: packageJson.name, APP_TITLE: packageJson.displayName, APP_INSTALLER_ICON: 'installer.png', APP_BUILD_DIR: this.api.locations.build, APP_BUILD_RES_DIR: this.api.locations.buildRes, APP_WWW_DIR: this.api.locations.www, BUILD_TYPE: this.isDevelopment ? 'development' : 'distribution', ELECTRON_INSTALLED_VERSION: getInstalledElectronVersion() }; // convert to string for string replacement let buildSettingsString = JSON.stringify(this.buildSettings); Object.keys(userConfig).forEach((key) => { const regex = new RegExp(`\\$\\{${key}\\}`, 'g'); const value = userConfig[key].replace(/\\/g, '\\\\'); buildSettingsString = buildSettingsString.replace(regex, value); }); // update build settings with formated data this.buildSettings = JSON.parse(buildSettingsString); return this; } fetchPlatformDefaults (platform) { const platformFile = path.resolve(__dirname, `./build/${platform}.json`); if (!fs.existsSync(platformFile)) { throw Error(`Your platform "${platform}" is not supported as a default target platform for Electron.`); } return require(platformFile); } build () { return require('electron-builder').build(this.buildSettings); } } module.exports.run = (buildOptions, api) => require('./check_reqs') .run() .then(() => (new ElectronBuilder(buildOptions, api)) .configure() .build() ) .catch(error => { throw error; }); ================================================ FILE: lib/check_reqs.js ================================================ /* 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. */ // add methods as we determine what are the requirements module.exports.run = () => Promise.resolve([]); ================================================ FILE: lib/clean.js ================================================ /* 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 check_reqs = require('./check_reqs'); const platformBuildDir = path.join('platforms', 'electron', 'www'); module.exports.run = () => { // TODO: everything calls check_reqs ... why? // Check that requirements are (still) met if (!check_reqs.run()) { console.error('Please make sure you meet the software requirements in order to clean an electron cordova project'); process.exit(2); } try { if (fs.existsSync(platformBuildDir)) { fs.rmSync(platformBuildDir, { recursive: true, force: true }); } } catch (err) { console.log(`could not remove ${platformBuildDir} : ${err.message}`); } }; // just on the off chance something is still calling cleanProject, we will leave this here for a while module.exports.cleanProject = () => { console.log('lib/clean will soon only export a `run` command, please update to not call `cleanProject`.'); return module.exports.run(); }; ================================================ FILE: lib/create.js ================================================ /* 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 rootDir = path.resolve(__dirname, '..'); const events = require('cordova-common').events; const check_reqs = require(path.join(rootDir, 'lib/check_reqs')); // exported method to create a project, returns a promise that resolves with null module.exports.createProject = (platform_dir, package_name, project_name, options) => { /* // create the dest and the standard place for our api to live // platforms/platformName/cordova/Api.js */ events.emit('log', 'Creating Cordova project for cordova-electron:'); events.emit('log', '\tPath: ' + platform_dir); events.emit('log', '\tName: ' + project_name); // Check if project already exists if (fs.existsSync(platform_dir)) { events.emit('error', 'Oops, destination already exists! Delete it and try again'); } // Check that requirements are met and proper targets are installed if (!check_reqs.run()) { // TODO: use events.emit events.emit('error', 'Please make sure you meet the software requirements in order to build a cordova electron project'); } // Make sure that the platform directory is created if missing. fs.mkdirSync(platform_dir, { recursive: true }); // copy templates directory to the platform directory recursively fs.cpSync(path.join(rootDir, 'bin/templates'), path.join(platform_dir), { recursive: true, force: true }); return Promise.resolve(); }; ================================================ FILE: lib/handler.js ================================================ /* 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 path = require('node:path'); const fs = require('node:fs'); const execa = require('execa'); const { events } = require('cordova-common'); const { _orderObject } = require('./PackageJsonParser'); const { deepMerge } = require('./util'); module.exports = { www_dir: (project_dir) => path.join(project_dir, 'www'), package_name: (project_dir) => { // this method should the id from root config.xml => 1) { pkgName = res[1]; } } return pkgName; }, 'js-module': { install: (jsModule, plugin_dir, plugin_id, www_dir) => { // Copy the plugin's files into the www directory. const moduleSource = path.resolve(plugin_dir, jsModule.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))}`; // Read in the file, prepend the cordova.define, and write it back out. let scriptContent = fs.readFileSync(moduleSource, 'utf8').replace(/^\ufeff/, ''); // Window BOM if (moduleSource.match(/.*\.json$/)) { scriptContent = `module.exports = + ${scriptContent}`; } scriptContent = `cordova.define('${moduleName}', function (require, exports, module) { ${scriptContent} });`; const moduleDestination = path.resolve(www_dir, 'plugins', plugin_id, jsModule.src); fs.mkdirSync(path.dirname(moduleDestination), { recursive: true }); fs.writeFileSync(moduleDestination, scriptContent, 'utf8'); }, uninstall: (jsModule, www_dir, plugin_id) => { fs.rmSync(path.join(www_dir, 'plugins', plugin_id), { recursive: true, force: true }); const pluginRelativePath = path.join('plugins', plugin_id, jsModule.src); // common.removeFileAndParents(www_dir, pluginRelativePath); events.emit('verbose', `js-module uninstall called : ${pluginRelativePath}`); } }, 'source-file': { install: (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); events.emit('verbose', 'source-file.install is not supported for electron'); }, uninstall: (obj, plugin_dir, project_dir, plugin_id, options) => { // var dest = path.join(obj.targetDir, path.basename(obj.src)); // common.removeFile(project_dir, dest); events.emit('verbose', 'source-file.uninstall is not supported for electron'); } }, 'header-file': { install: (obj, plugin_dir, project_dir, plugin_id, options) => { events.emit('verbose', 'header-file.install is not supported for electron'); }, uninstall: (obj, plugin_dir, project_dir, plugin_id, options) => { events.emit('verbose', 'header-file.uninstall is not supported for electron'); } }, 'resource-file': { install: (obj, plugin_dir, project_dir, plugin_id, options) => { events.emit('verbose', 'resource-file.install is not supported for electron'); }, uninstall: (obj, plugin_dir, project_dir, plugin_id, options) => { events.emit('verbose', 'resource-file.uninstall is not supported for electron'); } }, framework: { install: (obj, plugin_dir, project_dir, plugin_id, options) => { const electronPluginSrc = path.resolve(plugin_dir, obj.src); if (!fs.existsSync(electronPluginSrc)) { events.emit( 'warn', '[Cordova Electron] The defined "framework" source path does not exist and can not be installed.' ); return; } const wwwDir = path.join(project_dir, 'www'); // First: Install the Cordova Electron plugin dependencies execa('npm', ['install'], { cwd: electronPluginSrc }); // Second: Install the Cordova Electron plugin to the Electron app scope. (npm 8 creates symlink) execa('npm', ['install', electronPluginSrc], { cwd: wwwDir }); const appPackageFile = path.join(wwwDir, 'package.json'); let appPackage = JSON.parse(fs.readFileSync(appPackageFile, 'utf8')); const pluginPackage = JSON.parse(fs.readFileSync(path.join(electronPluginSrc, 'package.json'), 'utf8')); if (!pluginPackage.cordova || !pluginPackage.cordova.serviceName) { return; } const serviceName = pluginPackage.cordova.serviceName; if ( appPackage.cordova && appPackage.cordova.services && appPackage.cordova.services[serviceName] ) { events.emit( 'warn', `[Cordova Electron] The service name "${serviceName}" is already taken by "${appPackage.cordova.services[serviceName]}" and can not be redeclared.` ); return; } const appendingData = { cordova: { services: { [serviceName]: pluginPackage.name } } }; appPackage = deepMerge(appPackage, appendingData); appPackage.cordova.services = _orderObject(appPackage.cordova.services); fs.writeFileSync( appPackageFile, JSON.stringify(appPackage, null, 2), 'utf8' ); }, uninstall: (obj, plugin_dir, project_dir, plugin_id, options) => { const electronPluginPackageFile = path.resolve(plugin_dir, obj.src, 'package.json'); const electronPluginPackage = JSON.parse( fs.readFileSync(electronPluginPackageFile, 'utf8') ); const electronPluginName = electronPluginPackage.name; const wwwDir = path.join(project_dir, 'www'); console.log(electronPluginPackageFile); console.log(electronPluginName); execa('npm', ['uninstall', electronPluginName], { cwd: wwwDir }); const appPackageFile = path.join(wwwDir, 'package.json'); const appPackage = JSON.parse(fs.readFileSync(appPackageFile, 'utf8')); if ( appPackage && appPackage.cordova && appPackage.cordova.services ) { let hasUpdatedPackage = false; Object.keys(appPackage.cordova.services).forEach(serviceName => { if (appPackage.cordova.services[serviceName] === electronPluginName) { delete appPackage.cordova.services[serviceName]; hasUpdatedPackage = true; events.emit( 'verbose', `[Cordova Electron] The service name "${serviceName}" was delinked.` ); } }); if (hasUpdatedPackage) { fs.writeFileSync( appPackageFile, JSON.stringify(appPackage, null, 2), 'utf8' ); } } } }, 'lib-file': { install: (obj, plugin_dir, project_dir, plugin_id, options) => { events.emit('verbose', 'lib-file.install is not supported for electron'); }, uninstall: (obj, plugin_dir, project_dir, plugin_id, options) => { events.emit('verbose', 'lib-file.uninstall is not supported for electron'); } }, asset: { install: (asset, plugin_dir, wwwDest) => { const src = path.join(plugin_dir, asset.src); const dest = path.join(wwwDest, asset.target); const destDir = path.parse(dest).dir; fs.mkdirSync(destDir, { recursive: true }); fs.cpSync(src, dest, { recursive: true }); }, uninstall: (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 }); } } }; ================================================ FILE: lib/parser.js ================================================ /* 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 CordovaError = require('cordova-common').CordovaError; const events = require('cordova-common').events; const FileUpdater = require('cordova-common').FileUpdater; function dirExists (dir) { return fs.existsSync(dir) && fs.statSync(dir).isDirectory(); } class Parser { constructor (project) { if (!dirExists(project) || !dirExists(path.join(project, 'cordova'))) { throw new CordovaError(`The provided path "${project}" is not a valid electron project.`); } this.path = project; } update_from_config () { return Promise.resolve(); } www_dir () { return path.join(this.path, 'www'); } // Replace the www dir with contents of platform_www and app www. update_www (cordovaProject, opts) { const platform_www = path.join(this.path, 'platform_www'); const my_www = this.www_dir(); // add cordova www and platform_www to sourceDirs const sourceDirs = [ path.relative(cordovaProject.root, cordovaProject.locations.www), path.relative(cordovaProject.root, platform_www) ]; // If project contains 'merges' for our platform, use them as another overrides const merges_path = path.join(cordovaProject.root, 'merges', 'electron'); if (fs.existsSync(merges_path)) { events.emit('verbose', 'Found "merges/electron" folder. Copying its contents into the electron project.'); // add merges/electron to sourceDirs sourceDirs.push(path.join('merges', 'electron')); } // targetDir points to electron/www const targetDir = path.relative(cordovaProject.root, my_www); events.emit('verbose', `Merging and updating files from [${sourceDirs.join(', ')}] to ${targetDir}`); FileUpdater.mergeAndUpdateDir(sourceDirs, targetDir, { rootDir: cordovaProject.root, exclude: ['node_modules', 'package.json'] }, logFileOp); } config_xml () { return path.join(this.path, 'config.xml'); } update_project (cfg) { return this.update_from_config() .then( // Copy munged config.xml to platform www dir () => fs.cpSync(path.join(this.www_dir(), '..', 'config.xml'), path.join(this.www_dir(), 'config.xml'), { recursive: true }) ); } } module.exports = function (project) { return new Parser(project); }; /** * Logs all file operations via the verbose event stream, indented. */ function logFileOp (message) { events.emit('verbose', ` ${message}`); } ================================================ FILE: lib/prepare.js ================================================ /* 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 { ConfigParser, xmlHelpers, events, CordovaError } = require('cordova-common'); const ManifestJsonParser = require('./ManifestJsonParser'); const PackageJsonParser = require('./PackageJsonParser'); const SettingJsonParser = require('./SettingJsonParser'); module.exports.prepare = function (cordovaProject, options) { // First cleanup current config and merge project's one into own const defaultConfigPath = path.join(this.locations.platformRootDir, 'cordova', 'defaults.xml'); const ownConfigPath = this.locations.configXml; const sourceCfg = cordovaProject.projectConfig; // If defaults.xml is present, overwrite platform config.xml with it. // Otherwise save whatever is there as defaults so it can be // restored or copy project config into platform if none exists. if (fs.existsSync(defaultConfigPath)) { this.events.emit('verbose', `Generating config.xml from defaults for platform "${this.platform}"`); fs.cpSync(defaultConfigPath, ownConfigPath, { recursive: true }); } else if (fs.existsSync(ownConfigPath)) { this.events.emit('verbose', `Generating defaults.xml from own config.xml for platform "${this.platform}"`); fs.cpSync(ownConfigPath, defaultConfigPath, { recursive: true }); } else { this.events.emit('verbose', `case 3 "${this.platform}"`); fs.cpSync(sourceCfg.path, ownConfigPath, { recursive: true }); } // merge our configs this.config = new ConfigParser(ownConfigPath); xmlHelpers.mergeXml(sourceCfg.doc.getroot(), this.config.doc.getroot(), this.platform, true); this.config.write(); // Update own www dir with project's www assets and plugins' assets and js-files this.parser.update_www(cordovaProject, this.locations); // Update icons updateIcons(cordovaProject, this.locations); // Update splash screens updateSplashScreens(cordovaProject, this.config, this.locations); // Copy or Create manifest.json const srcManifestPath = path.join(cordovaProject.locations.www, 'manifest.json'); if (fs.existsSync(srcManifestPath)) { // just blindly copy it to our output/www // todo: validate it? ensure all properties we expect exist? const manifestPath = path.join(this.locations.www, 'manifest.json'); this.events.emit('verbose', `Copying ${srcManifestPath} => ${manifestPath}`); fs.cpSync(srcManifestPath, manifestPath, { recursive: true }); } else { this.events.emit('verbose', `Creating new manifest file in => ${this.path}`); (new ManifestJsonParser(this.locations.www)) .configure(this.config) .write(); } (new PackageJsonParser(this.locations.www, cordovaProject.root)) .configure(this.config) .enableDevTools(options && options.options && !options.options.release) .write(); const userElectronSettings = cordovaProject.projectConfig.getPlatformPreference('ElectronSettingsFilePath', 'electron'); const userElectronSettingsPath = userElectronSettings && fs.existsSync(path.resolve(cordovaProject.root, userElectronSettings)) ? path.resolve(cordovaProject.root, userElectronSettings) : undefined; // update Electron settings in .json file (new SettingJsonParser(this.locations.www)) .configure(this.config, options.options, userElectronSettingsPath) .write(); // update project according to config.xml changes. return this.parser.update_project(this.config, options); }; /** * Update Electron Splash Screen image. */ function updateSplashScreens (cordovaProject, config, locations) { const splashScreens = cordovaProject.projectConfig.getSplashScreens('electron'); // Skip if there are no splash screens defined in config.xml if (!splashScreens.length) { events.emit('verbose', 'This app does not have splash screens defined.'); return; } const splashScreen = prepareSplashScreens(splashScreens); const resourceMap = createResourceMap(cordovaProject, locations, splashScreen); updatePathToSplashScreen(config, locations, resourceMap); events.emit('verbose', 'Updating splash screens'); copyResources(cordovaProject.root, resourceMap); } /** * Get splashScreen image. Choose only one image, if the user provided multiple. */ function prepareSplashScreens (splashScreens) { let splashScreen; // choose one icon for a target const chooseOne = (defaultSplash, splash) => { events.emit('verbose', `Found extra splash screen image: ${defaultSplash.src} and ignoring in favor of ${splash.src}.`); defaultSplash = splash; return defaultSplash; }; // iterate over remaining icon elements to find the icons for the app and installer for (const image of splashScreens) { image.extension = path.extname(image.src); splashScreen = splashScreen ? chooseOne(splashScreen, image) : image; } return { splashScreen }; } /** * Update path to Splash Screen in the config.xml */ function updatePathToSplashScreen (config, locations, resourceMap) { const elementKeys = Object.keys(resourceMap[0]); const splashScreenPath = resourceMap[0][elementKeys]; const splash = config.doc.find('splash'); const preferences = config.doc.findall('preference'); splash.attrib.src = path.relative(locations.www, splashScreenPath); for (const preference of preferences) { if (preference.attrib.name === 'SplashScreen') { preference.attrib.value = splash.attrib.src; } } config.write(); } /** * Update Electron App and Installer icons. */ function updateIcons (cordovaProject, locations) { const icons = cordovaProject.projectConfig.getIcons('electron'); // Skip if there are no app defined icons in config.xml if (!icons.length) { events.emit('verbose', 'This app does not have icons defined'); return; } const filteredIcons = icons.filter(icon => checkIconsAttributes(icon)); if (!filteredIcons.length) { throw new CordovaError('No icon match the required size. Please ensure that ".png" icon is at least 512x512 and has a src attribute.'); } const chosenIcons = prepareIcons(filteredIcons); const resourceMap = createResourceMap(cordovaProject, locations, chosenIcons); events.emit('verbose', 'Updating icons'); copyResources(cordovaProject.root, resourceMap); } /** * Check if all required attributes are set. */ function checkIconsAttributes (icon) { if (((icon.height && icon.width) >= 512 || (icon.height && icon.width) === undefined) && icon.src) return true; events.emit('info', `The following${icon.target ? ` ${icon.target}` : ''} icon with a size of width=${icon.width} height=${icon.height} does not meet the requirements and will be ignored.`); return false; } /** * Find and select icons for the app and installer. * Also, set high resolution icons, if provided by a user. */ function prepareIcons (icons) { let customIcon; let appIcon; let installerIcon; // choose one icon for a target const chooseOne = (defaultIcon, icon) => { events.emit('verbose', `Found extra icon for target ${icon.target}: ${defaultIcon.src} and ignoring in favor of ${icon.src}.`); defaultIcon = icon; return defaultIcon; }; // find if there is high resolution images that has DPI suffix const highResAndRemainingIcons = findHighResIcons(icons); const highResIcons = highResAndRemainingIcons.highResIcons; const remainingIcons = highResAndRemainingIcons.remainingIcons; // iterate over remaining icon elements to find the icons for the app and installer for (const icon of remainingIcons) { const size = icon.width || icon.height; icon.extension = path.extname(icon.src); switch (icon.target) { case 'app': appIcon = appIcon ? chooseOne(appIcon, icon) : icon; break; case 'installer': installerIcon = installerIcon ? chooseOne(installerIcon, icon) : icon; break; case undefined: if ((size >= 512 || size === undefined) && !Object.keys(highResIcons).length) { customIcon = customIcon ? chooseOne(customIcon, icon) : icon; } break; } } return { customIcon, appIcon, installerIcon, highResIcons }; } /** * Find and high resolution icons and return remaining icons, * unless an icon has a specified target. */ function findHighResIcons (icons) { // find icons that are not in the highResIcons, unless they have a target set const findRemainingIcons = (icons, highResIcons) => icons.filter( (icon) => (icon.target || (!icon.target && !highResIcons.includes(icon))) ? Object.assign(icon) : false ); const highResIcons = icons.filter(icon => { // eslint-disable-line array-callback-return if (icon.src.includes('@')) { const extension = path.extname(icon.src); const suffix = icon.src.split('@').pop().slice(0, -extension.length); return Object.assign(icon, { suffix, extension }); } }); let remainingIcons = findRemainingIcons(icons, highResIcons); // set normal image that has standard resolution const has1x = highResIcons.find(obj => obj.suffix === '1x'); if (!has1x && Object.keys(highResIcons).length) { const highResIcon = highResIcons[Object.keys(highResIcons)[0]]; let baseIcon = remainingIcons.find(obj => obj.src === highResIcon.src.split('@')[0] + highResIcon.extension); if (!baseIcon) { throw new CordovaError('Base icon for high resolution images was not found.'); } const extension = path.extname(baseIcon.src); const suffix = '1x'; baseIcon = Object.assign(baseIcon, { suffix, extension }); highResIcons.push(baseIcon); remainingIcons = findRemainingIcons(icons, highResIcons); } return { highResIcons, remainingIcons }; } /** * Map resources to the appropriate target directory and name. */ function createResourceMap (cordovaProject, locations, resources) { const resourceMap = []; for (const key in resources) { const resource = resources[key]; if (!resource) { continue; } let targetPath; switch (key) { case 'customIcon': // Copy icon for the App targetPath = path.join(locations.www, 'img', `app${resource.extension}`); resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); // Copy icon for the Installer targetPath = path.join(locations.buildRes, `installer${resource.extension}`); resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); break; case 'appIcon': targetPath = path.join(locations.www, 'img', `app${resource.extension}`); resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); break; case 'installerIcon': targetPath = path.join(locations.buildRes, `installer${resource.extension}`); resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); break; case 'highResIcons': for (const key in resource) { const highResIcon = resource[key]; targetPath = path.join(locations.www, 'img', 'icon'); targetPath += highResIcon.suffix === '1x' ? highResIcon.extension : `@${highResIcon.suffix}${highResIcon.extension}`; resourceMap.push(mapResources(cordovaProject.root, highResIcon.src, targetPath)); } break; case 'splashScreen': targetPath = path.join(locations.www, '.cdv', `splashScreen${resource.extension}`); resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); break; } } return resourceMap; } /** * Get a map containing resources of a specified name (or directory) to the target directory. */ function mapResources (rootDir, sourcePath, targetPath) { return fs.existsSync(path.join(rootDir, sourcePath)) ? { [sourcePath]: targetPath } : {}; } /** * Copy resources to the target destination according to the resource map. */ function copyResources (rootDir, resourceMap) { resourceMap.forEach(element => { const elementKeys = Object.keys(element); if (elementKeys.length) { const value = elementKeys.map((e) => element[e])[0]; fs.cpSync(path.join(rootDir, elementKeys[0]), value, { recursive: true }); } }); } ================================================ FILE: lib/run.js ================================================ /* 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 electron = require('electron'); const execa = require('execa'); const path = require('node:path'); module.exports.run = function (args = {}) { const electonArgs = args.argv || []; // Add the path to the main process as the last Electron argument to pass into execa electonArgs.push( path.join(this.locations.www, 'cdv-electron-main.js') ); const child = execa(electron, electonArgs, { windowsHide: false, stdio: 'inherit' }); child.on('close', (code) => { process.exit(code); }); }; ================================================ FILE: lib/util.js ================================================ /* 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. */ let _packageJson = null; module.exports.deepMerge = (mergeTo, mergeWith) => { for (const property in mergeWith) { if ((property === '__proto__' || property === 'constructor' || property === 'prototype')) { continue; } if (Object.prototype.toString.call(mergeWith[property]) === '[object Object]') { mergeTo[property] = module.exports.deepMerge((mergeTo[property] || {}), mergeWith[property]); } else if (Object.prototype.toString.call(mergeWith[property]) === '[object Array]') { mergeTo[property] = [].concat((mergeTo[property] || []), mergeWith[property]); } else { mergeTo[property] = mergeWith[property]; } } return mergeTo; }; /** * Gets the `cordova-electron` package.json file. * The path to the file depends on if called from a unit testing or an actual Cordova project. * * @return {Object} package.json content */ module.exports.getPackageJson = () => { if (_packageJson) return _packageJson; try { // coming from user project _packageJson = require(require.resolve('cordova-electron/package.json')); } catch (e) { // coming from repo test & coho _packageJson = require('../package.json'); } return _packageJson; }; /** * Gets the installed Electron version from the Electron dependency package.json file. * * @return {String} version of installed Electron dependency */ module.exports.getInstalledElectronVersion = () => { return require('electron/package.json').version; }; ================================================ FILE: licence_checker.yml ================================================ # 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 for the release audit workflow. # The `license-config` is required even if there are no custom configs ================================================ FILE: package.json ================================================ { "name": "cordova-electron", "version": "4.0.1-dev", "description": "electron apps as a target for cordova developers", "main": "lib/Api.js", "repository": "github:apache/cordova-electron", "bugs": "https://github.com/apache/cordova-electron/issues", "keywords": [ "apache", "cordova", "cordova:platform", "ecosystem:cordova", "electron" ], "scripts": { "lint": "eslint .", "prepare": "cordova-js build > bin/templates/platform_www/cordova.js", "test": "npm run lint && npm run test:coverage", "test:coverage": "nyc npm run test:unit", "test:unit": "jasmine --config=tests/spec/unit.json" }, "dependencies": { "cordova-common": "^5.0.0", "electron": "^29.0.0", "electron-builder": "^24.12.0", "electron-devtools-installer": "^3.2.0", "execa": "^5.1.1" }, "devDependencies": { "@cordova/eslint-config": "^5.0.0", "cordova-js": "^6.1.0", "jasmine": "^5.1.0", "nyc": "^15.1.0", "rewire": "^7.0.0" }, "author": "Apache Software Foundation", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" }, "nyc": { "all": true, "check-coverage": true, "per-file": true, "lines": 100, "statements": 100, "functions": 100, "branches": 100, "exclude": [ ".npm-scripts/", "bin/templates/build-res/", "bin/templates/platform_www/", "cordova-js-src/", "coverage/", "tests/" ], "reporter": [ "lcov", "text" ] } } ================================================ FILE: tests/spec/coverage.json ================================================ { "spec_dir": "tests/spec", "spec_files": [ "unit/**/*[sS]pec.js" ], "random": false } ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/config.xml ================================================ cordovaTestApp A sample Apache Cordova application that responds to the deviceready event. Apache Cordova Team ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/package.json ================================================ { "name": "org.apache.cordovaTestApp", "displayName": "cordovaTestApp", "version": "1.0.0", "description": "A sample Apache Cordova application that responds to the deviceready event.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ "ecosystem:cordova" ], "author": "Apache Cordova Team", "license": "Apache-2.0", "devDependencies": {}, "cordova": { "plugins": {}, "platforms": [ "electron" ] } } ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/config.xml ================================================ cordovaTestApp A sample Apache Cordova application that responds to the deviceready event. Apache Cordova Team ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/cordova/Api.js ================================================ /* 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. */ try { module.exports = require('cordova-electron'); } catch (error) { module.exports = require('../../../lib/Api'); } ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/cordova/defaults.xml ================================================ ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/cordova/version ================================================ #!/usr/bin/env node /* 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 Api = require('./Api'); console.log(Api.version()); ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/cordova/version.bat ================================================ :: 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. @ECHO OFF SET script_path="%~dp0version" IF EXIST %script_path% ( node %script_path% %* ) ELSE ( ECHO. ECHO ERROR: Could not find 'version' script in 'cordova' folder, aborting...>&2 EXIT /B 1 ) ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/electron.json ================================================ { "prepare_queue": { "installed": [], "uninstalled": [] }, "config_munge": { "files": {} }, "installed_plugins": {}, "dependent_plugins": {}, "modules": [], "plugin_metadata": { } } ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/platform_www/config.xml ================================================ ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/www/config.xml ================================================ cordovaTestApp A sample Apache Cordova application that responds to the deviceready event. Apache Cordova Team ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/www/cordova_plugins.js ================================================ /* 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. */ cordova.define('cordova/plugin_list', function (require, exports, module) { module.exports = []; module.exports.metadata = // TOP OF METADATA {} // BOTTOM OF METADATA }); ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/platforms/electron/www/package.json ================================================ { "main": "cdv-electron-main.js", "name": "org.apache.cordovaTestApp", "displayName": "test-app-with-electron-plugin", "version": "1.0.0", "description": "A sample Apache Cordova application that responds to the deviceready event.", "homepage": "http://cordova.io", "license": "Apache-2.0", "author": { "name": "Apache Cordova Team", "email": "dev@cordova.apache.org" }, "dependencies": {}, "cordova": {} } ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/plugins/cordova-plugin-sample/package.json ================================================ { "name": "cordova-plugin-sample", "version": "1.0.0", "description": "Cordova Sample Plugin", "cordova": { "id": "cordova-plugin-sample", "platforms": [ "electron" ] }, "keywords": [ "cordova", "sample", "ecosystem:cordova", "cordova-electron" ], "author": "Apache Software Foundation", "license": "Apache-2.0", "engines": { "cordovaDependencies": { "1.0.0": { "cordova": ">100", "cordova-electron": ">=3.0.0" } } } } ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/plugins/cordova-plugin-sample/plugin.xml ================================================ Sanple Cordova Sample Plugin Apache 2.0 cordova,sample ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/plugins/cordova-plugin-sample/src/electron/index.js ================================================ /* 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 { system, osInfo } = require('systeminformation'); module.exports = { getSampleInfo: async () => { try { const { model } = await system(); const { platform } = await osInfo(); return { model, platform: platform === 'darwin' ? codename : distro, electronVersion: process.versions.electron }; } catch (e) { console.log(e); } } }; ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/plugins/cordova-plugin-sample/src/electron/package.json ================================================ { "name": "cordova-plugin-sample-electron", "version": "1.0.0", "description": "Cordova Sample Electron Plugin (Native Support)", "main": "index.js", "keywords": [ "cordova", "electron", "sample", "native" ], "author": "Apache Software Foundation", "license": "Apache-2.0", "dependencies": { "systeminformation": "^4.27.9" }, "cordova": { "serviceName": "Sample" } } ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/plugins/cordova-plugin-sample/www/sample.js ================================================ /* * * 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. * */ var argscheck = require('cordova/argscheck'); var channel = require('cordova/channel'); var exec = require('cordova/exec'); channel.createSticky('onCordovaInfoReady'); // Tell cordova channel to wait on the CordovaInfoReady event channel.waitForInitialization('onCordovaInfoReady'); /** * @constructor */ function Sample () { this.model = null; this.platform = null; channel.onCordovaReady.subscribe(() => { me.getInfo( info => { this.model = info.model; this.platform = info.platform; channel.onCordovaInfoReady.fire(); }, error => { console.error('[ERROR] Error initializing cordova-plugin-sample: ', error); } ); }); } /** * Get sample info * * @param {Function} successCallback The function to call when the heading data is available * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) */ Sample.prototype.getInfo = function (successCallback, errorCallback) { argscheck.checkArgs('fF', 'Sample.getInfo', arguments); exec(successCallback, errorCallback, 'Sample', 'getSampleInfo', []); }; module.exports = new Sample(); ================================================ FILE: tests/spec/fixtures/test-app-with-electron-plugin/www/index.html ================================================ Hello World
================================================ FILE: tests/spec/fixtures/test-browser-plugin/plugin.xml ================================================ Test Browser Plugin ================================================ FILE: tests/spec/fixtures/test-browser-plugin/www/plugin.js ================================================ /* * * 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. * */ class TestBrowserPlugin { } module.exports = new TestBrowserPlugin(); ================================================ FILE: tests/spec/fixtures/test-config-1.xml ================================================ HelloWorld A sample Apache Cordova application. Cordova Team Apache 2.0 License ================================================ FILE: tests/spec/fixtures/test-config-2.xml ================================================ HelloWorld A sample Apache Cordova application. Cordova Team Apache 2.0 License ================================================ FILE: tests/spec/fixtures/test-config-custom-scheme.xml ================================================ ================================================ FILE: tests/spec/fixtures/test-config-empty.xml ================================================ ================================================ FILE: tests/spec/fixtures/test-config-no-author-custom-email.xml ================================================ HelloWorld A sample Apache Cordova application. Apache 2.0 License ================================================ FILE: tests/spec/fixtures/test-non-electron-plugin/plugin.xml ================================================ Test Non Electron Plugin ================================================ FILE: tests/spec/fixtures/testapp/config.xml ================================================ HelloWorld A sample Apache Cordova application. Cordova Team Apache 2.0 License ================================================ FILE: tests/spec/fixtures/testapp/cordova/.gitkeep ================================================ ================================================ FILE: tests/spec/fixtures/testplugin/plugin.xml ================================================ Test Electron Plugin ================================================ FILE: tests/spec/fixtures/testplugin/src/electron/sample.json ================================================ { "title" : "sample" } ================================================ FILE: tests/spec/fixtures/testplugin/www/plugin.js ================================================ /* * * 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. * */ class TestPlugin { } module.exports = new TestPlugin(); ================================================ FILE: tests/spec/fixtures/testplugin-empty-jsmodule/plugin.xml ================================================ Test Electron Plugin ================================================ FILE: tests/spec/fixtures/testplugin-empty-jsmodule/www/MyTestPlugin2.js ================================================ /* * * 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. * */ class TestPlugin2 { } module.exports = new TestPlugin2(); ================================================ FILE: tests/spec/unit/lib/Api.spec.js ================================================ /* 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 rewire = require('rewire'); const { events, PluginInfo, ConfigParser, CordovaError } = require('cordova-common'); const rootDir = path.resolve(__dirname, '../../../..'); const fixturesDir = path.join(rootDir, 'tests/spec/fixtures'); const tmpDir = path.join(rootDir, 'temp'); const testProjectDir = path.join(tmpDir, 'testapp'); const create = require(path.join(rootDir, 'lib/create')); const Api = rewire(path.join(rootDir, 'lib/Api')); const apiRequire = Api.__get__('require'); const pluginFixture = path.join(fixturesDir, 'testplugin'); const pluginFixtureEmptyJSModule = path.join(fixturesDir, 'testplugin-empty-jsmodule'); const pluginNotElectronFixture = path.join(fixturesDir, 'test-non-electron-plugin'); const pluginBrowserFixture = path.join(fixturesDir, 'test-browser-plugin'); function dirExists (dir) { return fs.existsSync(dir) && fs.statSync(dir).isDirectory(); } function fileExists (file) { return fs.existsSync(file) && fs.statSync(file).isFile(); } const mockExpectedLocations = { platformRootDir: testProjectDir, root: testProjectDir, www: path.join(testProjectDir, 'www'), res: path.join(testProjectDir, 'res'), platformWww: path.join(testProjectDir, 'platform_www'), configXml: path.join(testProjectDir, 'config.xml'), defaultConfigXml: path.join(testProjectDir, 'cordova/defaults.xml'), build: path.join(testProjectDir, 'build'), buildRes: path.join(testProjectDir, 'build-res'), cache: path.join(testProjectDir, 'cache') }; describe('Api class', () => { let api; let apiEvents; beforeAll(() => { fs.mkdirSync(tmpDir, { recursive: true }); fs.cpSync(path.resolve(fixturesDir, 'testapp'), testProjectDir, { recursive: true }); apiEvents = Api.__get__('selfEvents'); apiEvents.addListener('verbose', (data) => { }); }); afterAll(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); apiEvents.removeAllListeners(); }); beforeEach(() => { api = new Api(null, testProjectDir); }); describe('constructor', () => { it('should have been constructed with initial values.', () => { expect(api).toBeDefined(); /** * In Unit Testing: * The API file path is located in "cordova-electron/bin/templates/cordova". * The expected path is the "cordova-electron/bin/templates" dir. * * In production: * The API file path is actually located in "/platforms/electron/cordova". * The expected path is "/platforms/electron" which is the electron's platform root dir */ expect(api.root).toEqual(testProjectDir); expect(api.locations).toEqual(jasmine.objectContaining(mockExpectedLocations)); }); it('should throw error when the constructor is missing platformRootDir argument.', () => { expect(() => new Api()).toThrowError( CordovaError, 'The path to the platform root directory was undefined or invalid.' ); }); it('should throw error when the constructor contains an invalid platformRootDir path.', () => { const fakePath = path.join(__dirname, '/some-fake-path'); expect(() => new Api(undefined, fakePath)).toThrowError( CordovaError, 'The path to the platform root directory was undefined or invalid.' ); }); }); describe('getPlatformInfo method', () => { it('should return object containing platform information', () => { spyOn(Api, 'version').and.returnValue('1.0.0'); expect(api.getPlatformInfo()).toEqual({ locations: mockExpectedLocations, root: testProjectDir, name: 'electron', version: '1.0.0', projectConfig: undefined }); }); }); describe('prepare method', () => { it('should return object containing platform information', () => { const prepare = jasmine.createSpy('prepare'); Api.__set__('require', () => ({ prepare })); // Mock project configs coming from lib. const appendProjectPath = (dirFile) => path.join(api.root, dirFile); const project = { root: api.root, projectConfig: new ConfigParser(appendProjectPath('config.xml')), locations: { plugins: appendProjectPath('plugins'), www: appendProjectPath('www'), rootConfigXml: appendProjectPath('config.xml') } }; api.prepare(project, {}); expect(prepare).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Object)); Api.__set__('require', apiRequire); }); }); describe('addPlugin method', () => { beforeEach(() => { spyOn(events, 'emit'); fs.rmSync(path.resolve(testProjectDir, 'electron.json'), { recursive: true, force: true }); fs.rmSync(path.resolve(testProjectDir, 'www'), { recursive: true, force: true }); }); afterEach(() => { fs.rmSync(path.resolve(testProjectDir, 'electron.json'), { recursive: true, force: true }); fs.rmSync(path.resolve(testProjectDir, 'www'), { recursive: true, force: true }); apiEvents.removeAllListeners(); }); it('should Promise reject when missing PluginInfo parameter.', () => { return api.addPlugin().then( () => {}, error => { expect(error).toEqual(new Error('Missing plugin info parameter. The first parameter should contain a valid PluginInfo instance.')); } ); }); it('should error out when a PluginInfo parameter is not valid', () => { class FakePluginInfo {} return expect(() => { api.addPlugin(new FakePluginInfo()); }).toThrowError(); }); describe('Use Electron Plugin with Default Install Options', () => { beforeEach(() => { const pluginInfo = new PluginInfo(pluginFixture); return api.addPlugin(pluginInfo); }); it('should add the plugins assets and meta data.', () => { expect(dirExists(path.resolve(testProjectDir, 'www'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'electron.json'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'www/cordova_plugins.js'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'www/js/electron.json'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'www/plugins/org.apache.testplugin/www/plugin.js'))).toBeTruthy(); }); it('should emit that source-file is not supported.', () => { expect(events.emit).toHaveBeenCalledWith( 'verbose', jasmine.stringMatching(/not supported/) ); }); it('should add a second plugins assets', () => { const pluginInfo2 = new PluginInfo(pluginBrowserFixture); return api.addPlugin(pluginInfo2).then(() => { expect(fileExists(path.resolve(testProjectDir, 'www/plugins/org.apache.testbrowserplugin/www/plugin.js'))).toBeTruthy(); }); }); it('should have "clobber", "merge", and "run" set when defined in "js-module".', () => { const { modules } = JSON.parse(fs.readFileSync(path.resolve(testProjectDir, 'electron.json'), 'utf8')); expect(modules[0].clobbers).toBeDefined(); expect(modules[0].merges).toBeDefined(); expect(modules[0].runs).toBeDefined(); }); it('should have module id containing the name attribute value.', () => { const { modules } = JSON.parse(fs.readFileSync(path.resolve(testProjectDir, 'electron.json'), 'utf8')); expect(modules[0].id).toBe('org.apache.testplugin.TestPlugin'); }); }); // usePlatformWww describe('Use Empty JS-Module Plugin', () => { beforeEach(() => { const pluginInfo = new PluginInfo(pluginFixtureEmptyJSModule); return api.addPlugin(pluginInfo); }); it('should not have "clobber", "merge", and "run" set when not defined in "js-module".', () => { const { modules } = JSON.parse(fs.readFileSync(path.resolve(testProjectDir, 'electron.json'), 'utf8')); expect(modules[0].clobbers).not.toBeDefined(); expect(modules[0].merges).not.toBeDefined(); expect(modules[0].runs).not.toBeDefined(); }); it('should use js filename for plugin id if name is missing.', () => { const { modules } = JSON.parse(fs.readFileSync(path.resolve(testProjectDir, 'electron.json'), 'utf8')); expect(modules[0].id).toBe('org.apache.testplugin2.MyTestPlugin2'); }); }); describe('Use Electron Plugin with Custom Install Options', () => { beforeEach(() => { const pluginInfo = new PluginInfo(pluginFixture); return api.addPlugin(pluginInfo, { variables: { PACKAGE_NAME: 'com.foobar.newpackagename' }, usePlatformWww: true }); }); it('should use custom package name.', () => { const { installed_plugins } = JSON.parse(fs.readFileSync(path.resolve(testProjectDir, 'electron.json'), 'utf8')); expect(installed_plugins['org.apache.testplugin'].PACKAGE_NAME).toEqual('com.foobar.newpackagename'); }); it('should use platform www instead of www.', () => { expect(fileExists(path.resolve(testProjectDir, 'www/cordova_plugins.js'))).toBeFalsy(); expect(fileExists(path.resolve(testProjectDir, 'platform_www/cordova_plugins.js'))).toBeTruthy(); }); }); /** * @todo verfiy validity of an "unknown" itemType acgtually being able to be set. */ it('should warn when unknown itemType is added.', () => { const pluginInfo = new PluginInfo(pluginFixture); pluginInfo.getAssets = () => [{ itemType: 'unknown' }]; return api.addPlugin(pluginInfo).then( () => { expect(events.emit).toHaveBeenCalledWith( 'warn', jasmine.stringMatching(/Unrecognized type/) ); } ); }); it('should add browser plugins as well.', () => { const pluginInfo = new PluginInfo(pluginBrowserFixture); return api.addPlugin(pluginInfo).then( () => { expect(dirExists(path.resolve(testProjectDir, 'www'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'electron.json'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'www', 'cordova_plugins.js'))).toBeTruthy(); } ); }); }); describe('removePlugin method', () => { beforeEach(() => { spyOn(events, 'emit'); fs.rmSync(path.resolve(testProjectDir, 'electron.json'), { recursive: true, force: true }); fs.rmSync(path.resolve(testProjectDir, 'www'), { recursive: true, force: true }); }); afterEach(() => { fs.rmSync(path.resolve(testProjectDir, 'electron.json'), { recursive: true, force: true }); fs.rmSync(path.resolve(testProjectDir, 'www'), { recursive: true, force: true }); apiEvents.removeAllListeners(); }); it('should Promise reject when missing PluginInfo parameter.', () => { return api.removePlugin().then( () => {}, error => { expect(error).toEqual(new Error('Missing plugin info parameter. The first parameter should contain a valid PluginInfo instance.')); } ); }); it('should error out when a PluginInfo parameter is not valid.', () => { class FakePluginInfo {} return expect(() => { api.removePlugin(new FakePluginInfo()); }).toThrowError(); }); describe('Use Electron Plugin with Default Install Options', () => { let pluginInfo; beforeEach(() => { pluginInfo = new PluginInfo(pluginFixture); return api.addPlugin(pluginInfo); }); it('should remove the empty plugin data from electron.json.', () => { return api.removePlugin(pluginInfo).then( () => { const { plugin_metadata, modules, installed_plugins } = JSON.parse(fs.readFileSync(path.resolve(testProjectDir, 'electron.json'), 'utf8')); expect(plugin_metadata).toEqual({}); expect(modules).toEqual([]); expect(installed_plugins).toEqual({}); } ); }); it('should remove the added plugin assets, source files, and meta data.', () => { return api.removePlugin(pluginInfo).then(() => { expect(dirExists(path.resolve(testProjectDir, 'www'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'electron.json'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'www/cordova_plugins.js'))).toBeTruthy(); expect(fileExists(path.resolve(testProjectDir, 'www/js/electron.json'))).toBeFalsy(); expect(fileExists(path.resolve(testProjectDir, 'www/plugins/org.apache.testplugin/www/plugin.js'))).toBeFalsy(); const cordovaPluginContent = fs.readFileSync(path.resolve(testProjectDir, 'www/cordova_plugins.js'), 'utf8'); expect(cordovaPluginContent).not.toContain(/org.apache.testplugin/); }); }); }); it('should remove the added plugin assets, from the platform www.', () => { const pluginInfo = new PluginInfo(pluginFixture); return api.addPlugin(pluginInfo, { usePlatformWww: true }) .then(() => api.removePlugin(pluginInfo, { usePlatformWww: true })) .then(() => { expect(fileExists(path.resolve(testProjectDir, 'www/cordova_plugins.js'))).toBeFalsy(); expect(fileExists(path.resolve(testProjectDir, 'platform_www/cordova_plugins.js'))).toBeTruthy(); }); }); /** * @todo verfiy validity of an "unknown" itemType acgtually being able to be set. */ it('should remove the plugin assets and source files.', () => { const pluginInfo = new PluginInfo(pluginFixture); pluginInfo.getAssets = () => [{ itemType: 'unknown' }]; return api.removePlugin(pluginInfo).then(() => { expect(events.emit).toHaveBeenCalledWith( 'warn', jasmine.stringMatching(/unrecognized type/) ); }); }); it('should remove the empty non-electron plugin using platform www as target.', () => { const pluginInfo = new PluginInfo(pluginNotElectronFixture); return api.addPlugin(pluginInfo, { usePlatformWww: true }) .then(() => api.removePlugin(pluginInfo, { usePlatformWww: true })) .then(() => { const cordovaPluginContent = fs.readFileSync(path.resolve(testProjectDir, 'platform_www/cordova_plugins.js'), 'utf8'); expect(cordovaPluginContent).not.toContain(/org.apache.testnonelectronplugin/); }); }); }); describe('build method', () => { it('should execute build', () => { const call = jasmine.createSpy('run'); const mockBuildOptions = { foo: 'bar' }; Api.__set__('require', () => ({ run: { call } })); api.build(mockBuildOptions); expect(call).toHaveBeenCalledWith(api, mockBuildOptions, api); Api.__set__('require', apiRequire); }); }); describe('run method', () => { it('should execute run', () => { const run = jasmine.createSpy('run'); const mockRunOptions = { foo: 'bar' }; Api.__set__('require', () => ({ run })); api.run(mockRunOptions); expect(run).toHaveBeenCalledWith(mockRunOptions); Api.__set__('require', apiRequire); }); }); describe('clean method', () => { it('should execute clean', () => { const run = jasmine.createSpy('clean'); const mockCleanOptions = { foo: 'bar' }; Api.__set__('require', () => ({ run })); api.clean(mockCleanOptions); expect(run).toHaveBeenCalledWith(mockCleanOptions); Api.__set__('require', apiRequire); }); }); describe('requirements method', () => { it('should execute requirements', () => { const run = jasmine.createSpy('requirements'); Api.__set__('require', () => ({ run })); api.requirements(); expect(run).toHaveBeenCalled(); Api.__set__('require', apiRequire); }); }); }); describe('Api prototype methods', () => { describe('updatePlatform method', () => { it('should return a resolved promise.', () => { Api.updatePlatform().then( result => { expect(result).toBeUndefined(); } ); }); }); describe('createPlatform method', () => { let config; beforeEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); config = new ConfigParser(path.join(fixturesDir, 'test-config-empty.xml')); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); it('should create cordova project at the provided destination', () => { spyOn(events, 'emit'); return Api.createPlatform(tmpDir, config) .then((results) => { expect(events.emit).toHaveBeenCalledWith( 'log', jasmine.stringMatching(/Creating Cordova project/) ); expect(results.constructor.name).toBe('Api'); }); }); it('should emit createPlatform not callable when error occurs.', () => { spyOn(create, 'createProject').and.returnValue(new Error('Some Random Error')); expect(() => Api.createPlatform(tmpDir, config)).toThrowError(); }); it('should throw error when config argument is missing.', () => { expect(() => Api.createPlatform(tmpDir)).toThrowError(/An Electron platform can not be created with a missing config argument./); }); }); describe('version method', () => { it('should get version from cordova-electron package.', () => { Api.__set__('getPackageJson', () => { return { version: '1.0.0' }; }); expect(Api.version()).toEqual('1.0.0'); }); }); }); ================================================ FILE: tests/spec/unit/lib/ManifestJsonParser.spec.js ================================================ /* 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 rewire = require('rewire'); const path = require('node:path'); const fs = require('node:fs'); const { ConfigParser } = require('cordova-common'); const rootDir = path.resolve(__dirname, '../../../..'); const fixturesDir = path.join(rootDir, 'tests/spec/fixtures'); const ManifestJsonParser = rewire(path.join(rootDir, 'lib/ManifestJsonParser')); // Create a real config object before mocking out everything. const cfg1 = new ConfigParser(path.join(fixturesDir, 'test-config-1.xml')); const cfg2 = new ConfigParser(path.join(fixturesDir, 'test-config-2.xml')); const cfgEmpty = new ConfigParser(path.join(fixturesDir, 'test-config-empty.xml')); const locations = { buildRes: path.join('mock', 'build-res'), www: path.join('mock', 'www'), configXml: path.join('mock', 'config.xml') }; const defaultInitManifest = { background_color: '#FFF', display: 'standalone', orientation: 'any', start_url: 'index.html' }; describe('ManifestJson class', () => { let manifestJsonParser; beforeEach(() => { manifestJsonParser = new ManifestJsonParser(locations.www); }); it('should have been constructed with initial values.', () => { expect(manifestJsonParser).toBeDefined(); expect(manifestJsonParser.path).toEqual(path.join(locations.www, 'manifest.json')); expect(manifestJsonParser.www).toEqual(locations.www); expect(manifestJsonParser.manifest).toEqual(defaultInitManifest); }); it('should return when config xml is not defined.', () => { manifestJsonParser.configure(); expect(manifestJsonParser.manifest).toEqual(defaultInitManifest); }); it('should set manifest json object values to default, when config xml is empty.', () => { manifestJsonParser.configure(cfgEmpty); expect(manifestJsonParser.manifest).toEqual(jasmine.objectContaining({ start_url: undefined })); }); it('should read and set manifest json object values from the first config xml.', () => { manifestJsonParser.configure(cfg1); expect(manifestJsonParser.manifest).toEqual(jasmine.objectContaining({ orientation: 'portrait', name: 'HelloWorld', short_name: 'HelloWorld', version: 'whatever', description: 'A sample Apache Cordova application.', author: 'Cordova Team', icons: [{ src: 'res/electron/cordova.png', type: 'image/png', sizes: '16x16' }] })); }); it('should read and set manifest json object values from second config xml.', () => { manifestJsonParser.configure(cfg2); expect(manifestJsonParser.manifest).toEqual(jasmine.objectContaining({ orientation: 'landscape', name: 'HelloWorld', short_name: 'Hello', theme_color: '0xff0000ff', version: 'whatever', description: 'A sample Apache Cordova application.', author: 'Cordova Team' })); }); it('should update theme color if start_url exists in path and contains meta theme-color.', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'readFileSync').and.returnValue(''); manifestJsonParser.configureThemeColor(cfg1); expect(manifestJsonParser.manifest.theme_color).toEqual('#33363b'); }); it('should update theme color with StatusBarBackgroundColor if start_url file does not contain meta theme-color.', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'readFileSync').and.returnValue(''); manifestJsonParser.configureThemeColor(cfg2); expect(manifestJsonParser.manifest.theme_color).toEqual('0xff0000ff'); }); it('should update theme color with no value when StatusBarBackgroundColor is missing and start_url file does not contain meta theme-color.', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'readFileSync').and.returnValue(''); manifestJsonParser.configureThemeColor(cfgEmpty); expect(manifestJsonParser.manifest.theme_color).toEqual(undefined); }); it('should write something.', () => { spyOn(fs, 'writeFileSync').and.returnValue(true); manifestJsonParser.write(); expect(fs.writeFileSync).toHaveBeenCalledWith( jasmine.any(String), JSON.stringify(defaultInitManifest, null, 2), 'utf8' ); }); it('should write defaults with empty config.xml.', () => { spyOn(fs, 'writeFileSync').and.returnValue(true); manifestJsonParser.configure(cfgEmpty) .write(); const expectedManifest = { background_color: '#FFF', display: 'standalone', orientation: 'any' }; expect(fs.writeFileSync).toHaveBeenCalledWith( jasmine.any(String), JSON.stringify(expectedManifest, null, 2), 'utf8' ); }); it('should write with user defined values from config.xml.', () => { spyOn(fs, 'writeFileSync').and.returnValue(true); manifestJsonParser.configure(cfg1) .write(); const expectedManifest = { background_color: '#FFF', display: 'standalone', orientation: 'portrait', start_url: 'index.html', name: 'HelloWorld', short_name: 'HelloWorld', version: 'whatever', description: 'A sample Apache Cordova application.', author: 'Cordova Team', icons: [{ src: 'res/electron/cordova.png', type: 'image/png', sizes: '16x16' }] }; expect(fs.writeFileSync).toHaveBeenCalledWith( jasmine.any(String), JSON.stringify(expectedManifest, null, 2), 'utf8' ); }); }); ================================================ FILE: tests/spec/unit/lib/PackageJsonParser.spec.js ================================================ /* 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 path = require('node:path'); const fs = require('node:fs'); const os = require('node:os'); const { ConfigParser, events } = require('cordova-common'); const rootDir = path.resolve(__dirname, '../../../..'); const fixturesDir = path.join(rootDir, 'tests/spec/fixtures'); const PackageJsonParser = require(path.join(rootDir, 'lib/PackageJsonParser')); // Create a real config object before mocking out everything. const cfg = new ConfigParser(path.join(fixturesDir, 'test-config-1.xml')); const cfgEmpty = new ConfigParser(path.join(fixturesDir, 'test-config-empty.xml')); const cfgNoAuthorCustomEmail = new ConfigParser(path.join(fixturesDir, 'test-config-no-author-custom-email.xml')); const defaultMockProjectPackageJson = { name: 'io.cordova.electronTest', displayName: 'electronTest', version: '1.0.0', description: 'A Sample Apache Cordova Electron Application.', author: 'Apache Cordova Team', license: 'Apache-2.0', dependencies: { 'cordova-electron': '^1.0.0', 'cordova-plugin-camera': '^1.0.0' }, devDependencies: {}, cordova: { plugins: {}, platforms: ['electron'] } }; const defaultInitPackageObj = { main: 'cdv-electron-main.js' }; describe('PackageJsonParser class', () => { let packageJsonParser; let locations; let tmpDir; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cordovaElectronTest-')); locations = { buildRes: path.join(tmpDir, 'build-res'), www: path.join(tmpDir, 'www'), configXml: path.join(tmpDir, 'config.xml') }; packageJsonParser = new PackageJsonParser(locations.www, '/root/project'); spyOn(events, 'emit'); }); afterAll(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); it('should have been constructed with initial values.', () => { expect(packageJsonParser).toBeDefined(); expect(packageJsonParser.path).toEqual(path.join(locations.www, 'package.json')); expect(packageJsonParser.www).toEqual(locations.www); expect(packageJsonParser.package).toEqual(defaultInitPackageObj); }); it('should not modify the package object when config is not provided.', () => { packageJsonParser.configure(); // the package object should be the same as it was initialized expect(packageJsonParser.package).toEqual(defaultInitPackageObj); }); it('should not modify the package object when config is not provided.', () => { packageJsonParser.configure(); // the package object should be the same as it was initialized expect(packageJsonParser.package).toEqual(defaultInitPackageObj); }); it('should not add dev tools extension when enable argument = false.', () => { packageJsonParser.enableDevTools(false); // the package object should be the same as it was initialized expect(packageJsonParser.package.dependencies).not.toBeDefined(); }); it('should remove dev tools extension when enable argument = false.', () => { packageJsonParser.package.dependencies = packageJsonParser.package.dependencies || { 'electron-devtools-installer': '1.0.0' // test }; // Ensure mock was set, this is acting as if it was set before. expect(packageJsonParser.package.dependencies['electron-devtools-installer']).toBeDefined(); // This should remove the mock packageJsonParser.enableDevTools(false); // the dependency should have been removed. expect(packageJsonParser.package.dependencies['electron-devtools-installer']).not.toBeDefined(); }); it('should not add dev tools extension when enable argument = undefined.', () => { packageJsonParser.enableDevTools(); // the package object should be the same as it was initialized expect(packageJsonParser.package.dependencies).not.toBeDefined(); }); it('should add dev tools extension when enable argument = true.', () => { packageJsonParser.enableDevTools(true); // the package object should be the same as it was initialized expect(packageJsonParser.package.dependencies).toBeDefined(); expect(packageJsonParser.package.dependencies['electron-devtools-installer']).toBeDefined(); }); it('should not create dependencies object if it exists an enable argument = true.', () => { packageJsonParser.package.dependencies = {}; // mocking that the object already exists packageJsonParser.enableDevTools(true); // the package object should be the same as it was initialized expect(packageJsonParser.package.dependencies).toBeDefined(); expect(packageJsonParser.package.dependencies['electron-devtools-installer']).toBeDefined(); }); it('should update the package object with default values, when config.xml is empty.', () => { packageJsonParser.configure(cfgEmpty, defaultMockProjectPackageJson); // Expected Mock Package Object const packageJsonObj = Object.assign({}, defaultInitPackageObj, { name: 'io.cordova.hellocordova', displayName: 'HelloCordova', version: '1.0.0', description: 'A sample Apache Cordova application that responds to the deviceready event.', homepage: 'https://cordova.io', license: 'Apache-2.0', author: 'Apache Cordova Team' }); expect(packageJsonParser.package).toEqual(packageJsonObj); }); it('should update package object with values from config.xml.', () => { packageJsonParser.configure(cfg, defaultMockProjectPackageJson); // Expected Mock Package Object const packageJsonObj = Object.assign({}, defaultInitPackageObj, { name: 'whatever', displayName: 'HelloWorld', version: '1.1.1', description: 'A sample Apache Cordova application.', homepage: 'http://cordova.io', license: 'Apache 2.0 License', author: { name: 'Cordova Team', email: 'dev@cordova.com' } }); expect(packageJsonParser.package).toEqual(packageJsonObj); }); it('should set default author when missing but author email is defined.', () => { packageJsonParser.configure(cfgNoAuthorCustomEmail, defaultMockProjectPackageJson); expect(packageJsonParser.package.author).toEqual({ name: 'Apache Cordova Team', email: 'dev@cordova.com' }); }); it('should write something.', () => { spyOn(fs, 'writeFileSync').and.returnValue(true); packageJsonParser.write(); expect(fs.writeFileSync).toHaveBeenCalledWith( jasmine.any(String), JSON.stringify(defaultInitPackageObj, null, 2), 'utf8' ); }); it('should write package object out with user custom defined values.', () => { spyOn(fs, 'writeFileSync').and.returnValue(true); packageJsonParser.configure(cfg, defaultMockProjectPackageJson) .write(); expect(fs.writeFileSync).toHaveBeenCalledWith( jasmine.any(String), jasmine.stringMatching(/whatever/), 'utf8' ); }); }); describe('PackageJsonParser (2) class', () => { it('should have constructed and already detected directories and files', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cordovaElectronTest-')); const locations = { buildRes: path.join(tmpDir, 'build-res'), www: path.join(tmpDir, 'www'), configXml: path.join(tmpDir, 'config.xml') }; const wwwDir = locations.www; const pkgFile = path.join(wwwDir, 'package.json'); fs.mkdirSync(wwwDir, { recursive: true }); fs.writeFileSync(pkgFile, '{}', 'utf8'); spyOn(events, 'emit'); spyOn(fs, 'readFileSync'); // files & directories should already exisy expect(fs.existsSync(wwwDir)).toBeTruthy(); expect(fs.existsSync(pkgFile)).toBeTruthy(); /* eslint-disable-next-line */ const packageJsonParser = new PackageJsonParser(wwwDir, '/root/project'); expect(fs.readFileSync).toHaveBeenCalled(); // There should be no change in truthy values. expect(fs.existsSync(wwwDir)).toBeTruthy(); expect(fs.existsSync(pkgFile)).toBeTruthy(); expect(packageJsonParser.package).toBeDefined(); fs.rmSync(tmpDir, { recursive: true, force: true }); }); }); ================================================ FILE: tests/spec/unit/lib/SettingJsonParser.spec.js ================================================ /* 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 rewire = require('rewire'); const path = require('node:path'); const ConfigParser = require('cordova-common').ConfigParser; const rootDir = path.resolve(__dirname, '../../../..'); const fixturesDir = path.join(rootDir, 'tests/spec/fixtures'); // Create a real config object before mocking out everything. const cfg = new ConfigParser(path.join(fixturesDir, 'test-config-1.xml')); const cfgEmpty = new ConfigParser(path.join(fixturesDir, 'test-config-empty.xml')); const cfgCustomSchemeHostname = new ConfigParser(path.join(fixturesDir, 'test-config-custom-scheme.xml')); describe('Testing SettingJsonParser.js:', () => { let SettingJsonParser; let locations; beforeEach(() => { SettingJsonParser = rewire(path.join(rootDir, 'lib/SettingJsonParser')); locations = { buildRes: path.join('mock', 'build-res'), www: path.join('mock', 'www'), configXml: path.join('mock', 'config.xml') }; }); describe('SettingJson class', () => { let settingJsonParser; let requireSpy; let options; beforeEach(() => { settingJsonParser = SettingJsonParser.__get__('SettingJsonParser'); requireSpy = jasmine.createSpy('require').and.returnValue({}); settingJsonParser.__set__({ require: requireSpy }); }); it('should should be defined.', () => { expect(settingJsonParser).toBeDefined(); }); it('should be called and package equal to false, if settings json does not exist.', () => { requireSpy = jasmine.createSpy('require').and.returnValue(false); settingJsonParser.__set__({ require: requireSpy }); settingJsonParser = new SettingJsonParser(locations.www); expect(requireSpy).toHaveBeenCalled(); expect(settingJsonParser.package).toEqual(false); }); it('should be called and package equal to true, if settings json does exist.', () => { settingJsonParser = new SettingJsonParser(locations.www); expect(requireSpy).toHaveBeenCalled(); expect(settingJsonParser.package).toEqual({}); }); it('should use default when users settings files does not exist and config.xml is empty, but set devTools value from options', () => { options = { options: { release: true, argv: [] } }; SettingJsonParser.__set__('require', (file) => { // return defaults if (file.includes('cdv-electron-settings.json')) { return { browserWindow: { webPreferences: { devTools: true, nodeIntegration: true } } }; } return require(file); }); settingJsonParser = new SettingJsonParser(locations.www).configure(cfgEmpty, options.options, false); expect(settingJsonParser.package.browserWindow.webPreferences.devTools).toBe(false); expect(settingJsonParser.package.browserWindow.webPreferences.nodeIntegration).toBe(true); }); it('should set scheme and hostname to default when not defined.', () => { options = { options: { release: true, argv: [] } }; SettingJsonParser.__set__('require', (file) => { // return defaults if (file.includes('cdv-electron-settings.json')) { return { browserWindow: { webPreferences: { } } }; } return require(file); }); settingJsonParser = new SettingJsonParser(locations.www).configure(cfgEmpty, options.options, false); expect(settingJsonParser.package.scheme).toBe('file'); expect(settingJsonParser.package.hostname).toBe('localhost'); }); it('should set custom scheme and hostname from config.xml.', () => { options = { options: { release: true, argv: [] } }; SettingJsonParser.__set__('require', (file) => { // return defaults if (file.includes('cdv-electron-settings.json')) { return { browserWindow: { webPreferences: { } } }; } return require(file); }); settingJsonParser = new SettingJsonParser(locations.www).configure(cfgCustomSchemeHostname, options.options, false); expect(settingJsonParser.package.scheme).toBe('app'); expect(settingJsonParser.package.hostname).toBe('cordova'); }); it('should use default when users settings files does not exist.', () => { options = {}; SettingJsonParser.__set__('require', (file) => { // return defaults if (file.includes('cdv-electron-settings.json')) { return { browserWindow: { webPreferences: { devTools: true, nodeIntegration: true } } }; } return require(file); }); settingJsonParser = new SettingJsonParser(locations.www).configure(cfg, options.options, false); expect(settingJsonParser.package.browserWindow.webPreferences.devTools).toBe(true); expect(settingJsonParser.package.browserWindow.webPreferences.nodeIntegration).toBe(true); }); it('should load users override settings and merge on default, but set devTools value from options.', () => { options = { options: { debug: true, argv: [] } }; SettingJsonParser.__set__('require', (file) => { if (file === 'LOAD_MY_FAKE_DATA') { return { browserWindow: { webPreferences: { devTools: false, nodeIntegration: false } } }; } // return defaults if (file.includes('cdv-electron-settings.json')) { return { browserWindow: { webPreferences: { devTools: true, nodeIntegration: true } } }; } return require(file); }); settingJsonParser = new SettingJsonParser(locations.www).configure(cfg, options.options, 'LOAD_MY_FAKE_DATA'); expect(settingJsonParser.package.browserWindow.webPreferences.devTools).toBe(true); expect(settingJsonParser.package.browserWindow.webPreferences.nodeIntegration).toBe(false); }); it('should write provided data.', () => { const writeFileSyncSpy = jasmine.createSpy('writeFileSync'); settingJsonParser.__set__('fs', { writeFileSync: writeFileSyncSpy }); options = { options: { debug: true, argv: [] } }; SettingJsonParser.__set__('require', (file) => { if (file === 'LOAD_MY_FAKE_DATA') return {}; // return defaults if (file.includes('cdv-electron-settings.json')) { return { browserWindow: { webPreferences: { devTools: true, nodeIntegration: true } } }; } return require(file); }); settingJsonParser = new SettingJsonParser(locations.www).configure(cfg, options.options, 'LOAD_MY_FAKE_DATA').write(); expect(writeFileSyncSpy).toHaveBeenCalled(); // get settings json file content and remove white spaces let settingsFile = writeFileSyncSpy.calls.argsFor(0)[1]; settingsFile = settingsFile.replace(/\s+/g, ''); expect(settingsFile).toEqual('{"browserWindow":{"webPreferences":{"devTools":true,"nodeIntegration":true}},"browserWindowInstance":{"loadURL":{"url":"index.html"}},"scheme":"file","hostname":"localhost"}'); const settingsFormat = writeFileSyncSpy.calls.argsFor(0)[2]; expect(settingsFormat).toEqual('utf8'); }); }); }); ================================================ FILE: tests/spec/unit/lib/build.spec.js ================================================ /* eslint-disable no-template-curly-in-string */ /* 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 rewire = require('rewire'); const path = require('node:path'); const fs = require('node:fs'); const rootDir = path.resolve(__dirname, '../../../..'); const fixturesDir = path.join(rootDir, 'tests/spec/fixtures'); const tmpDir = path.join(rootDir, 'temp'); const testProjectDir = path.join(tmpDir, 'testapp'); const Api = rewire(path.join(rootDir, 'lib/Api')); const check_reqs = require(path.join(rootDir, 'lib/check_reqs')); describe('Testing build.js:', () => { let build; let api; beforeAll(() => { fs.mkdirSync(tmpDir, { recursive: true }); fs.cpSync(path.resolve(fixturesDir, 'testapp'), path.resolve(tmpDir, 'testapp'), { recursive: true }); api = new Api(null, testProjectDir); }); afterAll(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); beforeEach(() => { build = rewire(path.join(rootDir, 'lib/build')); }); describe('Build class', () => { let ElectronBuilder; let electronBuilder; let requireSpy; let existsSyncSpy; let emitSpy; const emptyObj = {}; beforeEach(() => { ElectronBuilder = build.__get__('ElectronBuilder'); build.__set__({ require: requireSpy }); spyOn(process, 'env'); emitSpy = jasmine.createSpy('emit'); build.__set__('events', { emit: emitSpy }); }); it('should should be defined.', () => { expect(ElectronBuilder).toBeDefined(); }); it('should set isDevelopment to undefined and buildConfig to false, when buildOptions is empty.', () => { electronBuilder = new ElectronBuilder(emptyObj, emptyObj); expect(electronBuilder.api).toEqual(emptyObj); expect(electronBuilder.isDevelopment).toEqual(undefined); expect(electronBuilder.buildConfig).toEqual(false); }); it('should set isDevelopment to true and buildConfig to false, when buildOptions is not empty.', () => { // mock buildOptions Objecet const buildOptions = { debug: true, argv: [] }; electronBuilder = new ElectronBuilder(buildOptions, emptyObj); expect(electronBuilder.api).toEqual(emptyObj); expect(electronBuilder.isDevelopment).toEqual(true); expect(electronBuilder.buildConfig).toEqual(false); }); it('should set isDevelopment to true and buildConfig to false, when buildOptions.buildCofing is defined, but does not exist.', () => { // mock buildOptions Objecet const buildOptions = { debug: true, buildConfig: 'build.xml', argv: [] }; // create spy existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, emptyObj); expect(existsSyncSpy).toHaveBeenCalled(); expect(electronBuilder.api).toEqual(emptyObj); expect(electronBuilder.isDevelopment).toEqual(true); expect(electronBuilder.buildConfig).toEqual(false); }); it('should set isDevelopment to true and buildConfig to true, when buildOptions.buildCofing is defined and does exist.', () => { // mock buildOptions Objecet const buildOptions = { debug: true, buildConfig: {}, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(true); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); electronBuilder = new ElectronBuilder(buildOptions, emptyObj); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); expect(electronBuilder.api).toEqual(emptyObj); expect(electronBuilder.isDevelopment).toEqual(true); expect(electronBuilder.buildConfig).toEqual(true); }); it('should set isDevelopment is true and buildConfig to actual config, when buildOptions.buildCofing is actual cofing and does exist.', () => { // mock BuildConfig and buildOptions Object const buildConfig = { electron: 'electron', author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: true, buildConfig, argv: [] }; // create spies const getInstalledElectronVersionSpy = jasmine.createSpy('getInstalledElectronVersion').and.returnValue('1.33.7'); existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy, getInstalledElectronVersion: getInstalledElectronVersionSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configure(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); expect(getInstalledElectronVersionSpy).toHaveBeenCalled(); expect(electronBuilder.buildSettings).toEqual(buildConfig); }); it('should set isDevelopment to false and buildConfig to actual config, when buildOptions.buildCofing is actual cofing and does exist.', () => { // mock BuildConfig and buildOptions Object const buildConfig = { electron: 'electron', author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies const getInstalledElectronVersionSpy = jasmine.createSpy('getInstalledElectronVersion').and.returnValue('1.33.7'); existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy, getInstalledElectronVersion: getInstalledElectronVersionSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configure(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); expect(getInstalledElectronVersionSpy).toHaveBeenCalled(); expect(electronBuilder.buildSettings).toEqual(buildConfig); }); it('should set isDevelopment to false and buildConfig to actual config, when buildOptions.buildCofing file does not exist.', () => { // mock BuildConfig and buildOptions Object const buildConfig = { electron: 'electron', author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); expect(() => { electronBuilder = new ElectronBuilder(buildOptions, api).configure(); expect(existsSyncSpy).toHaveBeenCalled(); expect(electronBuilder.buildSettings).toEqual(buildConfig); }).toThrowError(/not supported as a default target platform/); }); it('should set configureUserBuildSettings (release mode) for all 3 platforms and one invalid one.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['package', 'package2'], arch: 'arch', signing: { debug: 'debug', release: 'release', store: 'store' } }, win: { package: ['package', 'package2'], arch: 'arch', signing: { debug: 'debug', release: 'release' } }, linux: { package: ['package', 'package2'], arch: 'arch', category: 'Game' }, darwin: {} }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}' }; const expectedLinux = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], icon: '${APP_INSTALLER_ICON}', category: 'Game' }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); expect(electronBuilder.userBuildSettings.config.linux).toEqual(expectedLinux); expect(electronBuilder.userBuildSettings.config.win).toEqual(undefined); }); it('should continue to use defaults with supplied valid configs.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { signing: { provisioningProfile: 'release' } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig: 'LOAD_MY_FAKE_DATA', argv: [] }; // create spies build.__set__('fs', { existsSync: jasmine.createSpy('existsSync').and.returnValue(true) }); build.__set__('require', (file) => { if (file === 'LOAD_MY_FAKE_DATA') return buildConfig; return require(file); }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); const expectedMac = [ { target: 'dmg', arch: ['x64'] }, { target: 'zip', arch: ['x64'] } ]; expect(electronBuilder.userBuildSettings.config.mac.target).toEqual(expectedMac); }); it('should use all default options when no options supplied.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig: 'LOAD_MY_FAKE_DATA', argv: [] }; // create spies build.__set__('fs', { existsSync: jasmine.createSpy('existsSync').and.returnValue(true) }); build.__set__('require', (file) => { if (file === 'LOAD_MY_FAKE_DATA') return buildConfig; return require(file); }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); const expectedMac = [ { target: 'dmg', arch: ['x64'] }, { target: 'zip', arch: ['x64'] } ]; expect(electronBuilder.userBuildSettings.config.mac.target).toEqual(expectedMac); }); it('should set configureUserBuildSettings (debug mode) for all 3 platforms and one invalid one.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['package', 'package2'], arch: 'arch', signing: { debug: 'debug', release: 'release', store: 'store' } }, win: { package: ['package', 'package2'], arch: 'arch', signing: { debug: 'debug', release: 'release' } }, linux: { package: ['package', 'package2'], arch: 'arch' }, darwin: {} }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: true, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}' }; const expectedLinux = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], icon: '${APP_INSTALLER_ICON}' }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); expect(electronBuilder.userBuildSettings.config.linux).toEqual(expectedLinux); expect(electronBuilder.userBuildSettings.config.win).toEqual(undefined); }); it('should set configureUserBuildSettings for all 3 platforms without package.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { arch: ['arch1', 'arch2'], signing: { debug: 'debug', release: 'release', store: 'store' } }, windows: { arch: ['arch1', 'arch2'], signing: { debug: 'debug', release: 'release' } }, linux: { arch: ['arch1', 'arch2'] } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig: 'LOAD_MY_FAKE_DATA', argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); // requireSpy = jasmine.createSpy('require').and.returnValue(); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__('require', (file) => { if (file === 'LOAD_MY_FAKE_DATA') return buildConfig; return require(file); }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); const expected = { linux: [], mac: [], win: [], config: { linux: { icon: '${APP_INSTALLER_ICON}', target: [ { target: 'tar.gz', arch: ['arch1', 'arch2'] } ] }, mac: { type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}', target: [ { target: 'dmg', arch: ['arch1', 'arch2'] }, { target: 'zip', arch: ['arch1', 'arch2'] } ] }, win: { icon: '${APP_INSTALLER_ICON}', target: [ { target: 'nsis', arch: ['arch1', 'arch2'] } ] } } }; expect(electronBuilder.userBuildSettings).toEqual(expected); }); it('should not set this.userBuildSettings.', () => { const buildConfig = { author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig: 'LOAD_MY_FAKE_DATA', argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); // requireSpy = jasmine.createSpy('require').and.returnValue(); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__('require', (file) => { if (file === 'LOAD_MY_FAKE_DATA') return buildConfig; return require(file); }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(electronBuilder.userBuildSettings).toBe(undefined); }); it('should set configureUserBuildSettings for all 3 platforms without arch.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: 'store' } }, win: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release' } }, linux: { package: ['package', 'package2'] } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'package', arch: ['x64'] }, { target: 'package2', arch: ['x64'] } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}' }; const expectedLinux = { target: [ { target: 'package', arch: ['x64'] }, { target: 'package2', arch: ['x64'] } ], icon: '${APP_INSTALLER_ICON}' }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); expect(electronBuilder.userBuildSettings.config.linux).toEqual(expectedLinux); expect(electronBuilder.userBuildSettings.config.win).toEqual(undefined); }); it('should set configureUserBuildSettings for all 3 platforms without signing.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['package', 'package2'], arch: 'arch' }, win: { package: ['package', 'package2'], arch: 'arch' }, linux: { package: ['package', 'package2'], arch: 'arch' } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: true, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}' }; const expectedLinux = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], icon: '${APP_INSTALLER_ICON}' }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); expect(electronBuilder.userBuildSettings.config.linux).toEqual(expectedLinux); expect(electronBuilder.userBuildSettings.config.win).toEqual(undefined); }); it('should set configureUserBuildSettings for mac when platform configs is empty.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: {} }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); }); it('should throw new Error mac with incorrect platform build properties.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { pack: ['package', 'package2'], architecture: 'arch', sign: 'signing' } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); electronBuilder = new ElectronBuilder(buildOptions, api); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); expect(() => { electronBuilder.configureUserBuildSettings(); }).toThrow( new Error('The platform "mac" contains an invalid property. Valid properties are: package, arch, signing') ); }); it('should set configureUserBuildSettings for when using windows instead of win.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { windows: { package: ['mas', 'package2'], arch: 'arch', signing: 'signing' } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedWin = { target: [ { target: 'mas', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], icon: '${APP_INSTALLER_ICON}' }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(undefined); expect(electronBuilder.userBuildSettings.config.linux).toEqual(undefined); expect(electronBuilder.userBuildSettings.config.win).toEqual(expectedWin); }); it('should append package top-level key options if the object is empty.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['pkg', { dmg: { } }], arch: 'arch', signing: 'signing' } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'pkg', arch: 'arch' }, { target: 'dmg', arch: 'arch' } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}' }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); expect(electronBuilder.userBuildSettings.config.dmg).toEqual({ }); expect(electronBuilder.userBuildSettings.config.linux).toEqual(undefined); expect(electronBuilder.userBuildSettings.config.windows).toEqual(undefined); }); it('should append package top-level key options.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['pkg', { dmg: { format: 'UDZO' } }], arch: 'arch', signing: 'signing' } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'pkg', arch: 'arch' }, { target: 'dmg', arch: 'arch' } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}' }; const expectedDmgOptions = { format: 'UDZO' }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); expect(electronBuilder.userBuildSettings.config.dmg).toEqual(expectedDmgOptions); expect(electronBuilder.userBuildSettings.config.linux).toEqual(undefined); expect(electronBuilder.userBuildSettings.config.windows).toEqual(undefined); }); it('should append package top-level key nested options.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['pkg', { dmg: { format: { UDZO: '' } } }], arch: 'arch', signing: 'signing' } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'pkg', arch: 'arch' }, { target: 'dmg', arch: 'arch' } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}' }; const expectedDmgOptions = { format: { UDZO: '' } }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); expect(electronBuilder.userBuildSettings.config.dmg).toEqual(expectedDmgOptions); expect(electronBuilder.userBuildSettings.config.linux).toEqual(undefined); expect(electronBuilder.userBuildSettings.config.windows).toEqual(undefined); }); it('should set overridable per platform options.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['package', 'package2'], arch: 'arch', appId: 'com.test.app', artifactName: '${productName}-${version}.${ext}', compression: 'normal', files: [ 'test/files/file1', 'test/files/file2' ], extraResources: [ 'test/resources/file1', 'test/resources/file2' ], extraFiles: [ 'test/extra/files/file1', 'test/extra/files/file2' ], asar: false, fileAssociations: [ { ext: 'png', name: 'PNG' } ], forceCodeSigning: false, electronUpdaterCompatibility: '>=2.15', publish: ['github', 'bintray'], detectUpdateChannel: false, generateUpdatesFilesForAllChannels: false } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: true, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], type: '${BUILD_TYPE}', icon: '${APP_INSTALLER_ICON}', appId: 'com.test.app', artifactName: '${productName}-${version}.${ext}', compression: 'normal', files: [ 'test/files/file1', 'test/files/file2' ], extraResources: [ 'test/resources/file1', 'test/resources/file2' ], extraFiles: [ 'test/extra/files/file1', 'test/extra/files/file2' ], asar: false, fileAssociations: [ { ext: 'png', name: 'PNG' } ], forceCodeSigning: false, electronUpdaterCompatibility: '>=2.15', publish: ['github', 'bintray'], detectUpdateChannel: false, generateUpdatesFilesForAllChannels: false }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); }); it('should set top-level macOS specific.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['package', 'package2'], arch: 'arch', category: 'public.app-category.developer-tools', icon: 'build/icon.icns', bundleVersion: 'CFBundleVersion', bundleShortVersion: 'CFBundleShortVersionString', darkModeSupport: true, helperBundleId: '${appBundleIdentifier}.helper', extendInfo: 'Info.plist', binaries: [ 'test/binaries/file1', 'test/binaries/file2' ], minimumSystemVersion: '10.0.10', electronLanguages: [ 'en', 'jp', 'ru' ], extraDistFiles: [ 'test/extraDist/file1', 'test/extraDist/file2' ] } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: true, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedMac = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], type: '${BUILD_TYPE}', category: 'public.app-category.developer-tools', icon: 'build/icon.icns', bundleVersion: 'CFBundleVersion', bundleShortVersion: 'CFBundleShortVersionString', darkModeSupport: true, helperBundleId: '${appBundleIdentifier}.helper', extendInfo: 'Info.plist', binaries: [ 'test/binaries/file1', 'test/binaries/file2' ], minimumSystemVersion: '10.0.10', electronLanguages: [ 'en', 'jp', 'ru' ], extraDistFiles: [ 'test/extraDist/file1', 'test/extraDist/file2' ] }; expect(electronBuilder.userBuildSettings.config.mac).toEqual(expectedMac); }); it('should set top-level Windows specific.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { windows: { package: ['package', 'package2'], arch: 'arch', icon: 'build/icon.icns', legalTrademarks: 'trademarks and registered trademarks', rfc3161TimeStampServer: 'http://timestamp.comodoca.com/rfc3161', timeStampServer: 'http://timestamp.verisign.com/scripts/timstamp.dll', publisherName: 'publisher name', verifyUpdateCodeSignature: true, requestedExecutionLevel: 'asInvoker', signAndEditExecutable: true, signDlls: false } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: true, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedWindows = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], icon: 'build/icon.icns', legalTrademarks: 'trademarks and registered trademarks', rfc3161TimeStampServer: 'http://timestamp.comodoca.com/rfc3161', timeStampServer: 'http://timestamp.verisign.com/scripts/timstamp.dll', publisherName: 'publisher name', verifyUpdateCodeSignature: true, requestedExecutionLevel: 'asInvoker', signAndEditExecutable: true, signDlls: false }; expect(electronBuilder.userBuildSettings.config.win).toEqual(expectedWindows); }); it('should set top-level Linux specific.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { linux: { package: ['package', 'package2'], arch: 'arch', maintainer: 'maintainer', vendor: 'vendor', executableName: 'productName', icon: 'path to icon set directory or one png file', synopsis: 'short description', description: 'description', category: 'Utility', mimeTypes: [ 'type1', 'type2' ], desktop: 'desktop file entries' } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: true, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(buildConfig); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); const __validateUserPlatformBuildSettingsSpy = jasmine.createSpy('__validateUserPlatformBuildSettings').and.returnValue(true); build.__set__({ __validateUserPlatformBuildSettings: __validateUserPlatformBuildSettingsSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).configureUserBuildSettings(); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); const expectedLinux = { target: [ { target: 'package', arch: 'arch' }, { target: 'package2', arch: 'arch' } ], maintainer: 'maintainer', vendor: 'vendor', executableName: 'productName', icon: 'path to icon set directory or one png file', synopsis: 'short description', description: 'description', category: 'Utility', mimeTypes: [ 'type1', 'type2' ], desktop: 'desktop file entries' }; expect(electronBuilder.userBuildSettings.config.linux).toEqual(expectedLinux); }); it('should fetchPlatformDefaults true.', () => { // mock buildOptions Objecet and platformFile path const buildOptions = { debug: true, buildConfig: 'build.xml', argv: [] }; const platformFile = path.join(__dirname, 'build', 'platform.json'); // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); requireSpy = jasmine.createSpy('require').and.returnValue(platformFile); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__({ require: requireSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).fetchPlatformDefaults('electron'); expect(existsSyncSpy).toHaveBeenCalled(); expect(requireSpy).toHaveBeenCalled(); expect(electronBuilder).toEqual(platformFile); }); it('should fetchPlatformDefaults false.', () => { // mock buildOptions Object const buildOptions = { debug: true, buildConfig: 'build.xml', argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api); expect(existsSyncSpy).toHaveBeenCalled(); expect(() => { electronBuilder.fetchPlatformDefaults('name'); }).toThrow(new Error('Your platform "name" is not supported as a default target platform for Electron.')); }); it('should __appendUserSigning linux signing.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { linux: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release' } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendUserSigning('linux', platformConfig.linux.signing, buildOptions); expect(existsSyncSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled(); const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'The provided signing information for the Linux platform is ignored. Linux does not support signing.'; expect(actual).toEqual(expected); }); it('should __appendUserSigning mac with masconfig.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { mac: { package: ['package', 'package2'], signing: { store: { requirements: 'requirements' } } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; // config.mas is appeneded to build options to spoof what __formatAppendUserSettings method would have performed. const buildOptions = { debug: false, buildConfig, argv: [], config: { mas: {} } }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendUserSigning('mac', platformConfig.mac.signing, buildOptions); expect(existsSyncSpy).toHaveBeenCalled(); expect(buildOptions.buildConfig.electron.mac.signing.store.requirements).toBe(undefined); }); it('should format nsis-web taget with nsisWeb top-level configs in __formatAppendUserSettings.', () => { // Sample target configuration option const appPackageUrl = 'https://foo.bar/apps/win/web'; // The settings which will be populated by `__formatAppendUserSettings` const userBuildSettings = {}; // platform config partial from `build.json` const platformConfig = { package: [ { 'nsis-web': { appPackageUrl } } ] }; // the mock `build.json` const buildConfig = { electron: { windows: platformConfig } }; // the build options which is passed from CLI/Lib to Platform Build const buildOptions = { argv: [], buildConfig }; // // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api) .__formatAppendUserSettings('win', platformConfig, userBuildSettings); expect(existsSyncSpy).toHaveBeenCalled(); expect(userBuildSettings.config.nsisWeb.appPackageUrl).toBe(appPackageUrl); }); it('should append user singing for windows', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { win: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release' } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendUserSigning('win', platformConfig, buildOptions); expect(existsSyncSpy).toHaveBeenCalled(); }); it('should set buildConfigs __appendMacUserSigning when files exist.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { darwin: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: { requirements: 'requirements' } } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; const config = { debug: 'debug', release: 'release', identity: 'identify', entitlements: 'entitlements', entitlementsInherit: 'entitlementsInherit', requirements: 'requirements', provisioningProfile: 'provisioningProfile', store: 'store' }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendMacUserSigning(config, buildConfig); expect(existsSyncSpy).toHaveBeenCalled(); expect(buildConfig.identity).toEqual(config.identity); expect(buildConfig.entitlements).toEqual(config.entitlements); expect(buildConfig.entitlementsInherit).toEqual(config.entitlementsInherit); expect(buildConfig.requirements).toEqual(config.requirements); expect(buildConfig.provisioningProfile).toEqual(config.provisioningProfile); }); it('should emit warning when in __appendMacUserSigning when files does not exist and set identity to process env link.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { darwin: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: { requirements: 'requirements' } } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; const config = { debug: 'debug', release: 'release', entitlements: 'entitlements', entitlementsInherit: 'entitlementsInherit', requirements: 'requirements', provisioningProfile: 'provisioningProfile', store: 'store' }; // set process.env.CSC_LINK process.env.CSC_LINK = 'csc_link'; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendMacUserSigning(config, buildConfig); expect(existsSyncSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled(); expect(buildConfig.identity).toEqual('csc_link'); expect(buildConfig.entitlements).toEqual(undefined); expect(buildConfig.entitlementsInherit).toEqual(undefined); expect(buildConfig.requirements).toEqual(undefined); expect(buildConfig.provisioningProfile).toEqual(undefined); const actualEntitlements = emitSpy.calls.argsFor(0)[1]; const expectedEntitlements = 'The provided entitlements file does not exist'; expect(actualEntitlements).toContain(expectedEntitlements); const actualEntitlementsInherits = emitSpy.calls.argsFor(1)[1]; const expectedEntitlementsInherits = 'The provided entitlements inherit file does not exist'; expect(actualEntitlementsInherits).toContain(expectedEntitlementsInherits); const actualRequirements = emitSpy.calls.argsFor(2)[1]; const expectedRequirements = 'The provided requirements file does not exist'; expect(actualRequirements).toContain(expectedRequirements); const actualProvisioningProfile = emitSpy.calls.argsFor(3)[1]; const expectedProvisioningProfiles = 'The provided provisioning profile does not exist'; expect(actualProvisioningProfile).toContain(expectedProvisioningProfiles); }); it('should emit warning when in __appendMacUserSigning when files does not exist and set identity to process env name.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { darwin: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: { requirements: 'requirements' } } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; const config = { debug: 'debug', release: 'release', entitlements: 'entitlements', entitlementsInherit: 'entitlementsInherit', requirements: 'requirements', provisioningProfile: 'provisioningProfile', store: 'store' }; // set process.env.CSC_NAME process.env.CSC_NAME = 'csc_name'; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendMacUserSigning(config, buildConfig); expect(existsSyncSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled(); expect(buildConfig.identity).toEqual('csc_name'); expect(buildConfig.entitlements).toEqual(undefined); expect(buildConfig.entitlementsInherit).toEqual(undefined); expect(buildConfig.requirements).toEqual(undefined); expect(buildConfig.provisioningProfile).toEqual(undefined); const actualEntitlements = emitSpy.calls.argsFor(0)[1]; const expectedEntitlements = 'The provided entitlements file does not exist'; expect(actualEntitlements).toContain(expectedEntitlements); const actualEntitlementsInherits = emitSpy.calls.argsFor(1)[1]; const expectedEntitlementsInherits = 'The provided entitlements inherit file does not exist'; expect(actualEntitlementsInherits).toContain(expectedEntitlementsInherits); const actualRequirements = emitSpy.calls.argsFor(2)[1]; const expectedRequirements = 'The provided requirements file does not exist'; expect(actualRequirements).toContain(expectedRequirements); const actualProvisioningProfile = emitSpy.calls.argsFor(3)[1]; const expectedProvisioningProfiles = 'The provided provisioning profile does not exist'; expect(actualProvisioningProfile).toContain(expectedProvisioningProfiles); }); it('should set buildConfigs in __appendWindowsUserSigning for windows singning when files exist.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { windows: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: 'requirements' } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; const config = { debug: 'debug', release: 'release', identity: 'identify', certificateFile: 'certificateFile', certificatePassword: 'certificatePassword', certificateSubjectName: 'certificateSubjectName', certificateSha1: 'certificateSha1', signingHashAlgorithms: 'signingHashAlgorithms', additionalCertificateFile: 'additionalCertificateFile' }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendWindowsUserSigning(config, buildConfig); expect(existsSyncSpy).toHaveBeenCalled(); expect(buildConfig.certificateFile).toEqual(config.certificateFile); expect(buildConfig.certificatePassword).toEqual(config.certificatePassword); expect(buildConfig.certificateSubjectName).toEqual(config.certificateSubjectName); expect(buildConfig.certificateSha1).toEqual(config.certificateSha1); expect(buildConfig.signingHashAlgorithms).toEqual(config.signingHashAlgorithms); expect(buildConfig.additionalCertificateFile).toEqual(config.additionalCertificateFile); }); it('should set buildConfigs in __appendWindowsUserSigning for windows singning when files exist, but certificate password does not.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { windows: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: 'requirements' } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; const config = { debug: 'debug', release: 'release', identity: 'identify', certificateFile: 'certificateFile', certificateSubjectName: 'certificateSubjectName', certificateSha1: 'certificateSha1', signingHashAlgorithms: 'signingHashAlgorithms', additionalCertificateFile: 'additionalCertificateFile' }; // set process.env.CSC_KEY_PASSWORD process.env.CSC_KEY_PASSWORD = 'csc_key_password'; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendWindowsUserSigning(config, buildConfig); expect(existsSyncSpy).toHaveBeenCalled(); expect(buildConfig.certificateFile).toEqual(config.certificateFile); expect(buildConfig.certificatePassword).toEqual('csc_key_password'); expect(buildConfig.certificateSubjectName).toEqual(config.certificateSubjectName); expect(buildConfig.certificateSha1).toEqual(config.certificateSha1); expect(buildConfig.signingHashAlgorithms).toEqual(config.signingHashAlgorithms); expect(buildConfig.additionalCertificateFile).toEqual(config.additionalCertificateFile); }); it('should set buildConfigs in __appendWindowsUserSigning for windows singning when files exist, but certificate and process env password does not.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { windows: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: 'requirements' } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; const config = { debug: 'debug', release: 'release', identity: 'identify', certificateFile: 'certificateFile', certificateSubjectName: 'certificateSubjectName', certificateSha1: 'certificateSha1', signingHashAlgorithms: 'signingHashAlgorithms', additionalCertificateFile: 'additionalCertificateFile' }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendWindowsUserSigning(config, buildConfig); expect(existsSyncSpy).toHaveBeenCalled(); expect(buildConfig.certificateFile).toEqual(config.certificateFile); expect(buildConfig.certificatePassword).toEqual(undefined); expect(buildConfig.certificateSubjectName).toEqual(config.certificateSubjectName); expect(buildConfig.certificateSha1).toEqual(config.certificateSha1); expect(buildConfig.signingHashAlgorithms).toEqual(config.signingHashAlgorithms); expect(buildConfig.additionalCertificateFile).toEqual(config.additionalCertificateFile); }); it('should set buildConfigs in __appendWindowsUserSigning for windows singning when files does not exist.', () => { // mock platformConfig, buildConfig and buildOptions Objects const platformConfig = { windows: { package: ['package', 'package2'], signing: { debug: 'debug', release: 'release', store: 'requirements' } } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig, argv: [] }; const config = { debug: 'debug', release: 'release', identity: 'identify', certificateFile: 'certificateFile', certificatePassword: 'certificatePassword', certificateSubjectName: 'certificateSubjectName', certificateSha1: 'certificateSha1', signingHashAlgorithms: 'signingHashAlgorithms', additionalCertificateFile: 'additionalCertificateFile' }; // create spies existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(false); build.__set__('fs', { existsSync: existsSyncSpy }); electronBuilder = new ElectronBuilder(buildOptions, api).__appendWindowsUserSigning(config, buildConfig); expect(existsSyncSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled(); expect(buildConfig.certificateFile).toEqual(undefined); expect(buildConfig.certificatePassword).toEqual(undefined); expect(buildConfig.certificateSubjectName).toEqual(config.certificateSubjectName); expect(buildConfig.certificateSha1).toEqual(config.certificateSha1); expect(buildConfig.signingHashAlgorithms).toEqual(config.signingHashAlgorithms); expect(buildConfig.additionalCertificateFile).toEqual(undefined); const actualCertificateFile = emitSpy.calls.argsFor(0)[1]; const expectedCertificateFile = 'The provided certificate file does not exist'; expect(actualCertificateFile).toContain(expectedCertificateFile); const actualAdditionalCertificateFile = emitSpy.calls.argsFor(1)[1]; const expectedAdditionalCertificateFile = 'The provided addition certificate file does not exist'; expect(actualAdditionalCertificateFile).toContain(expectedAdditionalCertificateFile); }); it('should call build method.', () => { // mock buildOptions Objecet const buildOptions = { debug: true, buildConfig: 'build.xml', argv: [] }; // create spies const buildSpy = jasmine.createSpy('build'); build.__set__('require', () => ({ build: buildSpy })); electronBuilder = new ElectronBuilder(buildOptions, api).build(); expect(buildSpy).toHaveBeenCalled(); }); }); describe('Module exports run', () => { it('should have called configure and build.', () => { const platformConfig = { mac: { arch: ['x64'] } }; const buildConfig = { electron: platformConfig, author: 'Apache', name: 'Guy', displayName: 'HelloWorld', APP_BUILD_DIR: api.locations.build, APP_BUILD_RES_DIR: api.locations.buildRes, APP_WWW_DIR: api.locations.www }; const buildOptions = { debug: false, buildConfig: 'LOAD_MY_FAKE_DATA', argv: [] }; // create spies const existsSyncSpy = jasmine.createSpy('existsSync').and.returnValue(true); build.__set__('fs', { existsSync: existsSyncSpy }); build.__set__('require', (file) => { if (file === 'LOAD_MY_FAKE_DATA') return buildConfig; if (file === './check_reqs') return { run: () => Promise.resolve([]) }; return require(file); }); const configureSpy = jasmine.createSpy('configure'); const buildSpy = jasmine.createSpy('build'); class ElectronBuilderMock { configure () { configureSpy(); return this; } build () { buildSpy(); return this; } } build.__set__('ElectronBuilder', ElectronBuilderMock); build.run(buildOptions, api).then(() => { expect(configureSpy).toHaveBeenCalled(); expect(buildSpy).toHaveBeenCalled(); }); }); it('should have failed requirement check and thrown error.', () => { const buildOptions = { debug: false, buildConfig: 'LOAD_MY_FAKE_DATA', argv: [] }; const errorMsg = 'error'; spyOn(check_reqs, 'run').and.callFake(() => Promise.reject(new Error(errorMsg))); return build.run(buildOptions, api).then( () => fail('Unexpectedly resolved'), error => { expect(check_reqs.run).toHaveBeenCalled(); expect(error.message).toBe(errorMsg); } ); }); }); }); ================================================ FILE: tests/spec/unit/lib/clean.spec.js ================================================ /* 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 path = require('node:path'); const fs = require('node:fs'); const rootDir = path.resolve(__dirname, '../../../..'); const clean = require(path.join(rootDir, 'lib/clean')); const check_reqs = require(path.join(rootDir, 'lib/check_reqs')); describe('Clean', () => { describe('run export method', () => { it('should stop process when requirement check fails.', () => { // set spies spyOn(console, 'error'); spyOn(check_reqs, 'run').and.returnValue(false); spyOn(process, 'exit'); // run test clean.run(); const expectedLog = 'Please make sure you meet the software requirements in order to clean an electron cordova project'; expect(console.error).toHaveBeenCalledWith(expectedLog); expect(process.exit).toHaveBeenCalledWith(2); }); it('should not find previous build dir and not attempt to remove.', () => { spyOn(check_reqs, 'run').and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(false); spyOn(fs, 'rmSync'); clean.run(); expect(fs.existsSync).toHaveBeenCalled(); expect(fs.rmSync).not.toHaveBeenCalled(); }); it('should find previous build dir and attempt to remove.', () => { spyOn(check_reqs, 'run').and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'rmSync'); clean.run(); expect(fs.existsSync).toHaveBeenCalled(); expect(fs.rmSync).toHaveBeenCalled(); }); it('should find previous build dir and fail to remove.', () => { spyOn(console, 'log'); spyOn(check_reqs, 'run').and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'rmSync').and.callFake(() => { throw new Error('Fake Error'); }); clean.run(); expect(console.log).toHaveBeenCalledWith( jasmine.stringMatching(/could not remove/) ); }); }); describe('cleanProject export method', () => { it('should console out that it will execute run command.', () => { spyOn(console, 'log'); clean.cleanProject(); expect(console.log).toHaveBeenCalledWith('lib/clean will soon only export a `run` command, please update to not call `cleanProject`.'); }); }); }); ================================================ FILE: tests/spec/unit/lib/create.spec.js ================================================ /* 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 os = require('node:os'); const fs = require('node:fs'); const path = require('node:path'); const rewire = require('rewire'); const rootDir = path.resolve(__dirname, '../../../..'); function makeTempDir () { const dir = path.join(os.tmpdir(), 'cordova-electron-test-'); return fs.realpathSync(fs.mkdtempSync(dir)); } describe('create', () => { let create, tmpDir; beforeEach(() => { create = rewire(path.join(rootDir, 'lib/create')); tmpDir = makeTempDir(); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); it('creates a project that has the expected files', () => { const projectname = 'testcreate'; const projectid = 'com.test.app1'; const projectPath = path.join(tmpDir, projectname); return create.createProject(projectPath, projectname, projectid).then(() => { for (const filePath of ['build-res', 'cordova/Api.js', 'cordova/version']) { expect(fs.existsSync(path.join(projectPath, filePath))).toBe(true); } }); }); it('create project with ascii+unicode name, and spaces', () => { const projectname = '応応応応 hello 用用用用'; const projectid = 'com.test.app6'; const projectPath = path.join(tmpDir, projectname); create.__set__('fs', { mkdirSync: fs.mkdirSync, existsSync: path => path !== projectPath, cpSync: () => true }); return create.createProject(projectPath, projectname, projectid).then(() => { expect(fs.existsSync(projectPath)).toBe(true); }); }); it('should stop creating project when project destination already exists', () => { create.__set__('fs', { existsSync: jasmine.createSpy('existsSync').and.returnValue(true) }); const projectname = 'alreadyexist'; const projectid = 'com.test.alreadyexist'; expect(() => { create.createProject(tmpDir, projectname, projectid, projectname); }).toThrowError(/destination already exists/); }); it('should stop creating project when requirement check fails', () => { create.__set__('check_reqs', { run: jasmine.createSpy('check_reqs').and.returnValue(false) }); const emitSpy = jasmine.createSpy('emit'); create.__set__('events', { emit: emitSpy }); const projectname = 'reqfail'; const projectid = 'com.test.reqfail'; create.createProject(tmpDir, projectname, projectid, projectname); expect(emitSpy).toHaveBeenCalledWith('error', 'Please make sure you meet the software requirements in order to build a cordova electron project'); }); }); ================================================ FILE: tests/spec/unit/lib/handler.spec.js ================================================ /* 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 path = require('node:path'); const fs = require('node:fs'); const { events } = require('cordova-common'); const rewire = require('rewire'); const rootDir = path.resolve(__dirname, '../../../..'); const fixturesDir = path.join(rootDir, 'tests/spec/fixtures'); const tmpDir = path.join(rootDir, 'temp'); const testProjectDir = path.join(tmpDir, 'testapp'); const handler = rewire(path.join(rootDir, 'lib/handler')); describe('Handler export', () => { describe('www_dir method', () => { it('should return the project\'s www dir.', () => { const projectDir = 'mocked-project-dir-path'; expect( handler.www_dir(projectDir) ).toBe(path.join(projectDir, 'www')); }); }); describe('package_name method', () => { it('should return default package name when config.xml file is missing', () => { spyOn(fs, 'existsSync').and.returnValue(false); expect( handler.package_name('mocked-project-dir-path') ).toBe('io.cordova.hellocordova'); }); it('should return default package name when config.xml file does not contain the widget id', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'readFileSync').and.returnValue(''); // blank config.file expect( handler.package_name('mocked-project-dir-path') ).toBe('io.cordova.hellocordova'); }); it('should return package name defined on the widget element id attribute in config.xml file', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'readFileSync').and.returnValue(` `); expect( handler.package_name('mocked-project-dir-path') ).toBe('com.foobar.random'); }); }); describe('js-module method', () => { describe('js-module.install', () => { const moduleExportTestCase = jsModule => { const plugin_dir = 'mock'; const plugin_id = 'com.foo.dummy-plugin'; const www_dir = 'www'; const moduleName = `${plugin_id}.${jsModule.name || path.basename(jsModule.src, path.extname(jsModule.src))}`; // spies spyOn(fs, 'readFileSync').and.returnValue(''); // fake scriptContent spyOn(fs, 'mkdirSync').and.returnValue(true); spyOn(fs, 'writeFileSync'); handler['js-module'].install(jsModule, plugin_dir, plugin_id, www_dir); const moduleDetination = path.dirname(path.resolve(www_dir, 'plugins', plugin_id, jsModule.src)); const writeFileSyncContent = fs.writeFileSync.calls.argsFor(0)[1]; expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(plugin_dir, jsModule.src), 'utf8'); expect(fs.mkdirSync).toHaveBeenCalledWith(moduleDetination, { recursive: true }); expect(writeFileSyncContent).toContain(`cordova.define('${moduleName}'`); return writeFileSyncContent; }; it('should write cordova.define for module "com.foo.dummy-plugin.dummy-module" (from source name) and not module.exports as module source is not JSON.', () => { const jsModule = { src: 'src/dummy-module', name: 'dummy-module' }; const writeFileSyncContent = moduleExportTestCase(jsModule); expect(writeFileSyncContent).not.toContain('module.exports'); }); it('should write cordova.define for module "com.foo.dummy-plugin.dummy-module" (from source filepath) and not module.exports as module source is not JSON.', () => { const jsModule = { src: 'src/dummy-module' }; const writeFileSyncContent = moduleExportTestCase(jsModule); expect(writeFileSyncContent).not.toContain('module.exports'); }); it('should write cordova.define for module "com.foo.dummy-plugin.dummy-module" (from source filepath) and not module.exports as module source is not JSON.', () => { const jsModule = { src: 'src/dummy-module/something.json' }; const writeFileSyncContent = moduleExportTestCase(jsModule); expect(writeFileSyncContent).toContain('module.exports'); }); }); describe('js-module.uninstall', () => { it('should emit that js-module uninstall was called.', () => { const jsModule = { src: 'src/dummy-module', name: 'dummy-module' }; const www_dir = 'www'; const plugin_id = 'com.foo.dummy-plugin'; // spies spyOn(fs, 'rmSync').and.returnValue(true); spyOn(events, 'emit'); handler['js-module'].uninstall(jsModule, www_dir, plugin_id); expect(fs.rmSync).toHaveBeenCalledWith(path.join(www_dir, 'plugins', plugin_id), { recursive: true, force: true }); expect(events.emit).toHaveBeenCalledWith( 'verbose', `js-module uninstall called : ${path.join('plugins', plugin_id, jsModule.src)}` ); }); }); }); describe('asset method', () => { const plugin_dir = 'pluginDir'; const wwwDest = 'dest'; describe('asset.install', () => { it('should cpSync assets to destination.', () => { const asset = { itemType: 'asset', src: 'someSrc/ServiceWorker.js', target: 'ServiceWorker.js' }; // Spies spyOn(fs, 'cpSync'); spyOn(fs, 'mkdirSync').and.returnValue(true); handler.asset.install(asset, plugin_dir, wwwDest); expect(fs.mkdirSync).toHaveBeenCalled(); expect(fs.cpSync).toHaveBeenCalledWith(jasmine.any(String), path.join('dest', asset.target), { recursive: true }); }); }); describe('asset.uninstall', () => { it('should remove assets', () => { const asset = { itemType: 'asset', src: 'someSrc', target: 'ServiceWorker.js' }; const mockPluginId = 'com.foobar.random-plugin-id'; // Spies spyOn(fs, 'rmSync').and.returnValue(true); handler.asset.uninstall(asset, wwwDest, mockPluginId); expect(fs.rmSync).toHaveBeenCalled(); expect(fs.rmSync.calls.argsFor(0)[0]).toBe(path.join(wwwDest, asset.target)); expect(fs.rmSync.calls.argsFor(1)[0]).toBe(path.join(wwwDest, 'plugins', mockPluginId)); }); }); }); describe('framework method', () => { const frameworkInstallMockObject = { itemType: 'framework', type: undefined, parent: undefined, custom: false, embed: false, src: 'src/electron', spec: undefined, weak: false, versions: undefined, targetDir: undefined, deviceTarget: undefined, arch: undefined, implementation: undefined }; const frameworkInstallPluginId = 'cordova-plugin-sample'; const frameworkInstallElectronPluginId = `${frameworkInstallPluginId}-electron`; const frameworkInstallPluginDir = path.join(testProjectDir, `plugins/${frameworkInstallPluginId}`); const frameworkInstallProjectDir = path.join(testProjectDir, 'platforms/electron'); const frameworkInstallProjectAppPackageFile = path.join(frameworkInstallProjectDir, 'www/package.json'); const frameworkInstallPluginPackageFile = path.join(frameworkInstallPluginDir, 'src/electron/package.json'); beforeEach(() => { fs.mkdirSync(tmpDir, { recursive: true }); fs.cpSync(path.resolve(fixturesDir, 'test-app-with-electron-plugin'), testProjectDir, { recursive: true }); spyOn(events, 'emit'); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); describe('framework.install', () => { it('should not install framework when the source path does not exist.', async () => { const execaSpy = jasmine.createSpy('execa'); handler.__set__('execa', execaSpy); spyOn(fs, 'existsSync').and.returnValue(false); await handler.framework.install( frameworkInstallMockObject, frameworkInstallPluginDir, frameworkInstallProjectDir, frameworkInstallPluginId ); expect(events.emit).toHaveBeenCalledWith( 'warn', '[Cordova Electron] The defined "framework" source path does not exist and can not be installed.' ); events.emit.calls.reset(); }); it('should update the electron app package when service is registered', async () => { const execaSpy = jasmine.createSpy('execa'); handler.__set__('execa', execaSpy); // Mock npm install by updating the App's package.json const appPackage = JSON.parse( fs.readFileSync(frameworkInstallProjectAppPackageFile, 'utf8') ); appPackage.dependencies[frameworkInstallElectronPluginId] = path.relative( frameworkInstallProjectAppPackageFile, path.join(frameworkInstallPluginDir, 'src/electron') ); fs.writeFileSync( frameworkInstallProjectAppPackageFile, JSON.stringify(appPackage, null, 2), 'utf8' ); await handler.framework.install( frameworkInstallMockObject, frameworkInstallPluginDir, frameworkInstallProjectDir, frameworkInstallPluginId ); // Validate that sample plugin's service is registered. const validateAppPackage = JSON.parse(fs.readFileSync(frameworkInstallProjectAppPackageFile, 'utf8')); const test = validateAppPackage && validateAppPackage.cordova && validateAppPackage.cordova.services && validateAppPackage.cordova.services.Sample; expect(test).toBe(frameworkInstallElectronPluginId); }); it('should not update the electron app package when there are no registered service', async () => { const execaSpy = jasmine.createSpy('execa').and.returnValue({ stdout: frameworkInstallElectronPluginId }); handler.__set__('execa', execaSpy); // Mock npm install by updating the App's package.json const appPackage = JSON.parse( fs.readFileSync(frameworkInstallProjectAppPackageFile, 'utf8') ); appPackage.dependencies[frameworkInstallElectronPluginId] = path.relative( frameworkInstallProjectAppPackageFile, path.join(frameworkInstallPluginDir, 'src/electron') ); fs.writeFileSync( frameworkInstallProjectAppPackageFile, JSON.stringify(appPackage, null, 2), 'utf8' ); const pluginPackage = JSON.parse(fs.readFileSync(frameworkInstallPluginPackageFile, 'utf8')); delete pluginPackage.cordova; fs.writeFileSync( frameworkInstallPluginPackageFile, JSON.stringify(pluginPackage, null, 2), 'utf8' ); await handler.framework.install( frameworkInstallMockObject, frameworkInstallPluginDir, frameworkInstallProjectDir, frameworkInstallPluginId ); // Validate that sample plugin's service is registered. const validateAppPackage = JSON.parse(fs.readFileSync(frameworkInstallProjectAppPackageFile, 'utf8')); const test = validateAppPackage && validateAppPackage.cordova && validateAppPackage.cordova.services && validateAppPackage.cordova.services.Sample; expect(test).not.toBe(frameworkInstallElectronPluginId); }); it('should warn when there are conflicting service name between more then one plugin', async () => { const execaSpy = jasmine.createSpy('execa').and.returnValue({ stdout: frameworkInstallElectronPluginId }); handler.__set__('execa', execaSpy); // Mock npm install by updating the App's package.json const appPackage = JSON.parse( fs.readFileSync(frameworkInstallProjectAppPackageFile, 'utf8') ); appPackage.dependencies[frameworkInstallElectronPluginId] = path.relative( frameworkInstallProjectAppPackageFile, path.join(frameworkInstallPluginDir, 'src/electron') ); // fake some other sample service already registered appPackage.cordova = appPackage.cordova || {}; appPackage.cordova.services = appPackage.cordova.services || { Sample: 'cordova-plugin-sample-electron' }; fs.writeFileSync( frameworkInstallProjectAppPackageFile, JSON.stringify(appPackage, null, 2), 'utf8' ); await handler.framework.install( frameworkInstallMockObject, frameworkInstallPluginDir, frameworkInstallProjectDir, frameworkInstallPluginId ); expect(events.emit).toHaveBeenCalledWith( 'warn', '[Cordova Electron] The service name "Sample" is already taken by "cordova-plugin-sample-electron" and can not be redeclared.' ); events.emit.calls.reset(); }); }); describe('framework.uninstall', () => { it('should delink service name if defined', async () => { const execaSpy = jasmine.createSpy('execa'); handler.__set__('execa', execaSpy); // Mock npm install by updating the App's package.json const appPackage = JSON.parse( fs.readFileSync(frameworkInstallProjectAppPackageFile, 'utf8') ); appPackage.dependencies[frameworkInstallElectronPluginId] = path.relative( frameworkInstallProjectAppPackageFile, path.join(frameworkInstallPluginDir, 'src/electron') ); // fake some other sample service already registered appPackage.cordova = appPackage.cordova || {}; appPackage.cordova.services = appPackage.cordova.services || { Sample: 'cordova-plugin-sample-electron' }; fs.writeFileSync( frameworkInstallProjectAppPackageFile, JSON.stringify(appPackage, null, 2), 'utf8' ); await handler.framework.uninstall( frameworkInstallMockObject, frameworkInstallPluginDir, frameworkInstallProjectDir ); expect(events.emit).toHaveBeenCalledWith( 'verbose', '[Cordova Electron] The service name "Sample" was delinked.' ); events.emit.calls.reset(); }); it('should not delink service name if defined by another plugin', async () => { const execaSpy = jasmine.createSpy('execa'); handler.__set__('execa', execaSpy); // Mock npm install by updating the App's package.json const appPackage = JSON.parse( fs.readFileSync(frameworkInstallProjectAppPackageFile, 'utf8') ); appPackage.dependencies[frameworkInstallElectronPluginId] = path.relative( frameworkInstallProjectAppPackageFile, path.join(frameworkInstallPluginDir, 'src/electron') ); // fake some other sample service already registered appPackage.cordova = appPackage.cordova || {}; appPackage.cordova.services = appPackage.cordova.services || { Sample: 'some-other-package' }; fs.writeFileSync( frameworkInstallProjectAppPackageFile, JSON.stringify(appPackage, null, 2), 'utf8' ); await handler.framework.uninstall( frameworkInstallMockObject, frameworkInstallPluginDir, frameworkInstallProjectDir ); expect(events.emit).not.toHaveBeenCalled(); }); it('should not delink service name when not defined', async () => { const execaSpy = jasmine.createSpy('execa'); handler.__set__('execa', execaSpy); await handler.framework.uninstall( frameworkInstallMockObject, frameworkInstallPluginDir, frameworkInstallProjectDir ); expect(events.emit).not.toHaveBeenCalled(); }); }); }); describe('Unsupported Handlers', () => { it('should emit that the install and uninstall methods are not supported for X types.', () => { spyOn(events, 'emit'); // loop unsupported types [ 'source-file', 'header-file', 'resource-file', 'lib-file' ].forEach(type => { for (const method of ['install', 'uninstall']) { handler[type][method](); expect(events.emit).toHaveBeenCalledWith( 'verbose', `${type}.${method} is not supported for electron` ); events.emit.calls.reset(); } }); }); }); }); ================================================ FILE: tests/spec/unit/lib/parser.spec.js ================================================ /* 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 path = require('node:path'); const fs = require('node:fs'); const rewire = require('rewire'); const { CordovaError, events } = require('cordova-common'); const rootDir = path.resolve(__dirname, '../../../..'); const Parser = rewire(path.join(rootDir, 'lib/parser')); const mockProjectPath = 'mock_project_path'; describe('Parser class', () => { describe('Constructing parser instanse', () => { it('should not have valid electron project', () => { Parser.__set__('dirExists', jasmine.createSpy('dirExists').and.returnValue(false)); expect(() => new Parser(mockProjectPath)).toThrow( new CordovaError(`The provided path "${mockProjectPath}" is not a valid electron project.`) ); }); it('should have valid electron project and set path', () => { Parser.__set__('dirExists', jasmine.createSpy('dirExists').and.returnValue(true)); const parser = new Parser(mockProjectPath); expect(parser.path).toBe(mockProjectPath); }); }); describe('Class Methods', () => { let parser; beforeEach(() => { Parser.__set__('dirExists', jasmine.createSpy('dirExists').and.returnValue(true)); parser = new Parser(mockProjectPath); }); describe('update_from_config method', () => { it('should return a resolved promise.', () => { const actual = parser.update_from_config(); expect(typeof actual).toBe(typeof Promise.resolve()); }); }); describe('www_dir method', () => { it('should return the projects www dir path.', () => { const actual = parser.www_dir(); expect(actual).toBe(path.join(mockProjectPath, 'www')); }); }); describe('update_www method', () => { const mockConfigParser = { root: 'mock', projectConfig: { path: path.join('mock', 'config.xml'), cdvNamespacePrefix: 'cdv' }, locations: { www: path.join('mock', 'www') } }; it('should detect merges/electron file path and merge/update user source files into the platform staging dir', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(events, 'emit'); const mergeAndUpdateDirSpy = jasmine.createSpy('mergeAndUpdateDir'); Parser.__set__('FileUpdater', { mergeAndUpdateDir: mergeAndUpdateDirSpy }); parser.update_www(mockConfigParser); expect(events.emit).toHaveBeenCalledWith( 'verbose', 'Found "merges/electron" folder. Copying its contents into the electron project.' ); const expectedSourceDirs = [ 'www', path.join('..', mockProjectPath, 'platform_www'), path.join('merges', 'electron') ]; const expectedTargetDir = path.join('..', mockProjectPath, 'www'); expect(events.emit).toHaveBeenCalledWith( 'verbose', `Merging and updating files from [${expectedSourceDirs.join(', ')}] to ${expectedTargetDir}` ); expect(mergeAndUpdateDirSpy).toHaveBeenCalled(); }); it('should detect merges/electron file path and merge/update user source files into the platform staging dir', () => { spyOn(fs, 'existsSync').and.returnValue(false); spyOn(events, 'emit'); const mergeAndUpdateDirSpy = jasmine.createSpy('mergeAndUpdateDir'); Parser.__set__('FileUpdater', { mergeAndUpdateDir: mergeAndUpdateDirSpy }); parser.update_www(mockConfigParser); expect(fs.existsSync).toHaveBeenCalled(); expect(events.emit).toHaveBeenCalled(); // The emit message was. let actualEmit = events.emit.calls.argsFor(0)[1]; let expectedEmit = 'Found "merges/electron" folder. Copying its contents into the electron project.'; expect(actualEmit).not.toBe(expectedEmit); const expectedSourceDirs = [ 'www', path.join('..', mockProjectPath, 'platform_www') ]; const expectedTargetDir = path.join('..', mockProjectPath, 'www'); actualEmit = events.emit.calls.argsFor(0)[1]; expectedEmit = `Merging and updating files from [${expectedSourceDirs.join(', ')}] to ${expectedTargetDir}`; expect(actualEmit).toBe(expectedEmit); expect(mergeAndUpdateDirSpy).toHaveBeenCalled(); }); }); describe('config_xml method', () => { it('should return the projects config.xml file path.', () => { expect(parser.config_xml()).toBe(path.join(mockProjectPath, 'config.xml')); }); }); describe('update_project method', () => { it('should copy munged config.xml to platform www dir.', () => { const cpSyncSpy = jasmine.createSpy('cpSync'); Parser.__set__('fs', { cpSync: cpSyncSpy }); parser.update_project().then(() => { const actualSrc = cpSyncSpy.calls.argsFor(0)[0]; const actualDest = cpSyncSpy.calls.argsFor(0)[1]; expect(cpSyncSpy).toHaveBeenCalled(); expect(actualSrc).toBe(path.join(mockProjectPath, 'config.xml')); expect(actualDest).toBe(path.join(mockProjectPath, 'www', 'config.xml')); }); }); }); }); describe('logFileOp method', () => { it('should emit passed in message.', () => { spyOn(events, 'emit'); const msg = 'random message'; const logFileOp = Parser.__get__('logFileOp'); logFileOp(msg); // The emit message was. const actualEmit = events.emit.calls.argsFor(0)[1]; expect(events.emit).toHaveBeenCalled(); expect(actualEmit).toBe(` ${msg}`); }); }); }); ================================================ FILE: tests/spec/unit/lib/prepare.spec.js ================================================ /* 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 rewire = require('rewire'); const path = require('node:path'); const fs = require('node:fs'); const CordovaError = require('cordova-common').CordovaError; const rootDir = path.resolve(__dirname, '../../../..'); const fixturesDir = path.join(rootDir, 'tests/spec/fixtures'); const tmpDir = path.join(rootDir, 'temp'); const testProjectDir = path.join(tmpDir, 'testapp'); const Api = require(path.join(rootDir, 'bin/templates/cordova/Api')); let prepare; /** * Create a mock item from the getIcon and getSplashScreens collections with the supplied updated data. * * @param {Object} data Changes to apply to the mock getIcon item */ function mockGetImageItem (data) { return Object.assign({}, { src: undefined, target: undefined, density: undefined, platform: 'electron', width: undefined, height: undefined, background: undefined, foreground: undefined }, data); } const defaultMockProjectPackageJson = `{ "name": "io.cordova.electronTest", "displayName": "electronTest", "version": "1.0.0", "description": "A Sample Apache Cordova Electron Application.", "author": "Apache Cordova Team", "license": "Apache-2.0", "dependencies": { "cordova-electron": "^1.0.2" }, "devDependencies": {}, "cordova": { "plugins": {}, "platforms": ["electron"] } }`; const cordovaProjectDefault = { root: 'MOCK_PROJECT_ROOT', projectConfig: { path: path.join('MOCK_PROJECT_ROOT', 'config.xml'), cdvNamespacePrefix: 'cdv', doc: { getroot: function () { return this; }, find: function (path) { path = { attrib: { src: '' } }; return path; }, findall: function (path) { path = [ { attrib: { name: 'SplashScreen', value: '' } }, { attrib: { name: '', value: '' } } ]; return path; } }, write: function () { return this; } }, locations: { buildRes: path.join('MOCK_PROJECT_ROOT', 'build-res'), www: path.join('MOCK_PROJECT_ROOT', 'www'), configXml: path.join('MOCK_PROJECT_ROOT', 'config.xml'), platformRootDir: path.join('MOCK_PROJECT_ROOT', 'platform_www') } }; const locationsDefault = cordovaProjectDefault.locations; let fakeParserConstructorSpy; let fakeManifestJsonParserConfigureSpy; let fakePackageJsonParserConfigureSpy; let fakePackageJsonParserEnableDevToolsSpy; let fakeSettingJsonParserConfigureSpy; let fakeParserWriteSpy; let fakeConfigParserConstructorSpy; let fakeConfigParserWriteSpy; let mergeXmlSpy; let updateIconsSpy; let updateSplashScreensSpy; let emitSpy; let xmlHelpersMock; let updateIconsFake; let updateSplashScreensFake; function createSpies () { fakeParserConstructorSpy = jasmine.createSpy('fakeParserConstructorSpy'); fakeManifestJsonParserConfigureSpy = jasmine.createSpy('fakeManifestJsonParserConfigureSpy'); fakePackageJsonParserConfigureSpy = jasmine.createSpy('fakePackageJsonParserConfigureSpy'); fakePackageJsonParserEnableDevToolsSpy = jasmine.createSpy('fakePackageJsonParserEnableDevToolsSpy'); fakeSettingJsonParserConfigureSpy = jasmine.createSpy('fakeSettingJsonParserConfigureSpy'); fakeParserWriteSpy = jasmine.createSpy('fakeParserWriteSpy'); fakeConfigParserConstructorSpy = jasmine.createSpy('fakeConfigParserConstructorSpy'); fakeConfigParserWriteSpy = jasmine.createSpy('fakeConfigParserWriteSpy'); mergeXmlSpy = jasmine.createSpy('mergeXmlSpy'); updateIconsSpy = jasmine.createSpy('updateIconsSpy'); updateSplashScreensSpy = jasmine.createSpy('updateSplashScreensSpy'); emitSpy = jasmine.createSpy('emitSpy'); prepare = rewire(path.join(rootDir, 'lib/prepare')); prepare.__set__('events', { emit: emitSpy }); xmlHelpersMock = { mergeXml: function () { mergeXmlSpy(); return this; } }; updateIconsFake = () => { updateIconsSpy(); return this; }; updateSplashScreensFake = () => { updateSplashScreensSpy(); return this; }; } // define fake classses, methods and variables class FakeParser { constructor () { fakeParserConstructorSpy(); } write () { fakeParserWriteSpy(); return this; } } class FakeManifestJsonParser extends FakeParser { configure (configXmlParser) { fakeManifestJsonParserConfigureSpy(configXmlParser); return this; } } class FakePackageJsonParser extends FakeParser { configure (configXmlParser, projectPackageJson) { fakePackageJsonParserConfigureSpy(configXmlParser, projectPackageJson); return this; } enableDevTools (enable) { fakePackageJsonParserEnableDevToolsSpy(enable); return this; } } class FakeSettingJsonParser extends FakeParser { configure (configXmlParser, options, userElectronFile) { fakeSettingJsonParserConfigureSpy(configXmlParser, options, userElectronFile); return this; } } class FakeConfigParser { constructor () { this.doc = { getroot: function () { return this; } }; fakeConfigParserConstructorSpy(); } write () { fakeConfigParserWriteSpy(); return this; } } describe('Testing prepare.js:', () => { describe('module.exports.prepare method', () => { // define spies let updatePathsSpy; let cordovaProject; let api; beforeAll(() => { fs.mkdirSync(tmpDir, { recursive: true }); fs.cpSync(path.resolve(fixturesDir, 'testapp'), path.resolve(tmpDir, 'testapp'), { recursive: true }); api = new Api(null, testProjectDir); }); afterAll(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); beforeEach(() => { createSpies(); cordovaProject = Object.assign({}, cordovaProjectDefault); updatePathsSpy = jasmine.createSpy('updatePaths'); prepare.__set__('FileUpdater', { updatePaths: updatePathsSpy }); }); it('should generate config.xml from defaults for platform.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const defaultConfigPathMock = path.join(api.locations.platformRootDir, 'cordova', 'defaults.xml'); const ownConfigPathMock = api.locations.configXml; const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (configPath) { return configPath === defaultConfigPathMock; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = () => undefined; prepare.prepare.call(api, cordovaProject, { }, api); expect(cpSyncSpy).toHaveBeenCalledWith(defaultConfigPathMock, ownConfigPathMock, { recursive: true }); expect(mergeXmlSpy).toHaveBeenCalled(); expect(updateIconsSpy).toHaveBeenCalled(); expect(updateSplashScreensSpy).toHaveBeenCalled(); expect(fakeParserConstructorSpy).toHaveBeenCalled(); expect(fakeConfigParserConstructorSpy).toHaveBeenCalled(); expect(fakeManifestJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalled(); expect(fakeSettingJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakeParserWriteSpy).toHaveBeenCalled(); expect(fakeConfigParserWriteSpy).toHaveBeenCalled(); const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'Generating config.xml'; expect(actual).toContain(expected); }); }); it('should enable devtools.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const defaultConfigPathMock = path.join(api.locations.platformRootDir, 'cordova', 'defaults.xml'); const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (configPath) { return configPath === defaultConfigPathMock; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = () => undefined; prepare.prepare.call( api, cordovaProject, { options: { release: false } }, api ); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalledWith(true); }); }); it('should not enable devtools.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const defaultConfigPathMock = path.join(api.locations.platformRootDir, 'cordova', 'defaults.xml'); const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (configPath) { return configPath === defaultConfigPathMock; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = () => undefined; prepare.prepare.call( api, cordovaProject, { options: { release: true } }, api ); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalledWith(false); }); }); it('should get user supplied Electron settings overide path from config.xml but ignore for incorrect path.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const defaultConfigPathMock = path.join(api.locations.platformRootDir, 'cordova', 'defaults.xml'); const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (configPath) { if (configPath === defaultConfigPathMock) return true; if (configPath.includes('fail_test_path')) return false; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = (name, platform) => 'fail_test_path'; prepare.prepare.call(api, cordovaProject, { }, api); expect(fakeManifestJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalled(); expect(fakeSettingJsonParserConfigureSpy).toHaveBeenCalled(); const actual = fakeSettingJsonParserConfigureSpy.calls.argsFor(0)[1]; expect(actual).toEqual(undefined); }); }); it('should get valid user supplied Electron settings overide path from config.xml.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const defaultConfigPathMock = path.join(api.locations.platformRootDir, 'cordova', 'defaults.xml'); const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (configPath) { if (configPath === defaultConfigPathMock) return true; if (configPath.includes('pass_test_path')) return true; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = (name, platform) => 'pass_test_path'; prepare.prepare.call(api, cordovaProject, { }, api); expect(fakeManifestJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalled(); expect(fakeSettingJsonParserConfigureSpy).toHaveBeenCalled(); const actual = fakeSettingJsonParserConfigureSpy.calls.argsFor(0)[2]; expect(actual).toContain('pass_test_path'); }); }); it('should generate defaults.xml from own config.xml for platform.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const defaultConfigPathMock = path.join(api.locations.platformRootDir, 'cordova', 'defaults.xml'); const ownConfigPathMock = api.locations.configXml; const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (configPath) { return configPath === ownConfigPathMock; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = () => undefined; prepare.prepare.call(api, cordovaProject, { }, api); expect(cpSyncSpy).toHaveBeenCalledWith(ownConfigPathMock, defaultConfigPathMock, { recursive: true }); expect(mergeXmlSpy).toHaveBeenCalled(); expect(updateIconsSpy).toHaveBeenCalled(); expect(updateSplashScreensSpy).toHaveBeenCalled(); expect(fakeParserConstructorSpy).toHaveBeenCalled(); expect(fakeConfigParserConstructorSpy).toHaveBeenCalled(); expect(fakeManifestJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalled(); expect(fakeSettingJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakeParserWriteSpy).toHaveBeenCalled(); expect(fakeConfigParserWriteSpy).toHaveBeenCalled(); const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'Generating defaults.xml'; expect(actual).toContain(expected); }); }); it('should hit case 3.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const defaultConfigPathMock = path.join(api.locations.platformRootDir, 'cordova', 'defaults.xml'); const ownConfigPathMock = api.locations.configXml; const sourceCfgMock = cordovaProject.projectConfig; const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (configPath) { return configPath !== ownConfigPathMock && configPath !== defaultConfigPathMock; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = () => undefined; prepare.prepare.call(api, cordovaProject, { }, api); expect(cpSyncSpy).toHaveBeenCalledWith(sourceCfgMock.path, ownConfigPathMock, { recursive: true }); expect(mergeXmlSpy).toHaveBeenCalled(); expect(updateIconsSpy).toHaveBeenCalled(); expect(updateSplashScreensSpy).toHaveBeenCalled(); expect(fakeParserConstructorSpy).toHaveBeenCalled(); expect(fakeConfigParserConstructorSpy).toHaveBeenCalled(); expect(fakeManifestJsonParserConfigureSpy).not.toHaveBeenCalled(); expect(fakePackageJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalled(); expect(fakeSettingJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakeParserWriteSpy).toHaveBeenCalled(); expect(fakeConfigParserWriteSpy).toHaveBeenCalled(); const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'case 3'; expect(actual).toContain(expected); }); }); it('should copy manifest.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const srcManifestPathMock = path.join(cordovaProject.locations.www, 'manifest.json'); const manifestPathMock = path.join(api.locations.www, 'manifest.json'); const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (srcManifestPath) { return srcManifestPath === srcManifestPathMock; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = () => undefined; prepare.prepare.call(api, cordovaProject, { }, api); expect(cpSyncSpy).toHaveBeenCalledWith(srcManifestPathMock, manifestPathMock, { recursive: true }); expect(mergeXmlSpy).toHaveBeenCalled(); expect(updateIconsSpy).toHaveBeenCalled(); expect(updateSplashScreensSpy).toHaveBeenCalled(); expect(fakeParserConstructorSpy).toHaveBeenCalled(); expect(fakeConfigParserConstructorSpy).toHaveBeenCalled(); expect(fakeManifestJsonParserConfigureSpy).not.toHaveBeenCalled(); expect(fakePackageJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalled(); expect(fakeSettingJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakeParserWriteSpy).toHaveBeenCalled(); expect(fakeConfigParserWriteSpy).toHaveBeenCalled(); const actual = emitSpy.calls.argsFor(1)[1]; const expected = 'Copying'; expect(actual).toContain(expected); }); }); it('should create new manifest file.', () => { // Mocking the scope with dummy API; return Promise.resolve().then(function () { // Create API instance and mock for test case. api.events = { emit: emitSpy }; api.parser.update_www = () => this; api.parser.update_project = () => this; const srcManifestPathMock = path.join(cordovaProject.locations.www, 'manifest.json'); const cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { existsSync: function (srcManifestPath) { return srcManifestPath !== srcManifestPathMock; }, cpSync: cpSyncSpy, readFileSync: function (filePath) { if (filePath === path.join('MOCK_PROJECT_ROOT', 'package.json')) { return defaultMockProjectPackageJson; } } }); // override classes and methods called in modules.export.prepare prepare.__set__('ConfigParser', FakeConfigParser); prepare.__set__('xmlHelpers', xmlHelpersMock); prepare.__set__('updateIcons', updateIconsFake); prepare.__set__('updateSplashScreens', updateSplashScreensFake); prepare.__set__('ManifestJsonParser', FakeManifestJsonParser); prepare.__set__('PackageJsonParser', FakePackageJsonParser); prepare.__set__('SettingJsonParser', FakeSettingJsonParser); cordovaProject.projectConfig.getPlatformPreference = () => undefined; prepare.prepare.call(api, cordovaProject, { }, api); expect(mergeXmlSpy).toHaveBeenCalled(); expect(updateIconsSpy).toHaveBeenCalled(); expect(updateSplashScreensSpy).toHaveBeenCalled(); expect(fakeParserConstructorSpy).toHaveBeenCalled(); expect(fakeConfigParserConstructorSpy).toHaveBeenCalled(); expect(fakeManifestJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakePackageJsonParserEnableDevToolsSpy).toHaveBeenCalled(); expect(fakeSettingJsonParserConfigureSpy).toHaveBeenCalled(); expect(fakeParserWriteSpy).toHaveBeenCalled(); expect(fakeConfigParserWriteSpy).toHaveBeenCalled(); const actual = emitSpy.calls.argsFor(1)[1]; const expected = 'Creating'; expect(actual).toContain(expected); }); }); }); describe('updateSplashScreens method', () => { let cordovaProject; let config; let locations; beforeEach(() => { createSpies(); cordovaProject = Object.assign({}, cordovaProjectDefault); config = Object.assign({}, cordovaProjectDefault.projectConfig); locations = Object.assign({}, locationsDefault); }); it('should detect no defined splash screens.', () => { const updateSplashScreens = prepare.__get__('updateSplashScreens'); cordovaProject.projectConfig.getSplashScreens = () => []; updateSplashScreens(cordovaProject, config, locations); // The emit was called expect(emitSpy).toHaveBeenCalled(); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'This app does not have splash screens defined.'; expect(actual).toEqual(expected); }); it('should update splashScreen.', () => { const updateSplashScreens = prepare.__get__('updateSplashScreens'); // create spies const prepareSplashScreensSpy = jasmine.createSpy('prepareSplashScreens'); prepare.__set__('prepareSplashScreens', prepareSplashScreensSpy); const createResourceMapSpy = jasmine.createSpy('createResourceMap'); prepare.__set__('createResourceMap', createResourceMapSpy); const updatePathToSplashScreenSpy = jasmine.createSpy('updatePathToSplashScreen'); prepare.__set__('updatePathToSplashScreen', updatePathToSplashScreenSpy); const copyResourcesSpy = jasmine.createSpy('copyResources'); prepare.__set__('copyResources', copyResourcesSpy); cordovaProject.projectConfig.getSplashScreens = () => { const splashScreen = mockGetImageItem({}); return [splashScreen]; }; updateSplashScreens(cordovaProject, locations); // The emit was called expect(emitSpy).toHaveBeenCalled(); expect(prepareSplashScreensSpy).toHaveBeenCalled(); expect(createResourceMapSpy).toHaveBeenCalled(); expect(updatePathToSplashScreenSpy).toHaveBeenCalled(); expect(copyResourcesSpy).toHaveBeenCalled(); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'Updating splash screens'; expect(actual).toEqual(expected); }); }); describe('prepareSplashScreens method', () => { let prepareSplashScreens; beforeEach(() => { createSpies(); prepareSplashScreens = prepare.__get__('prepareSplashScreens'); }); it('should return object with splashScreen image, when there is only splashScreen image in res/electron folder.', () => { const splash = mockGetImageItem({ src: path.join('res', 'electron', 'splash.png'), platform: 'electron' }); const actual = prepareSplashScreens([splash]); const expected = { splashScreen: Object.assign(splash, { extension: '.png' }) }; expect(expected).toEqual(actual); }); it('should return object with the 2nd splashScreen image, when there two splashScreen images in res/electron folder.', () => { const splash = mockGetImageItem({ src: path.join('res', 'electron', 'splash.png'), platform: 'electron' }); const splash2 = mockGetImageItem({ src: path.join('res', 'electron', 'splash2.png'), platform: 'electron' }); let actual = prepareSplashScreens([splash, splash2]); let expected = { splashScreen: Object.assign(splash2, { extension: '.png' }) }; expect(expected).toEqual(actual); // The emit was called expect(emitSpy).toHaveBeenCalled(); // The emit message was. actual = emitSpy.calls.argsFor(0)[1]; expected = `Found extra splash screen image: ${path.join('res', 'electron', 'splash.png')} and ignoring in favor of ${path.join('res', 'electron', 'splash2.png')}.`; expect(actual).toEqual(expected); }); }); describe('updatePathToSplashScreen method', () => { let ConfigParser; let locations; let updatePathToSplashScreen; beforeEach(() => { createSpies(); ConfigParser = Object.assign({}, cordovaProjectDefault.projectConfig); locations = Object.assign({}, locationsDefault); updatePathToSplashScreen = prepare.__get__('updatePathToSplashScreen'); }); it('should update splash screen location in config.xml', () => { const resourceMap = [ { [path.join('res', 'electron', 'splash.png')]: path.join('MOCK_PROJECT_ROOT', 'www', '.cdv', 'splashScreen.png') } ]; updatePathToSplashScreen(ConfigParser, locations, resourceMap); const elementKeys = Object.keys(resourceMap[0]); const splashScreenPath = resourceMap[0][elementKeys]; const splashScreenRelativePath = path.relative(locations.www, splashScreenPath); expect(path.join('MOCK_PROJECT_ROOT', 'www', '.cdv', 'splashScreen.png')).toEqual(splashScreenPath); expect(path.join('.cdv', 'splashScreen.png')).toEqual(splashScreenRelativePath); }); }); describe('updateIcons method', () => { let cordovaProject; let locations; beforeEach(() => { createSpies(); cordovaProject = Object.assign({}, cordovaProjectDefault); locations = Object.assign({}, locationsDefault); }); it('should detect no defined icons.', () => { const updateIcons = prepare.__get__('updateIcons'); cordovaProject.projectConfig.getIcons = () => []; updateIcons(cordovaProject, locations); // The emit was called expect(emitSpy).toHaveBeenCalled(); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'This app does not have icons defined'; expect(actual).toEqual(expected); }); it('should not update icons when required attributes are undefined.', () => { const updateIcons = prepare.__get__('updateIcons'); // create spies const prepareIconsSpy = jasmine.createSpy('prepareIcons'); prepare.__set__('prepareIcons', prepareIconsSpy); const createResourceMapSpy = jasmine.createSpy('createResourceMap'); prepare.__set__('createResourceMap', createResourceMapSpy); const copyResourcesSpy = jasmine.createSpy('copyResources'); prepare.__set__('copyResources', copyResourcesSpy); cordovaProject.projectConfig.getIcons = () => { const icon = mockGetImageItem({}); return [icon]; }; expect(() => { updateIcons(cordovaProject, locations); }).toThrow( new CordovaError('No icon match the required size. Please ensure that ".png" icon is at least 512x512 and has a src attribute.') ); expect(emitSpy).toHaveBeenCalled(); expect(prepareIconsSpy).not.toHaveBeenCalled(); expect(createResourceMapSpy).not.toHaveBeenCalled(); expect(copyResourcesSpy).not.toHaveBeenCalled(); }); it('should update icons when required attributes are defined.', () => { const updateIcons = prepare.__get__('updateIcons'); // create spies const prepareIconsSpy = jasmine.createSpy('prepareIcons'); prepare.__set__('prepareIcons', prepareIconsSpy); const createResourceMapSpy = jasmine.createSpy('createResourceMap'); prepare.__set__('createResourceMap', createResourceMapSpy); const copyResourcesSpy = jasmine.createSpy('copyResources'); prepare.__set__('copyResources', copyResourcesSpy); cordovaProject.projectConfig.getIcons = () => { const icon = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png') }); return [icon]; }; updateIcons(cordovaProject, locations); expect(prepareIconsSpy).toHaveBeenCalled(); expect(createResourceMapSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled(); expect(copyResourcesSpy).toHaveBeenCalled(); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'Updating icons'; expect(actual).toEqual(expected); }); }); describe('checkIconsAttributes method', () => { let checkIconsAttributes; beforeEach(() => { createSpies(); checkIconsAttributes = prepare.__get__('checkIconsAttributes'); }); it('should not emit info message.', () => { const icons = [ mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), width: 512, height: 512 }) ]; icons.forEach(icon => { checkIconsAttributes(icon); expect(emitSpy).not.toHaveBeenCalled(); }); }); it('should detect icons with missing src and emit info message.', () => { const icons = [ mockGetImageItem({ src: '' }) ]; icons.forEach(icon => { checkIconsAttributes(icon); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'The following icon with a size of width=undefined height=undefined does not meet the requirements and will be ignored.'; expect(actual).toEqual(expected); }); }); it('should detect icons with missing src, but defined size and emit info message.', () => { const icons = [ mockGetImageItem({ src: '', width: 512, height: 512 }) ]; icons.forEach(icon => { checkIconsAttributes(icon); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'The following icon with a size of width=512 height=512 does not meet the requirements and will be ignored.'; expect(actual).toEqual(expected); }); }); it('should detect icons with target set, but missing src and emit info message.', () => { const icons = [ mockGetImageItem({ src: '', target: 'installer' }) ]; icons.forEach(icon => { checkIconsAttributes(icon); // The emit message was const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'The following installer icon with a size of width=undefined height=undefined does not meet the requirements and will be ignored.'; expect(actual).toEqual(expected); }); }); it('should detect icons with wrong size defined and emit info message.', () => { const icons = [ mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), height: 512, width: 256 }) ]; icons.forEach(icon => { checkIconsAttributes(icon); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'The following icon with a size of width=256 height=512 does not meet the requirements and will be ignored.'; expect(actual).toEqual(expected); }); }); it('should detect icons with wrong size defined for the installer and emit info message.', () => { const icons = [ mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', height: 256, width: 256 }) ]; icons.forEach(icon => { checkIconsAttributes(icon); // The emit message was. const actual = emitSpy.calls.argsFor(0)[1]; const expected = 'The following installer icon with a size of width=256 height=256 does not meet the requirements and will be ignored.'; expect(actual).toEqual(expected); }); }); }); describe('prepareIcons method', () => { let prepareIcons; beforeEach(() => { createSpies(); prepareIcons = prepare.__get__('prepareIcons'); }); it('should return array of objects with custom icon, when there is only one icon in res folder.', () => { const icons = mockGetImageItem({ src: path.join('res', 'logo.png'), platform: undefined }); const actual = prepareIcons([icons]); const expected = { customIcon: Object.assign(icons, { extension: '.png' }), appIcon: undefined, installerIcon: undefined, highResIcons: [] }; expect(expected).toEqual(actual); }); it('should return array of objects with custom icon, when there is only one icon in res/electron folder.', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'logo.png') }); const actual = prepareIcons([icons]); const expected = { customIcon: Object.assign(icons, { extension: '.png' }), appIcon: undefined, installerIcon: undefined, highResIcons: [] }; expect(expected).toEqual(actual); }); it('should return array of objects with custom icons, when there is only one icon with correct width and height set.', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), width: 512, height: 512 }); const actual = prepareIcons([icons]); const expected = { customIcon: Object.assign(icons, { extension: '.png' }), appIcon: undefined, installerIcon: undefined, highResIcons: [] }; expect(expected).toEqual(actual); }); it('should return array of objects with custom icons, when there is two icons with wrong width and height set.', () => { const icon1 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png'), width: 512, height: 512 }); const icon2 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_extra.png'), width: 512, height: 512 }); let actual = prepareIcons([icon1, icon2]); let expected = { customIcon: icon2, appIcon: undefined, installerIcon: undefined, highResIcons: [] }; expect(expected).toEqual(actual); // The emit was called expect(emitSpy).toHaveBeenCalled(); // The emit message was. actual = emitSpy.calls.argsFor(0)[1]; expected = `Found extra icon for target undefined: ${path.join('res', 'electron', 'cordova.png')} and ignoring in favor of ${path.join('res', 'electron', 'cordova_extra.png')}.`; expect(actual).toEqual(expected); }); it('should return array of objects with custom icons, when there is only one icon with wrong width and height set.', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), width: 500, height: 500 }); const actual = prepareIcons([icons]); const expected = { customIcon: undefined, appIcon: undefined, installerIcon: undefined, highResIcons: [] }; expect(expected).toEqual(actual); }); it('should return array of objects with installer icon, when icon is defined for target=installer', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512 }); const actual = prepareIcons([icons]); const expected = { customIcon: undefined, appIcon: undefined, installerIcon: Object.assign(icons, { extension: '.png' }), highResIcons: [] }; expect(expected).toEqual(actual); }); it('should return array of objects with app and installer icon, when there is one icon with target=app and one with target=installer', () => { const app = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png'), target: 'app', width: 512, height: 512 }); const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const icons = [app, installer]; const actual = prepareIcons(icons); const expected = { customIcon: undefined, appIcon: Object.assign(app, { extension: '.png' }), installerIcon: Object.assign(installer, { extension: '.png' }), highResIcons: [] }; expect(expected).toEqual(actual); }); it('should return array of objects with app and installer icon, when there more one icon with target=app and more than one with target=installer', () => { const app1 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png'), target: 'app', width: 512, height: 512 }); const app2 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_extra.png'), target: 'app', width: 512, height: 512 }); const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const installer2 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512_extra.png'), target: 'installer', width: 512, height: 512 }); const icons = [app1, app2, installer, installer2]; let actual = prepareIcons(icons); let expected = { customIcon: undefined, appIcon: Object.assign(app2, { extension: '.png' }), installerIcon: Object.assign(installer2, { extension: '.png' }), highResIcons: [] }; expect(expected).toEqual(actual); // The emit was called expect(emitSpy).toHaveBeenCalled(); // The emit message was. actual = emitSpy.calls.argsFor(0)[1]; expected = `Found extra icon for target app: ${path.join('res', 'electron', 'cordova.png')} and ignoring in favor of ${path.join('res', 'electron', 'cordova_extra.png')}.`; expect(actual).toEqual(expected); actual = emitSpy.calls.argsFor(1)[1]; expected = `Found extra icon for target installer: ${path.join('res', 'electron', 'cordova_512.png')} and ignoring in favor of ${path.join('res', 'electron', 'cordova_512_extra.png')}.`; expect(actual).toEqual(expected); }); it('should return array of objects with high resolution icons, if they are defined', () => { const highRes10 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png') }); const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png') }); const icons = [highRes10, highRes15, highRes20, highRes40, highRes80]; const actual = prepareIcons(icons); const expected = { customIcon: undefined, appIcon: undefined, installerIcon: undefined, highResIcons: [ Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }), Object.assign(highRes20, { suffix: '2x', extension: '.png' }), Object.assign(highRes40, { suffix: '4x', extension: '.png' }), Object.assign(highRes80, { suffix: '8x', extension: '.png' }), Object.assign(highRes10, { suffix: '1x', extension: '.png' }) ] }; expect(expected).toEqual(actual); }); it('should return array of objects with high resolution icons, if they are defined and an extra icon with target=installer', () => { const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const highRes10 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png') }); const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png') }); const icons = [installer, highRes10, highRes15, highRes20, highRes40, highRes80]; const actual = prepareIcons(icons); const expected = { customIcon: undefined, appIcon: undefined, installerIcon: Object.assign(installer, { extension: '.png' }), highResIcons: [ Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }), Object.assign(highRes20, { suffix: '2x', extension: '.png' }), Object.assign(highRes40, { suffix: '4x', extension: '.png' }), Object.assign(highRes80, { suffix: '8x', extension: '.png' }), Object.assign(highRes10, { suffix: '1x', extension: '.png' }) ] }; expect(expected).toEqual(actual); }); }); describe('findHighResIcons', () => { let findHighResIcons; beforeEach(() => { createSpies(); findHighResIcons = prepare.__get__('findHighResIcons'); }); it('should return array of objects with remaining icons, when there is only one icon in res folder.', () => { const icons = mockGetImageItem({ src: path.join('res', 'logo.png'), platform: undefined }); const actual = findHighResIcons([icons]); const expected = { highResIcons: [], remainingIcons: [icons] }; expect(expected).toEqual(actual); }); it('should return array of objects with remaining icons, when there is only one icon in res/electron folder.', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'logo.png') }); const actual = findHighResIcons([icons]); const expected = { highResIcons: [], remainingIcons: [icons] }; expect(expected).toEqual(actual); }); it('should return array of objects with remaining icon, when there is only one icon with correct width and height set.', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), width: 512, height: 512 }); const actual = findHighResIcons([icons]); const expected = { highResIcons: [], remainingIcons: [icons] }; expect(expected).toEqual(actual); }); it('should return array of objects with remaining icon, when icon is defined for target=installer', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512 }); const actual = findHighResIcons([icons]); const expected = { highResIcons: [], remainingIcons: [icons] }; expect(expected).toEqual(actual); }); it('should return array of objects with app and installer icon, when there is one icon with target=app and one with target=installer', () => { const app = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png'), target: 'app', width: 512, height: 512 }); const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const icons = [app, installer]; const actual = findHighResIcons(icons); const expected = { highResIcons: [], remainingIcons: icons }; expect(expected).toEqual(actual); }); it('should return remainingIcons array of objects with app and installer icon, when there more one icon with target=app and more than one with target=installer', () => { const app = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png'), target: 'app', width: 512, height: 512 }); const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const installer2 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512_extra.png'), target: 'installer', width: 512, height: 512 }); const icons = [app, installer, installer2]; const actual = findHighResIcons(icons); const expected = { highResIcons: [], remainingIcons: icons }; expect(expected).toEqual(actual); }); it('should throw Cordova Error when there is no base icon', () => { const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png') }); const icons = [highRes15, highRes20, highRes40, highRes80]; expect(() => { findHighResIcons(icons); }).toThrow( new CordovaError('Base icon for high resolution images was not found.') ); }); it('should return array of objects with high resolution icons, if they are defined', () => { const highRes10 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png') }); const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png') }); const icons = [highRes10, highRes15, highRes20, highRes40, highRes80]; const actual = findHighResIcons(icons); const expected = { highResIcons: [ Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }), Object.assign(highRes20, { suffix: '2x', extension: '.png' }), Object.assign(highRes40, { suffix: '4x', extension: '.png' }), Object.assign(highRes80, { suffix: '8x', extension: '.png' }), Object.assign(highRes10, { suffix: '1x', extension: '.png' }) ], remainingIcons: [] }; expect(expected).toEqual(actual); }); it('should return array of objects with high resolution icons, if they are defined and an extra icon with target=installer', () => { const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const highRes10 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png') }); const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png') }); const icons = [installer, highRes10, highRes15, highRes20, highRes40, highRes80]; const actual = findHighResIcons(icons); const expected = { highResIcons: [ Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }), Object.assign(highRes20, { suffix: '2x', extension: '.png' }), Object.assign(highRes40, { suffix: '4x', extension: '.png' }), Object.assign(highRes80, { suffix: '8x', extension: '.png' }), Object.assign(highRes10, { suffix: '1x', extension: '.png' }) ], remainingIcons: [installer] }; expect(expected).toEqual(actual); }); it('should return array of objects with high resolution icons, if they are defined and remaining icon with target=installer', () => { const highRes10 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png') }); const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png'), target: 'installer' }); const icons = [highRes10, highRes15, highRes20, highRes40, highRes80]; const actual = findHighResIcons(icons); const expected = { highResIcons: [ Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }), Object.assign(highRes20, { suffix: '2x', extension: '.png' }), Object.assign(highRes40, { suffix: '4x', extension: '.png' }), Object.assign(highRes80, { suffix: '8x', extension: '.png' }), Object.assign(highRes10, { suffix: '1x', extension: '.png' }) ], remainingIcons: [Object.assign(highRes80, { suffix: '8x', extension: '.png' })] }; expect(expected).toEqual(actual); }); }); describe('createResourceMap method', () => { let createResourceMap; let cordovaProject; let locations; beforeEach(() => { prepare = rewire(path.join(rootDir, 'lib/prepare')); cordovaProject = Object.assign({}, cordovaProjectDefault); locations = Object.assign({}, locationsDefault); createResourceMap = prepare.__get__('createResourceMap'); const existsSyncSpy = jasmine.createSpy('existsSyncSpy').and.returnValue(true); prepare.__set__('fs', { existsSync: existsSyncSpy }); }); it('should map custom icon to installer and app icon locations', () => { const icon = mockGetImageItem({ src: path.join('res', 'logo.png'), platform: undefined }); const data = { customIcon: Object.assign(icon, { extension: '.png' }), appIcon: undefined, installerIcon: undefined, highResIcons: [] }; const actual = createResourceMap(cordovaProject, locations, data); const expected = [ { [path.join('res', 'logo.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'app.png') }, { [path.join('res', 'logo.png')]: path.join('MOCK_PROJECT_ROOT', 'build-res', 'installer.png') } ]; expect(expected).toEqual(actual); }); it('should map installer icon to appoporiate location', () => { const icons = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512 }); const data = { customIcon: undefined, appIcon: undefined, installerIcon: Object.assign(icons, { extension: '.png' }), highResIcons: [] }; const actual = createResourceMap(cordovaProject, locations, data); const expected = [ { [path.join('res', 'electron', 'cordova_512.png')]: path.join('MOCK_PROJECT_ROOT', 'build-res', 'installer.png') } ]; expect(expected).toEqual(actual); }); it('should map installer and app icon to appoporiate location', () => { const app = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png'), target: 'app', width: 512, height: 512 }); const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const data = { customIcon: undefined, appIcon: Object.assign(app, { extension: '.png' }), installerIcon: Object.assign(installer, { extension: '.png' }), highResIcons: [] }; const actual = createResourceMap(cordovaProject, locations, data); const expected = [ { [path.join('res', 'electron', 'cordova.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'app.png') }, { [path.join('res', 'electron', 'cordova_512.png')]: path.join('MOCK_PROJECT_ROOT', 'build-res', 'installer.png') } ]; expect(expected).toEqual(actual); }); it('should map high resolution icons to appoporiate location', () => { const highRes10 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png') }); const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png') }); const data = { customIcon: undefined, appIcon: undefined, installerIcon: undefined, highResIcons: [ Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }), Object.assign(highRes20, { suffix: '2x', extension: '.png' }), Object.assign(highRes40, { suffix: '4x', extension: '.png' }), Object.assign(highRes80, { suffix: '8x', extension: '.png' }), Object.assign(highRes10, { suffix: '1x', extension: '.png' }) ] }; const actual = createResourceMap(cordovaProject, locations, data); const expected = [ { [path.join('res', 'electron', 'cordova@1.5x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@1.5x.png') }, { [path.join('res', 'electron', 'cordova@2x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@2x.png') }, { [path.join('res', 'electron', 'cordova@4x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@4x.png') }, { [path.join('res', 'electron', 'cordova@8x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@8x.png') }, { [path.join('res', 'electron', 'cordova.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon.png') } ]; expect(expected).toEqual(actual); }); it('should map high resolution icons and installer icon to appoporiate location', () => { const installer = mockGetImageItem({ src: path.join('res', 'electron', 'cordova_512.png'), target: 'installer', width: 512, height: 512 }); const highRes10 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova.png') }); const highRes15 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@1.5x.png') }); const highRes20 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@2x.png') }); const highRes40 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@4x.png') }); const highRes80 = mockGetImageItem({ src: path.join('res', 'electron', 'cordova@8x.png') }); const data = { customIcon: undefined, appIcon: undefined, installerIcon: Object.assign(installer, { extension: '.png' }), highResIcons: [ Object.assign(highRes15, { suffix: '1.5x', extension: '.png' }), Object.assign(highRes20, { suffix: '2x', extension: '.png' }), Object.assign(highRes40, { suffix: '4x', extension: '.png' }), Object.assign(highRes80, { suffix: '8x', extension: '.png' }), Object.assign(highRes10, { suffix: '1x', extension: '.png' }) ] }; const actual = createResourceMap(cordovaProject, locations, data); const expected = [ { [path.join('res', 'electron', 'cordova_512.png')]: path.join('MOCK_PROJECT_ROOT', 'build-res', 'installer.png') }, { [path.join('res', 'electron', 'cordova@1.5x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@1.5x.png') }, { [path.join('res', 'electron', 'cordova@2x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@2x.png') }, { [path.join('res', 'electron', 'cordova@4x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@4x.png') }, { [path.join('res', 'electron', 'cordova@8x.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon@8x.png') }, { [path.join('res', 'electron', 'cordova.png')]: path.join('MOCK_PROJECT_ROOT', 'www', 'img', 'icon.png') } ]; expect(expected).toEqual(actual); }); it('should map splashScreen images to the .cdv folder in the platform/www', () => { const icon = mockGetImageItem({ src: path.join('res', 'electron', 'splash.png'), platform: 'electron' }); const data = { splashScreen: Object.assign(icon, { extension: '.png' }) }; const actual = createResourceMap(cordovaProject, locations, data); const expected = [ { [path.join('res', 'electron', 'splash.png')]: path.join('MOCK_PROJECT_ROOT', 'www', '.cdv', 'splashScreen.png') } ]; expect(expected).toEqual(actual); }); }); describe('mapResources method', () => { let mapResources; let existsSyncSpy; let cordovaProject; beforeEach(() => { prepare = rewire(path.join(rootDir, 'lib/prepare')); cordovaProject = Object.assign({}, cordovaProjectDefault); mapResources = prepare.__get__('mapResources'); existsSyncSpy = jasmine.createSpy('existsSyncSpy').and.returnValue(true); prepare.__set__('fs', { existsSync: existsSyncSpy }); }); it('should return an empty object when the resource path does not exist.', () => { existsSyncSpy.and.returnValue(false); const resources = mapResources(cordovaProject.root, '', ''); expect(resources).toEqual({}); }); it('should map to file to file', () => { const sourcePath = path.join(cordovaProject.root, 'res', 'electron', 'cordova_512.png'); const targetPath = path.join(cordovaProject.root, 'www', 'img', 'icon.png'); const expected = {}; expected[sourcePath] = targetPath; const actual = mapResources(cordovaProject.root, sourcePath, targetPath); expect(existsSyncSpy).toHaveBeenCalled(); expect(expected).toEqual(actual); }); it('should map to folder to folder', () => { const sourcePath = path.join(cordovaProject.root, 'res', 'electron'); const targetPath = path.join(cordovaProject.root, 'www', 'img'); const expected = {}; expected[sourcePath] = targetPath; const actual = mapResources(cordovaProject.root, sourcePath, targetPath); expect(existsSyncSpy).toHaveBeenCalled(); expect(expected).toEqual(actual); }); }); describe('copyResources method', () => { let copyResources; let cpSyncSpy; let cordovaProject; beforeEach(() => { prepare = rewire(path.join(rootDir, 'lib/prepare')); cordovaProject = Object.assign({}, cordovaProjectDefault); copyResources = prepare.__get__('copyResources'); cpSyncSpy = jasmine.createSpy('cpSync'); prepare.__set__('fs', { cpSync: cpSyncSpy }); }); it('should not copy as no resources provided.', () => { copyResources(cordovaProject.root, [{}]); expect(cpSyncSpy).not.toHaveBeenCalled(); }); it('should copy provided resources.', () => { copyResources(cordovaProject.root, [ { [path.join('res', 'electron', 'cordova_512.png')]: path.join(cordovaProject.root, 'build-res', 'installer.png') }, { [path.join('res', 'electron', 'cordova.png')]: path.join(cordovaProject.root, 'www', 'img', 'icon.png') } ]); expect(cpSyncSpy).toHaveBeenCalled(); const installerIconSrcPathTest = cpSyncSpy.calls.argsFor(0)[0]; const installerIconDestPathTest = cpSyncSpy.calls.argsFor(0)[1]; expect(installerIconSrcPathTest).toBe(path.join(cordovaProject.root, 'res', 'electron', 'cordova_512.png')); expect(installerIconDestPathTest).toBe(path.join(cordovaProject.root, 'build-res', 'installer.png')); const appIconSrcPathTest = cpSyncSpy.calls.argsFor(1)[0]; const appIconDestPathTest = cpSyncSpy.calls.argsFor(1)[1]; expect(appIconSrcPathTest).toBe(path.join(cordovaProject.root, 'res', 'electron', 'cordova.png')); expect(appIconDestPathTest).toBe(path.join(cordovaProject.root, 'www', 'img', 'icon.png')); }); }); }); ================================================ FILE: tests/spec/unit/lib/run.spec.js ================================================ /* 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 rewire = require('rewire'); const path = require('node:path'); const rootDir = path.resolve(__dirname, '../../../..'); const apiStub = Object.freeze({ locations: Object.freeze({ www: 'FAKE_WWW' }) }); const expectedPathToMain = path.join(apiStub.locations.www, 'cdv-electron-main.js'); const expectedExecaOptions = { windowsHide: false, stdio: 'inherit' }; const run = rewire(path.join(rootDir, 'lib/run')); describe('Run', () => { describe('run export method', () => { it('should run electron with cdv-electron-main.js.', () => { const execaSpy = jasmine.createSpy('execa'); const onSpy = jasmine.createSpy('on'); run.__set__('electron', 'electron-require'); spyOn(process, 'exit'); run.__set__('execa', execaSpy.and.returnValue({ on: onSpy.and.callThrough() })); run.run.call(apiStub); expect(execaSpy).toHaveBeenCalledWith('electron-require', [expectedPathToMain], expectedExecaOptions); expect(onSpy).toHaveBeenCalled(); expect(process.exit).not.toHaveBeenCalled(); // trigger exist as if process was killed onSpy.calls.argsFor(0)[1](); expect(process.exit).toHaveBeenCalled(); }); it('should pass arguments to electron', () => { const execaSpy = jasmine.createSpy('execa'); const onSpy = jasmine.createSpy('on'); const expectedElectronArguments = [ '--inspect-brk=5858', expectedPathToMain ]; run.__set__('electron', 'electron-require'); spyOn(process, 'exit'); run.__set__('execa', execaSpy.and.returnValue({ on: onSpy.and.callThrough() })); run.run.call(apiStub, { argv: ['--inspect-brk=5858'] }); expect(execaSpy).toHaveBeenCalledWith('electron-require', expectedElectronArguments, expectedExecaOptions); expect(onSpy).toHaveBeenCalled(); expect(process.exit).not.toHaveBeenCalled(); // trigger exist as if process was killed onSpy.calls.argsFor(0)[1](); expect(process.exit).toHaveBeenCalled(); }); }); }); ================================================ FILE: tests/spec/unit/lib/util.spec.js ================================================ /* 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 path = require('node:path'); const rootDir = path.resolve(__dirname, '../../../..'); const util = require(path.join(rootDir, 'lib/util')); describe('Testing util.js:', () => { describe('deepMerge method', () => { it('should deep merge objects and arrays.', () => { const mergeTo = { foo: 'bar', abc: [1, 2, 3] }; const mergeWith = { food: 'candy', abc: [5] }; const actual = util.deepMerge(mergeTo, mergeWith); const expected = { foo: 'bar', food: 'candy', abc: [1, 2, 3, 5] }; expect(actual).toEqual(expected); }); it('should reject deep merge on reserved keys.', () => { const mergeTo = { foo: 'bar', abc: [1, 2, 3] }; const payload = '{"food":"candy","abc":[5],"__proto__":{ "hoge":"hoge"}}'; const actual = util.deepMerge(mergeTo, JSON.parse(payload)); const expected = { foo: 'bar', food: 'candy', abc: [1, 2, 3, 5] }; expect(actual).toEqual(expected); expect(actual.hoge).toBe(undefined); expect({}.hoge).toBe(undefined); }); }); describe('getInstalledElectronVersion method', () => { it('should have a version', () => { const actual = util.getInstalledElectronVersion(); expect(actual).not.toBe(null); expect(actual).not.toBe(undefined); }); }); }); ================================================ FILE: tests/spec/unit.json ================================================ { "spec_dir": "tests/spec", "spec_files": [ "unit/**/*[sS]pec.js" ], "stopSpecOnExpectationFailure": false, "random": false }