diff --git a/.circleci/config.yml b/.circleci/config.yml index ec972b1..d9cb6ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,13 +2,16 @@ version: 2 jobs: build: docker: - - image: circleci/node:10-browsers + - image: circleci/python:2.7-node-browsers steps: - checkout - run: name: Install dependencies command: | + pipenv install npm install + pipenv run mozdownload --version latest --destination $HOME/firefox.tar.bz2 + pipenv run mozinstall --destination $HOME $HOME/firefox.tar.bz2 - run: name: Build extension command: | @@ -17,9 +20,15 @@ jobs: name: Run linting checks command: | npm run lint + - run: + name: Run automated tests + command: | + pipenv run test -- --firefox_bin $HOME/firefox/firefox - run: name: Package extension as XPI command: | npm run package - store_artifacts: path: web-ext-artifacts + - store_artifacts: + path: gecko.log diff --git a/.gitignore b/.gitignore index ea9fc82..a26de97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules web-ext-artifacts build +gecko.log diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..2622af5 --- /dev/null +++ b/Pipfile @@ -0,0 +1,18 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[dev-packages] + +[packages] +click = "*" +marionette-client = "*" +mozdownload = "*" +mozinstall = "*" + +[requires] +python_version = "2.7" + +[scripts] +test = "npm test" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..454e8d3 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,251 @@ +{ + "_meta": { + "hash": { + "sha256": "5b6ba8e19281de5fdd31b2ced1fd9ece5591aacea4e432fe021c473d60657f61" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "2.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "blessings": { + "hashes": [ + "sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d", + "sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3", + "sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e" + ], + "version": "==1.7" + }, + "browsermob-proxy": { + "hashes": [ + "sha256:5f0e72767938d268999f1b56b0e8ff01cecd051bb868637ff550e25495cc840b", + "sha256:fb345bc2207fccdb8a584694c8d02d01c2cfc539c9d43bbed38f0c54e1abbbaf" + ], + "markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version < '4' and python_version >= '2.6'", + "version": "==0.8.0" + }, + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "index": "pypi", + "version": "==6.7" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "manifestparser": { + "hashes": [ + "sha256:adb0c58b8811711fc2220cb23620b7cd1fb7f61124cc61e2e54c592f90919619" + ], + "version": "==1.1" + }, + "marionette-client": { + "hashes": [ + "sha256:6bf104cfe75c1b3a8dfcfb6dfdd35427e96b3c31213eae6b34e5a670e695624e", + "sha256:d4ee1a2848215a42a12e99f5994a6a6da1aef6460146c142e22dfd3deb7f0291" + ], + "index": "pypi", + "version": "==3.3.0" + }, + "marionette-driver": { + "hashes": [ + "sha256:15c77ba548847dc05ce1b663a22c3324623f217dce5a859c3aaced31fd16707b", + "sha256:4b550e719f3d8245a51330bb4039a6f1972db9308a5707cac82a28266cdb3459" + ], + "version": "==2.7.0" + }, + "mohawk": { + "hashes": [ + "sha256:b3f85ffa93a5c7d2f9cc591246ef9f8ac4a9fa716bfd5bae0377699a2d89d78c", + "sha256:e98b331d9fa9ece7b8be26094cbe2d57613ae882133cc755167268a984bc0ab3" + ], + "version": "==0.3.4" + }, + "mozcrash": { + "hashes": [ + "sha256:3eb6568674056f58029bdc1dbfaa1ed53c3608b09d904466e6d697bbc1f8bc72", + "sha256:63c61f720beb045cd3ac668152cddfe96b25827cafd6bc98d9144bb1cd0e2008" + ], + "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==1.0" + }, + "mozdevice": { + "hashes": [ + "sha256:c3c50219127e36c171f84b2427e8fcf3a85457f336559aead9770b2a27fbc4b6" + ], + "version": "==1.0.0" + }, + "mozdownload": { + "hashes": [ + "sha256:5ca97e858e6fc0a37188ff72d0029e8b372e4b8d41181833281f718fa62c1ea9", + "sha256:efdcbc7e84b4a292c855c2753c659b19d018e3e2669b1f538e09b9c6f9a80f9b" + ], + "index": "pypi", + "version": "==1.23" + }, + "mozfile": { + "hashes": [ + "sha256:d3b00f336c6a89449bd78dd3ae65d74eb98497438d1ccfec07af0a736d20e957" + ], + "version": "==1.2" + }, + "mozinfo": { + "hashes": [ + "sha256:dcd53a1b1793340418e1ae42bf300e3e56d8f12047972378c6f9318b220b1023" + ], + "version": "==0.10" + }, + "mozinstall": { + "hashes": [ + "sha256:034e1e21cf5504324c74cab62944b25cedc7d2650054ec94f537055d6f581d7d", + "sha256:8a27c03a98f88f3998d5ba910b61b7f44f7aa43113f60e5b76bf4a860548cb89" + ], + "index": "pypi", + "version": "==1.16.0" + }, + "mozlog": { + "hashes": [ + "sha256:3c4386e6d07beca9f64ac176728205d69e822b3f19acccec8c52531ee1f9d957", + "sha256:a05718e9eeff693bc0509f6c66bd1fc68a5f14744bc7b2a67a06f0d8cc47a203" + ], + "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==3.8" + }, + "moznetwork": { + "hashes": [ + "sha256:5ac0d56cc46d7a080e4ff0742238360f72f43eb52191319df1b4021779e9d727" + ], + "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==0.27" + }, + "mozprocess": { + "hashes": [ + "sha256:9f3d533439f7721c90547bee0ecdf421c838e5976673430f6ea37db7a789b363", + "sha256:9f471c45bee9ff14e936c6ee216a6cc4941223659c01fa626bce628001d8485c" + ], + "version": "==0.26" + }, + "mozprofile": { + "hashes": [ + "sha256:25ffe505b9839ec8d044ddf7d829c21ad8618d1ae0e1e3a7e1278b3c5d1f1bb2", + "sha256:aa7fe7248719a224dd63cdc0498c9971d07cfc62fee7a69f51d593316b6bc1d8" + ], + "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==1.1.0" + }, + "mozrunner": { + "hashes": [ + "sha256:a4e3d532d1d7eb34b6332e0568612ebac3f56da849894005b3dbe68968e5d292" + ], + "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==7.0.1" + }, + "mozterm": { + "hashes": [ + "sha256:b1e91acec188de07c704dbb7b0100a7be5c1e06567b3beb67f6ea11d00a483a4", + "sha256:f5eafa25c23d391e2a2bb1dd45ee928fc9e3c811977a3856b5a5a0778011053c" + ], + "version": "==1.0.0" + }, + "moztest": { + "hashes": [ + "sha256:5ab589a30f24f608cd3ea31471bc315b3cf5037ad82f2cc324c1da4e30bee9dd", + "sha256:d4d154fa2fb7864ec27cf8026534c6c3df4bb218cd105f6382d63c2870368077" + ], + "version": "==0.8" + }, + "mozversion": { + "hashes": [ + "sha256:e4a8aad0a4748c8b4f1990ec52837d79be8564f30d097a10242d5a08c6b10206", + "sha256:e9b11e4a46bf7a4a11469ea4589c75f3ba50b34b7801e7edf1a09147af8bf70f" + ], + "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*'", + "version": "==1.5" + }, + "progressbar": { + "hashes": [ + "sha256:b2d38a729785149e65323381d2e6fca0a5e9615a6d8bcf10bfa8adedfc481254" + ], + "version": "==2.3" + }, + "redo": { + "hashes": [ + "sha256:69ea97e4d934806475fe86f93e4f74da2994acab20c2e3cfe0d3ca6380e3f907" + ], + "version": "==1.6" + }, + "requests": { + "hashes": [ + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + ], + "markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version < '4' and python_version >= '2.6'", + "version": "==2.19.1" + }, + "requests-hawk": { + "hashes": [ + "sha256:aef0dff8053dcae2057774516386bed0a3bc03fabea9e18f3aa98f02672ea5d0", + "sha256:c2626ab31ebef0c81b97781c44c2275bfcc6d8e8520fc4ced495f0f386f8fe26" + ], + "markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version < '4' and python_version >= '2.6'", + "version": "==1.0.0" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "treeherder-client": { + "hashes": [ + "sha256:84690096902891bef2d9b555352f791510f9a280a76c8cf76a2730deccf8dc4b", + "sha256:e4221b867621afedcd88d076d8652435a8b62d5b0d1c0149afddce4516c2f0c4" + ], + "version": "==4.0.0" + }, + "urllib3": { + "hashes": [ + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + ], + "markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version < '4' and python_version >= '2.6'", + "version": "==1.23" + }, + "wptserve": { + "hashes": [ + "sha256:ef6aa5ca7c7cc802129eefab773172fac49281e50b34352a311d06426f237866" + ], + "version": "==1.4.0" + } + }, + "develop": {} +} diff --git a/README.md b/README.md index e6ebe8e..0ee9492 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,35 @@ Prerequisites: npm start ``` -## NPM Scripts +## Running Tests + +Automated tests are run in a Firefox browser instance using [Marionette][]. We use the Python client for Marionette since there is no up-to-date JavaScript client. + +To set up your environment for running the tests, you must have: + +- A Firefox binary. On MacOS, this can be found within the `.app` folder at `Firefox.app/Contents/MacOS/firefox`. +- Python 2.7 +- [Pipenv][] + +With these installed, you can set up the test suite: + +1. Install Python dependencies: + + ```sh + pipenv install + ``` +2. Save the path to your Firefox binary with `npm`: + + ```sh + npm config set webext-commerce:firefox_bin + ``` + +After this, you can run `pipenv run test` to run the automated test suite. + +[Marionette]: https://firefox-source-docs.mozilla.org/testing/marionette/marionette/index.html +[Pipenv]: https://docs.pipenv.org/ + +## Scripts | Command | Description | | --- | --- | @@ -39,6 +67,7 @@ Prerequisites: | `npm run build` | Compile source files with Webpack | | `npm run watch` | Watch for changes and rebuild | | `npm run package` | Package the extension into an XPI file | +| `pipenv run test` | Run test suite (See "Running Tests" for setup) | ## License diff --git a/bin/run_tests.py b/bin/run_tests.py new file mode 100755 index 0000000..1770779 --- /dev/null +++ b/bin/run_tests.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Runs automated JavaScript tests by launching Firefox in headless mode +and using the Marionette protocol to run the test code in Firefox. + +This script relies on path modifications from npm and should be run +using the ``npm run test`` command. +""" + +import os +import sys +from subprocess import check_call, PIPE, Popen, STDOUT +from tempfile import mkstemp + +import click +from marionette_driver.marionette import Marionette + + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + + +@click.command() +@click.option( + '--firefox_bin', + required=True, + envvar='npm_package_config_firefox_bin', + help='Path to Firefox binary', +) +def main(firefox_bin): + if not firefox_bin: + raise click.BadParameter( + 'No Firefox binary found; configure the path to Firefox with `npm config`.' + ) + elif not os.path.exists(firefox_bin): + raise click.BadParameter('Path to Firefox binary does not exist.') + + click.echo('== Building test bundle with Webpack') + bundle_handle, bundle_path = mkstemp() + try: + webpack_config_path = os.path.join(ROOT, 'webpack.config.test.js') + check_call([ + 'webpack', + '--bail', + '--config', webpack_config_path, + '--output', bundle_path, + ]) + with open(bundle_path) as f: + test_code = f.read() + finally: + os.remove(bundle_path) + + click.echo('== Running tests') + client = None + try: + client = Marionette(bin=firefox_bin, headless=True) + client.start_session() + results = client.execute_async_script(test_code) + finally: + if client: + client.cleanup() + + # Pipe output through formatter to make it readable. + reporter_env = os.environ.copy() + reporter_env.setdefault('TAP_COLORS', '1') # Support color outpput + reporter = Popen( + ['tap-mocha-reporter', 'spec'], + stdout=PIPE, + stderr=STDOUT, + stdin=PIPE, + env=reporter_env, + ) + formatted_output, stderr = reporter.communicate(results['output']) + click.echo(formatted_output) + + # Exit with an error code if there were any failures + if results['failures'] > 0: + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/package-lock.json b/package-lock.json index 1b8aeda..37e2da2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "moz-commerce", + "name": "webext-commerce", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -2492,6 +2492,12 @@ "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", "dev": true }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, "colors": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", @@ -3077,6 +3083,12 @@ } } }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, "degenerator": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", @@ -3164,6 +3176,12 @@ "repeating": "^2.0.0" } }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -3185,6 +3203,12 @@ "path-type": "^3.0.0" } }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", + "dev": true + }, "dispensary": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/dispensary/-/dispensary-0.18.0.tgz", @@ -3473,6 +3497,55 @@ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, + "enzyme": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.3.0.tgz", + "integrity": "sha512-l8csyPyLmtxskTz6pX9W8eDOyH1ckEtDttXk/vlFWCjv00SkjTjtoUrogqp4yEvMyneU9dUJoOLnqFoiHb8IHA==", + "dev": true, + "requires": { + "cheerio": "^1.0.0-rc.2", + "function.prototype.name": "^1.0.3", + "has": "^1.0.1", + "is-boolean-object": "^1.0.0", + "is-callable": "^1.1.3", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-subset": "^0.1.1", + "lodash": "^4.17.4", + "object-inspect": "^1.5.0", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4", + "object.values": "^1.0.4", + "raf": "^3.4.0", + "rst-selector-parser": "^2.2.3" + } + }, + "enzyme-adapter-react-16": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz", + "integrity": "sha512-kC8pAtU2Jk3OJ0EG8Y2813dg9Ol0TXi7UNxHzHiWs30Jo/hj7alc//G1YpKUsPP1oKl9X+Lkx+WlGJpPYA+nvw==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.3.0", + "lodash": "^4.17.4", + "object.assign": "^4.0.4", + "object.values": "^1.0.4", + "prop-types": "^15.6.0", + "react-reconciler": "^0.7.0", + "react-test-renderer": "^16.0.0-0" + } + }, + "enzyme-adapter-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.4.0.tgz", + "integrity": "sha512-ajvyXQYbmCoKCX/FaraNzBgXDXJBltCd0GdXfKc0DdRPYgCLaZfS6Ts576IFt8aX2GU9ajZv2g5jfcJ+Nttejw==", + "dev": true, + "requires": { + "object.assign": "^4.1.0", + "prop-types": "^15.6.0" + } + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -4248,6 +4321,12 @@ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, + "events-to-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", + "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=", + "dev": true + }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -4755,6 +4834,15 @@ "readable-stream": "^2.0.4" } }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -5418,6 +5506,17 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "function.prototype.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz", + "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "is-callable": "^1.1.3" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -6263,6 +6362,12 @@ "binary-extensions": "^1.0.0" } }, + "is-boolean-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", + "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", + "dev": true + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -6459,6 +6564,12 @@ } } }, + "is-number-object": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", + "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", + "dev": true + }, "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", @@ -6572,6 +6683,18 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, + "is-string": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", + "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", + "dev": true + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, "is-supported-regexp-flag": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", @@ -7005,6 +7128,12 @@ "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -7673,6 +7802,12 @@ "dev": true, "optional": true }, + "moo": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", + "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", + "dev": true + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -7880,6 +8015,37 @@ "dev": true, "optional": true }, + "nearley": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.15.0.tgz", + "integrity": "sha512-ZjzdO+yBtMrRrBbr+BJ35ECla6PGCAb/6hqpBQe7bmhEJabQ4rpVdj4sadP1Z1jQGyaDmm1GciQWsGVxIZ3uJA==", + "dev": true, + "requires": { + "moo": "^0.4.3", + "nomnom": "~1.6.2", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6", + "semver": "^5.4.1" + }, + "dependencies": { + "nomnom": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", + "integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=", + "dev": true, + "requires": { + "colors": "0.5.x", + "underscore": "~1.4.4" + } + }, + "underscore": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", + "dev": true + } + } + }, "needle": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", @@ -8059,6 +8225,12 @@ "boolbase": "~1.0.0" } }, + "null-loader": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-0.1.1.tgz", + "integrity": "sha1-F76av80/8OFRL2/Er8sfUDk3j64=", + "dev": true + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -8113,6 +8285,18 @@ } } }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "dev": true + }, "object-keys": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", @@ -9225,6 +9409,31 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, + "raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", + "dev": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", + "dev": true + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, "randomatic": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", @@ -9317,6 +9526,36 @@ "prop-types": "^15.6.0" } }, + "react-is": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.4.1.tgz", + "integrity": "sha512-xpb0PpALlFWNw/q13A+1aHeyJyLYCg0/cCHPUA43zYluZuIPHaHL3k8OBsTgQtxqW0FhyDEMvi8fZ/+7+r4OSQ==", + "dev": true + }, + "react-reconciler": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.7.0.tgz", + "integrity": "sha512-50JwZ3yNyMS8fchN+jjWEJOH3Oze7UmhxeoJLn2j6f3NjpfCRbcmih83XTWmzqtar/ivd5f7tvQhvvhism2fgg==", + "dev": true, + "requires": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + } + }, + "react-test-renderer": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.4.1.tgz", + "integrity": "sha512-wyyiPxRZOTpKnNIgUBOB6xPLTpIzwcQMIURhZvzUqZzezvHjaGNsDPBhMac5fIY3Jf5NuKxoGvV64zDSOECPPQ==", + "dev": true, + "requires": { + "fbjs": "^0.8.16", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0", + "react-is": "^16.4.1" + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -9727,6 +9966,15 @@ "signal-exit": "^3.0.2" } }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "dev": true, + "requires": { + "through": "~2.3.4" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -9752,6 +10000,16 @@ "inherits": "^2.0.1" } }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "dev": true, + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -10951,6 +11209,17 @@ } } }, + "string.prototype.trim": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.0", + "function-bind": "^1.0.2" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -11248,12 +11517,78 @@ } } }, + "tap-mocha-reporter": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-3.0.7.tgz", + "integrity": "sha512-GHVXJ38C3oPRpM3YUc43JlGdpVZYiKeT1fmAd3HH2+J+ZWwsNAUFvRRdoGsXLw9+gU9o+zXpBqhS/oXyRQYwlA==", + "dev": true, + "requires": { + "color-support": "^1.1.0", + "debug": "^2.1.3", + "diff": "^1.3.2", + "escape-string-regexp": "^1.0.3", + "glob": "^7.0.5", + "js-yaml": "^3.3.1", + "readable-stream": "^2.1.5", + "tap-parser": "^5.1.0", + "unicode-length": "^1.0.0" + } + }, + "tap-parser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.4.0.tgz", + "integrity": "sha512-BIsIaGqv7uTQgTW1KLTMNPSEQf4zDDPgYOBRdgOfuB+JFOLRBfEu6cLa/KvMvmqggu1FKXDfitjLwsq4827RvA==", + "dev": true, + "requires": { + "events-to-array": "^1.0.1", + "js-yaml": "^3.2.7", + "readable-stream": "^2" + } + }, "tapable": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", "dev": true }, + "tape": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/tape/-/tape-4.9.1.tgz", + "integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "defined": "~1.0.0", + "for-each": "~0.3.3", + "function-bind": "~1.1.1", + "glob": "~7.1.2", + "has": "~1.0.3", + "inherits": "~2.0.3", + "minimist": "~1.2.0", + "object-inspect": "~1.6.0", + "resolve": "~1.7.1", + "resumer": "~0.0.0", + "string.prototype.trim": "~1.1.2", + "through": "~2.3.8" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "resolve": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + } + } + }, "tar-stream": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", @@ -11640,6 +11975,16 @@ "xtend": "^4.0.1" } }, + "unicode-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/unicode-length/-/unicode-length-1.0.3.tgz", + "integrity": "sha1-Wtp6f+1RhBpBijKM8UlHisg1irs=", + "dev": true, + "requires": { + "punycode": "^1.3.2", + "strip-ansi": "^3.0.1" + } + }, "unified": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", diff --git a/package.json b/package.json index 0295f4b..9158b76 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "moz-commerce", + "name": "webext-commerce", "version": "0.1.0", "description": "", "private": "true", @@ -10,7 +10,11 @@ "build": "webpack --mode=development", "watch": "webpack --watch --mode=development", "package": "web-ext build --source-dir build --overwrite-dest", - "lint": "bin/run_lints.sh" + "lint": "bin/run_lints.sh", + "test": "bin/run_tests.py" + }, + "config": { + "firefox_bin": "" }, "devDependencies": { "babel-core": "6.26.3", @@ -21,15 +25,20 @@ "babel-preset-react": "6.24.1", "copy-webpack-plugin": "4.5.2", "css-loader": "1.0.0", + "enzyme": "3.3.0", + "enzyme-adapter-react-16": "1.1.1", "eslint": "4.19.1", "eslint-config-airbnb": "17.0.0", "eslint-import-resolver-webpack": "0.10.1", "eslint-plugin-import": "2.13.0", "eslint-plugin-jsx-a11y": "6.0.3", "eslint-plugin-react": "7.10.0", + "null-loader": "0.1.1", "style-loader": "0.21.0", "stylelint": "9.3.0", "stylelint-config-standard": "18.2.0", + "tap-mocha-reporter": "3.0.7", + "tape": "4.9.1", "web-ext": "2.7.0", "webpack": "4.15.1", "webpack-command": "0.3.1" diff --git a/src/tests/.eslintrc.json b/src/tests/.eslintrc.json new file mode 100644 index 0000000..56edc86 --- /dev/null +++ b/src/tests/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "import/no-extraneous-dependencies": ["error", { + "devDependencies": true, + "optionalDependencies": false, + "peerDependencies": false + }] + } +} diff --git a/src/tests/index.jsx b/src/tests/index.jsx new file mode 100644 index 0000000..3b1b623 --- /dev/null +++ b/src/tests/index.jsx @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Entry point for the automated test suite. This script is run inside a + * content-scoped sandbox in Firefox by Marionette. See bin/run_tests.py for + * more info. + */ + +// marionetteScriptFinished is a wrapper added during the webpack build that +// returns a value to run_tests.py and ends execution. +/* global marionetteScriptFinished */ + +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import test from 'tape'; + +// Configure testing libraries +Enzyme.configure({ adapter: new Adapter() }); + +// Collect formatted output and count failures. +let output = ''; +let failures = 0; +test.createStream() + .on('data', (data) => { + output += data; + }).on('end', () => { + // Send the results back to run_tests.py + marionetteScriptFinished({output, failures}); + }); +test.onFailure(() => failures++); + +// Import all files within the tests directory that have the word "test" +// in their filename. +const requireTest = require.context('.', true, /test.*\.jsx?$/); +requireTest.keys().forEach(requireTest); diff --git a/src/tests/sidebar/components/test_Accordion.jsx b/src/tests/sidebar/components/test_Accordion.jsx new file mode 100644 index 0000000..131bae1 --- /dev/null +++ b/src/tests/sidebar/components/test_Accordion.jsx @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from 'react'; +import {shallow} from 'enzyme'; + +import test from 'tape'; + +import Accordion from 'commerce/sidebar/components/Accordion'; + +test(' should apply the className prop to the returned container element', (t) => { + const wrapper = shallow(); + t.ok(wrapper.is('.foobar.accordion'), 'Accordion container has extra class'); + t.end(); +}); diff --git a/webpack.config.js b/webpack.config.js index 0412f2f..9dd7c9f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Webpack configuration for building the webextension. + */ + /* eslint-env node */ + const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); diff --git a/webpack.config.test.js b/webpack.config.test.js new file mode 100644 index 0000000..8bc5750 --- /dev/null +++ b/webpack.config.test.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Webpack configuration for the automated test suite. + */ + +/* eslint-env node */ + +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + mode: 'development', + devtool: 'eval-source-map', + target: 'web', + entry: { + tests: './src/tests/index', + }, + module: { + rules: [ + { + test: /\.jsx$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + }, + }, + { + test: /\.css$/, + use: { + loader: 'null-loader', + }, + }, + ], + }, + node: { + fs: 'empty', + }, + plugins: [ + new webpack.BannerPlugin({ + banner: 'const marionetteScriptFinished = arguments[0];', + raw: true, + entryOnly: true, + }), + ], + resolve: { + extensions: ['.js', '.jsx'], + alias: { + commerce: path.resolve(__dirname, 'src'), + }, + }, +};