From 4832ea72f6bec2964df0ed0c4f5a1e9607bac837 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 16:01:14 -0700 Subject: [PATCH 01/33] Add testing tools for better code coverage. --- .gitattributes | 1 + .gitignore | 4 +- .travis.yml | 54 ++++++++--- bin/create-all-branches.sh | 29 ++++++ bin/eslint.sh | 40 ++++++++ bin/install-wp-tests.sh | 152 +++++++++++++++++++++++++++++++ bin/phpcs.sh | 45 +++++++++ bin/phpmd.sh | 42 +++++++++ composer.json | 26 +++++- phpunit.xml | 3 + tests/clover-results.php | 31 +++++++ tests/unit/Actions/LoginTest.php | 32 +++++++ 12 files changed, 440 insertions(+), 19 deletions(-) create mode 100644 bin/create-all-branches.sh create mode 100755 bin/eslint.sh create mode 100644 bin/install-wp-tests.sh create mode 100644 bin/phpcs.sh create mode 100644 bin/phpmd.sh create mode 100644 tests/clover-results.php create mode 100644 tests/unit/Actions/LoginTest.php diff --git a/.gitattributes b/.gitattributes index 8e52bb3..1719bb9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ /.git export-ignore /.github export-ignore +/bin export-ignore /coverage export-ignore /tests export-ignore .gitattributes export-ignore diff --git a/.gitignore b/.gitignore index 8328b3c..6fc07d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .DS_Store /.idea coverage/ +tests/clover.xml +/wordpress /vendor -composer.lock \ No newline at end of file +composer.lock diff --git a/.travis.yml b/.travis.yml index 31e2b41..e8062be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,53 @@ -language: php - -php: - - '7.3' - - '7.4' - -install: composer install - -script: - - ./vendor/bin/phpunit --coverage-html coverage - - ./vendor/bin/phpcs --standard=PSR2 --extensions=php src - -after_script: - - composer export-coverage +os: linux +dist: trusty cache: directories: + - "$HOME/.composer/cache" - vendor +language: php +php: + - '7.4' + - '7.3' + branches: only: - master - develop +jobs: + fast_finish: true + +env: + global: + - WORDPRESS_DB_USER=wp + - WORDPRESS_DB_PASS=password + - WORDPRESS_DB_NAME=wp_tests + - WP_VERSION=5.5.1 + - WP_MULTISITE=0 + +install: + - export PATH="$HOME/.composer/vendor/bin:$PATH" + - export DEV_BIN_PATH=bin + - source $DEV_BIN_PATH/create-all-branches.sh + - | + # Remove Xdebug for a huge performance increase: + if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then + phpenv config-rm xdebug.ini + else + echo "xdebug.ini does not exist" + fi + - composer require --dev --no-update roots/wordpress:${WP_VERSION} wp-phpunit/wp-phpunit:${WP_VERSION} + - composer update --prefer-dist --no-interaction --prefer-stable --no-suggest + +before_script: + - mysql -u root -e "GRANT ALL PRIVILEGES ON $WORDPRESS_DB_NAME.* TO $WORDPRESS_DB_USER IDENTIFIED BY '$WORDPRESS_DB_PASS';" + - mysql -u root -e "CREATE DATABASE $WORDPRESS_DB_NAME;" + +script: + - composer tests + notifications: email: on_success: never diff --git a/bin/create-all-branches.sh b/bin/create-all-branches.sh new file mode 100644 index 0000000..16deba5 --- /dev/null +++ b/bin/create-all-branches.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -e + +# https://stackoverflow.com/a/44036486/558561 +function create_all_branches() { + # Keep track of where Travis put us. + # We are on a detached head, and we need to be able to go back to it. + local build_head + build_head=$(git rev-parse HEAD) + + # Fetch all the remote branches. Travis clones with `--depth`, which + # implies `--single-branch`, so we need to overwrite remote.origin.fetch to + # do that. + git config --replace-all remote.origin.fetch +refs/heads/*:refs/remotes/origin/* + git fetch + # optionally, we can also fetch the tags + git fetch --tags + + # create the tacking branches + for branch in $(git branch -r | grep -v HEAD); do + git checkout -qf "${branch#origin/}" + done + + # finally, go back to where we were at the beginning + git checkout "${build_head}" +} + +create_all_branches diff --git a/bin/eslint.sh b/bin/eslint.sh new file mode 100755 index 0000000..7851c44 --- /dev/null +++ b/bin/eslint.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -e + +# Based off: https://gist.github.com/oroce/11282380 +# Go to root of the repository +echo 'Checking ESLint' + +if [[ $(git rev-parse --verify HEAD) ]]; then + against='HEAD' +elif [[ $(git rev-parse --verify develop) ]]; then + against='develop' +elif [[ $(git rev-parse --verify master) ]]; then + against='master' +else + echo "git can't verify HEAD, develop or master." + exit 1 +fi + +commitFiles=$(git diff --name-only "$(git merge-base develop ${against})") + +jsFiles="" +jsFilesCount=0 +for f in ${commitFiles}; do + if [[ ! -e ${f} ]]; then + continue + fi + if [[ ${f} =~ \.(js|jsx)$ ]]; then + jsFilesCount=$((jsFilesCount + 1)) + jsFiles="$jsFiles $f" + fi +done +if [[ ${jsFilesCount} == 0 ]]; then + echo "No JS files updated, nothing to check." + exit 0 +fi + +echo "Checking files: $jsFiles" + +npx standard "${jsFiles}" diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh new file mode 100644 index 0000000..282bad1 --- /dev/null +++ b/bin/install-wp-tests.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash + +if [[ $# -lt 3 ]]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} + +TMPDIR=${TMPDIR-/tmp} +TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") +WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} + +download() { + if [[ `which curl` ]]; then + curl -s "$1" > "$2"; + elif [[ `which wget` ]]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ ${WP_VERSION} =~ ^[0-9]+\.[0-9]+$ ]]; then + WP_TESTS_TAG="branches/$WP_VERSION" +elif [[ ${WP_VERSION} =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then + if [[ ${WP_VERSION} =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + WP_TESTS_TAG="tags/${WP_VERSION%??}" + else + WP_TESTS_TAG="tags/$WP_VERSION" + fi +elif [[ ${WP_VERSION} == 'nightly' || ${WP_VERSION} == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi + +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ ${WP_VERSION} == 'nightly' || ${WP_VERSION} == 'trunk' ]]; then + mkdir -p $TMPDIR/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip + unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ + mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [[ ${WP_VERSION} == 'latest' ]]; then + local ARCHIVE_NAME='latest' + elif [[ ${WP_VERSION} =~ [0-9]+\.[0-9]+ ]]; then + # https serves multiple offers, whereas http serves single. + download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json + if [[ ${WP_VERSION} =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + LATEST_VERSION=${WP_VERSION%??} + else + # otherwise, scan the releases and get the most up to date minor version of the major release + local VERSION_ESCAPED=`echo ${WP_VERSION} | sed 's/\./\\\\./g'` + LATEST_VERSION=$(grep -o '"version":"'${VERSION_ESCAPED}'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) + fi + if [[ -z "$LATEST_VERSION" ]]; then + local ARCHIVE_NAME="wordpress-$WP_VERSION" + else + local ARCHIVE_NAME="wordpress-$LATEST_VERSION" + fi + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz + tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C ${WP_CORE_DIR} + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php ${WP_CORE_DIR}/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i .bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [[ ! -d ${WP_TESTS_DIR} ]]; then + # set up testing suite + mkdir -p ${WP_TESTS_DIR} + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ ${WP_TESTS_DIR}/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ ${WP_TESTS_DIR}/data + fi + + if [[ ! -f wp-tests-config.php ]]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi + +} + +install_db() { + + if [[ ${SKIP_DB_CREATE} = "true" ]]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z ${DB_HOSTNAME} ] ; then + if [[ $(echo ${DB_SOCK_OR_PORT} | grep -e '^[0-9]\{1,\}$') ]]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [[ -z ${DB_SOCK_OR_PORT} ]] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [[ -z ${DB_HOSTNAME} ]] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create ${DB_NAME} --user="$DB_USER" --password="$DB_PASS"${EXTRA} +} + +install_wp +install_test_suite +install_db diff --git a/bin/phpcs.sh b/bin/phpcs.sh new file mode 100644 index 0000000..7f3a0cc --- /dev/null +++ b/bin/phpcs.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -e + +# Based off: https://gist.github.com/Hounddog/3891872 +# Go to root of the repository +echo 'Checking PHPCS' + +if [[ $(git rev-parse --verify HEAD) ]]; then + against='HEAD' +elif [[ $(git rev-parse --verify develop) ]]; then + against='develop' +elif [[ $(git rev-parse --verify master) ]]; then + against='master' +else + echo "git can't verify HEAD, develop or master." + exit 1 +fi + +commitFiles=$(git diff --name-only "$(git merge-base develop ${against})") + +args="-s --colors --extensions=php --tab-width=4 --standard=phpcs-ruleset.xml --runtime-set testVersion 7.3-" +phpFiles="" +phpFilesCount=0 +for f in ${commitFiles}; do + if [[ ! -e ${f} ]]; then + continue + fi + if [[ ${f} =~ \.(php|ctp)$ ]]; then + phpFilesCount=$((phpFilesCount + 1)) + phpFiles="$phpFiles $f" + fi +done +if [[ ${phpFilesCount} == 0 ]]; then + echo "No PHP files updated, nothing to check." + exit 0 +fi + +echo "Checking files: $phpFiles" + +if [[ ${phpFilesCount} -gt 2 ]] && { [[ ${TRAVIS+x} ]] || [[ ${CIRCLECI+x} ]]; }; then + args="$args --report=summary" +fi + +./vendor/bin/phpcs ${args} ${phpFiles} diff --git a/bin/phpmd.sh b/bin/phpmd.sh new file mode 100644 index 0000000..433a374 --- /dev/null +++ b/bin/phpmd.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -e + +# Go to root of the repository +echo 'Checking PHPMD' + +if [[ $(git rev-parse --verify HEAD) ]]; then + against='HEAD' +elif [[ $(git rev-parse --verify develop) ]]; then + against='develop' +elif [[ $(git rev-parse --verify master) ]]; then + against='master' +else + echo "git can't verify HEAD, develop or master." + exit 1 +fi + +commitFiles=$(git diff --name-only "$(git merge-base develop ${against})") + +args="text phpmd-ruleset.xml --exclude tests,vendor --suffixes php" +phpFiles="" +phpFilesCount=0 +for f in ${commitFiles}; do + if [[ ! -e ${f} ]]; then + continue + fi + if [[ ${f} =~ \.(php|ctp)$ ]]; then + phpFilesCount=$((phpFilesCount + 1)) + phpFiles="$phpFiles $f" + fi +done +if [[ ${phpFilesCount} == 0 ]]; then + echo "No PHP files updated, nothing to check." + exit 0 +fi + +echo "Checking files: $phpFiles" + +for file in ${phpFiles}; do + ./vendor/bin/phpmd ${file} ${args} +done diff --git a/composer.json b/composer.json index 9990b62..d0e6f5d 100755 --- a/composer.json +++ b/composer.json @@ -21,10 +21,14 @@ "thefrosty/wp-utilities": "^2.0" }, "require-dev": { + "ext-simplexml": "*", "phpunit/phpunit": "^7", + "phpunit/php-code-coverage": "^6", "roave/security-advisories": "dev-master", + "roots/wordpress": "^5.5.1", "slevomat/coding-standard": "~4.0", - "squizlabs/php_codesniffer": "^3.2" + "squizlabs/php_codesniffer": "^3.2", + "wp-phpunit/wp-phpunit": "^5.5.1" }, "config": { "optimize-autoloader": true, @@ -46,8 +50,22 @@ } }, "scripts": { - "export-coverage": "test-reporter", - "phpcs": "./vendor/bin/phpcs --standard=PSR2 --extensions=php src", - "test": "./vendor/bin/phpunit --colors" + "install-codestandards": [ + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run" + ], + "phpcs": [ + "bash ./bin/phpcs.sh" + ], + "phpmd": [ + "bash ./bin/phpmd.sh" + ], + "phpunit": [ + "./vendor/bin/phpunit --colors --verbose --coverage-html ./coverage && php ./tests/clover-results.php ./tests/clover.xml 0" + ], + "tests": [ + "@phpcs", + "@phpmd", + "@phpunit" + ] } } diff --git a/phpunit.xml b/phpunit.xml index 5d31c31..0e5cc05 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -17,4 +17,7 @@ ./src + + + diff --git a/tests/clover-results.php b/tests/clover-results.php new file mode 100644 index 0000000..472011f --- /dev/null +++ b/tests/clover-results.php @@ -0,0 +1,31 @@ +xpath('//metrics'); +$totalElements = 0; +$checkedElements = 0; + +foreach ($metrics as $metric) { + $totalElements += (int) $metric['elements']; + $checkedElements += (int) $metric['coveredelements']; +} + +$coverage = ($checkedElements / $totalElements) * 100; + +if ($coverage < $percentage) { + echo 'Code coverage is ' . $coverage . '%, which is below the accepted ' . $percentage . '%' . PHP_EOL; + exit(1); +} + +echo 'Code coverage is ' . $coverage . '% - OK!' . PHP_EOL; \ No newline at end of file diff --git a/tests/unit/Actions/LoginTest.php b/tests/unit/Actions/LoginTest.php new file mode 100644 index 0000000..944df8c --- /dev/null +++ b/tests/unit/Actions/LoginTest.php @@ -0,0 +1,32 @@ +login = new Login(); + } + + public function tearDown() + { + unset($this->login); + } +} From 195b93ff2e23cc2eaca0fe34e2157795a2a104c1 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 17:28:11 -0700 Subject: [PATCH 02/33] Update unit tests for test suite. --- phpunit.xml | 8 +++- tests/bootstrap.php | 24 ++++++++++-- tests/unit/Actions/LoginTest.php | 45 +++++++++++++++++++-- tests/wp-tests-config.php | 67 ++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 tests/wp-tests-config.php diff --git a/phpunit.xml b/phpunit.xml index 0e5cc05..3fffee0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,14 +1,18 @@ + + + + - ./tests/unit + ./tests/unit diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 195f822..39998d4 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,23 @@ -login = new Login(); + $this->login->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->login); } - public function tearDown() + public function tearDown(): void { unset($this->login); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->login, 'addHooks')); + $this->login->addHooks(); + } + + /** + * Test wpLoginAction(). + */ + public function testWpLoginAction(): void + { + $this->assertTrue(\method_exists($this->login, 'wpLoginAction')); + try { + $wpLoginAction = $this->reflection->getMethod('wpLoginAction'); + $wpLoginAction->setAccessible(true); + $WP_User = $this->getMockBuilder('WP_User') + ->disableOriginalConstructor() + ->getMock(); + $WP_User->ID = 0; + \WP_Mock::passthruFunction('add_user_meta'); + \WP_Mock::passthruFunction('get_user_meta'); + \WP_Mock::passthruFunction('wp_schedule_single_event'); + $wpLoginAction->invoke($this->login, 'user_1', $WP_User); + $this->assertTrue(\did_action(LoginLocker::HOOK_PREFIX . 'wp_login')); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } } } diff --git a/tests/wp-tests-config.php b/tests/wp-tests-config.php new file mode 100644 index 0000000..4993c74 --- /dev/null +++ b/tests/wp-tests-config.php @@ -0,0 +1,67 @@ + Date: Sat, 12 Sep 2020 19:49:41 -0700 Subject: [PATCH 03/33] Update to the test suite. --- tests/unit/TestCase.php | 62 +++++++++++++++++++++++++++++++++++++++ tests/wp-tests-config.php | 31 ++++++++++---------- 2 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 tests/unit/TestCase.php diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php new file mode 100644 index 0000000..4546415 --- /dev/null +++ b/tests/unit/TestCase.php @@ -0,0 +1,62 @@ +plugin = PluginFactory::create('wp-login-locker', $filename); + $this->container = $this->plugin->getContainer(); + } + + /** + * Tear down. + */ + public function tearDown(): void + { + unset($this->container, $this->plugin, $this->reflection); + parent::tearDown(); + } + + /** + * Gets an instance of the \ReflectionObject. + * @param object $argument + * @return \ReflectionObject + */ + protected function getReflection(object $argument): \ReflectionObject + { + static $reflector; + + if (!isset($reflector[\get_class($argument)]) || + !($reflector[\get_class($argument)] instanceof \ReflectionObject) + ) { + $reflector[\get_class($argument)] = new \ReflectionObject($argument); + } + + return $reflector[\get_class($argument)]; + } +} diff --git a/tests/wp-tests-config.php b/tests/wp-tests-config.php index 4993c74..2dc680f 100644 --- a/tests/wp-tests-config.php +++ b/tests/wp-tests-config.php @@ -4,7 +4,7 @@ /* Path to the WordPress codebase you'd like to test. Add a forward slash in the end. */ // This is the install path as defined by `wordpress-install-dir` in composer.json $abspath = defined('TRAVIS') || getenv('TRAVIS') ? '/wordpress/' : '/'; -define('ABSPATH', dirname(__DIR__) . '/wordpress/' ); +define('ABSPATH', dirname(__DIR__) . '/wordpress/'); /* * Path to the theme to test with. @@ -34,11 +34,11 @@ // These tests will DROP ALL TABLES in the database with the prefix named below. // DO NOT use a production database or one that is shared with something else. -define('DB_NAME', getenv('WP_DB_NAME') ?: 'wp_phpunit_tests'); -define('DB_USER', getenv('WP_DB_USER') ?: 'root'); -define('DB_PASSWORD', getenv('WP_DB_PASS') ?: ''); -define('DB_HOST', 'localhost'); -define('DB_CHARSET', 'utf8'); +define('DB_NAME', getenv('WORDPRESS_DB_NAME') ?: 'wp_phpunit_tests'); +define('DB_USER', getenv('WORDPRESS_DB_USER') ?: 'wordpress_user'); +define('DB_PASSWORD', getenv('WORDPRESS_DB_PASS') ?: 'mysql_password'); +define('DB_HOST', getenv('WORDPRESS_DB_HOST') ?: '127.0.0.1'); +define('DB_CHARSET', 'utf8mb4'); define('DB_COLLATE', ''); /**#@+ @@ -47,16 +47,17 @@ * Change these to different unique phrases! * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service} */ -define('AUTH_KEY', 'put your unique phrase here'); -define('SECURE_AUTH_KEY', 'put your unique phrase here'); -define('LOGGED_IN_KEY', 'put your unique phrase here'); -define('NONCE_KEY', 'put your unique phrase here'); -define('AUTH_SALT', 'put your unique phrase here'); -define('SECURE_AUTH_SALT', 'put your unique phrase here'); -define('LOGGED_IN_SALT', 'put your unique phrase here'); -define('NONCE_SALT', 'put your unique phrase here'); +define('AUTH_KEY', ']$:,ddmsBSpY++_,~!MT+T)m+N@dZfRjGMXXG}VvDx<`_L}p<1A1Egejj$3TkETd'); +define('SECURE_AUTH_KEY', 'q`M!posJii3GT{auHC6z a'); +define('LOGGED_IN_KEY', '$CRI0?|w`X/tVGz[}WF]B{]bE$DhE]0dg}jmmc`F0}QXT;]ib@GhHmeCG7ay [}H?>-NdsTf.KFjQ~9@DE+m0D QMj1-!+'); +define('SECURE_AUTH_SALT', 'oK%>WO`VV?p&OMAT6mk>U;#HT7%QnQ3I{W|LG~nxdW@KWaM,r&+;^8-f^J=QPg~,'); +define('LOGGED_IN_SALT', 'MOXV/qP< ML|&eZYuv6rp$(RW OK}Hp$SwhCqY%T5^XjKs0YQV]imsbNxp>|n|1&'); +define('NONCE_SALT', 'L3.( +bK+PG[7C{YkXLZlg]SBCLx[5&s3PPV]x)AZ-1!$y%-#SDNt#,T<~aB+SQ6'); -$table_prefix = 'wpphpunittests_'; // Only numbers, letters, and underscores please! + +$table_prefix = 'wptests_'; define('WP_TESTS_DOMAIN', 'example.org'); define('WP_TESTS_EMAIL', 'admin@example.org'); From 60e1c5a4525573825b52e58478d82e5e154fad8a Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 19:49:56 -0700 Subject: [PATCH 04/33] =?UTF-8?q?Add=20Login=20tests=20to=20=F0=9F=92=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/Actions/LoginTest.php | 147 +++++++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 9 deletions(-) diff --git a/tests/unit/Actions/LoginTest.php b/tests/unit/Actions/LoginTest.php index 98c36b0..e920220 100644 --- a/tests/unit/Actions/LoginTest.php +++ b/tests/unit/Actions/LoginTest.php @@ -6,6 +6,7 @@ use TheFrosty\Tests\WpLoginLocker\TestCase; use TheFrosty\WpLoginLocker\Actions\Login; use TheFrosty\WpLoginLocker\LoginLocker; +use TheFrosty\WpLoginLocker\WpMail\WpMail; /** * Class Login @@ -26,6 +27,7 @@ public function setUp(): void { parent::setUp(); $this->login = new Login(); + $this->login->setPlugin($this->plugin); $this->login->setRequest(Request::createFromGlobals()); $this->reflection = $this->getReflection($this->login); } @@ -54,15 +56,142 @@ public function testWpLoginAction(): void try { $wpLoginAction = $this->reflection->getMethod('wpLoginAction'); $wpLoginAction->setAccessible(true); - $WP_User = $this->getMockBuilder('WP_User') - ->disableOriginalConstructor() - ->getMock(); - $WP_User->ID = 0; - \WP_Mock::passthruFunction('add_user_meta'); - \WP_Mock::passthruFunction('get_user_meta'); - \WP_Mock::passthruFunction('wp_schedule_single_event'); - $wpLoginAction->invoke($this->login, 'user_1', $WP_User); - $this->assertTrue(\did_action(LoginLocker::HOOK_PREFIX . 'wp_login')); + $WP_User = new \WP_User(); + $wpLoginAction->invoke($this->login, $WP_User->user_login, $WP_User); + $this->assertEquals(1, \did_action(LoginLocker::HOOK_PREFIX . 'wp_login')); + $this->assertNull($this->reflection->getDefaultProperties()['wp_mail']); + + // Test with a valid user and user meta + $WP_User = self::factory()->user->create_and_get(); + \add_user_meta($WP_User->ID, LoginLocker::LAST_LOGIN_IP_META_KEY, '192.168.1.256'); + $wpLoginAction->invoke($this->login, $WP_User->user_login, $WP_User); + $this->assertEquals(2, \did_action(LoginLocker::HOOK_PREFIX . 'wp_login')); + \delete_user_meta($WP_User->ID, LoginLocker::LAST_LOGIN_IP_META_KEY); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test postMetaCleanup(). + */ + public function testPostMetaCleanup(): void + { + $this->assertTrue(\method_exists($this->login, 'postMetaCleanup')); + try { + $postMetaCleanup = $this->reflection->getMethod('postMetaCleanup'); + $postMetaCleanup->setAccessible(true); + $WP_User = new \WP_User(); + $this->assertNull($postMetaCleanup->invoke($this->login, $WP_User->ID)); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test setProtectedMeta(). + */ + public function testSetProtectedMeta(): void + { + $this->assertTrue(\method_exists($this->login, 'setProtectedMeta')); + try { + $setProtectedMeta = $this->reflection->getMethod('setProtectedMeta'); + $setProtectedMeta->setAccessible(true); + $actual = $setProtectedMeta->invoke($this->login, false, 'bad_key'); + $this->assertFalse($actual); + foreach ([LoginLocker::LAST_LOGIN_IP_META_KEY, LoginLocker::LAST_LOGIN_TIME_META_KEY] as $key) { + $actual = $setProtectedMeta->invoke($this->login, false, $key); + $this->assertTrue($actual); + } + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test getEmailPretext(). + */ + public function testGetEmailPretext(): void + { + $this->assertTrue(\method_exists($this->login, 'getEmailPretext')); + try { + $wp_mail = $this->reflection->getProperty('wp_mail'); + $wp_mail->setAccessible(true); + $wp_mail->setValue($this->login, new WpMail()); + $getEmailPretext = $this->reflection->getMethod('getEmailPretext'); + $getEmailPretext->setAccessible(true); + $actual = $getEmailPretext->invoke($this->login); + $this->assertIsString($actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test getEmailMessage(). + */ + public function testGetEmailMessage(): void + { + $this->assertTrue(\method_exists($this->login, 'getEmailMessage')); + try { + $getEmailMessage = $this->reflection->getMethod('getEmailMessage'); + $WP_User = new \WP_User(); + $actual = $getEmailMessage->invoke($this->login, $WP_User); + $this->assertIsString($actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test getUserName(). + */ + public function testGetUserName(): void + { + $this->assertTrue(\method_exists($this->login, 'getUserName')); + try { + $getUserName = $this->reflection->getMethod('getUserName'); + $getUserName->setAccessible(true); + foreach ( + [ + 'first_name' => 'First Name', + 'display_name' => 'Mr. First Name', + 'user_login' => 'mr_first' + ] as $key => $val) { + + $user = self::factory()->user->create_and_get([$key => $val]); + $actual = $getUserName->invoke($this->login, $user); + $this->assertTrue( + \in_array($actual, [$user->first_name, $user->display_name, $user->user_login], true) + ); + } + $user = self::factory()->user->create_and_get(); + $user->first_name = ''; + $user->display_name = ''; + $actual = $getUserName->invoke($this->login, $user); + $this->assertSame($user->user_login, $actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test getHomeUrl(). + */ + public function testGetHomeUrl(): void + { + $this->assertTrue(\method_exists($this->login, 'getHomeUrl')); + try { + $getHomeUrl = $this->reflection->getMethod('getHomeUrl'); + $getHomeUrl->setAccessible(true); + $actual = $getHomeUrl->invoke($this->login); + $this->assertIsString($actual); } catch (\ReflectionException $exception) { $this->assertInstanceOf(\ReflectionException::class, $exception); $this->markAsRisky(); From a61dfabc611a5a177cd8355e31f75cab4d262efc Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 20:30:37 -0700 Subject: [PATCH 05/33] Add group to test. --- tests/unit/Actions/LoginTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/Actions/LoginTest.php b/tests/unit/Actions/LoginTest.php index e920220..a4973db 100644 --- a/tests/unit/Actions/LoginTest.php +++ b/tests/unit/Actions/LoginTest.php @@ -11,6 +11,7 @@ /** * Class Login * @package TheFrosty\Tests\WpLoginLocker\Actions + * @group actions */ class LoginTest extends TestCase { From 423b8f14476f26bfd4d464d7feaebc44ec2e425e Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 20:30:47 -0700 Subject: [PATCH 06/33] Add NewUserTest. --- tests/unit/Actions/NewUserTest.php | 80 ++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/unit/Actions/NewUserTest.php diff --git a/tests/unit/Actions/NewUserTest.php b/tests/unit/Actions/NewUserTest.php new file mode 100644 index 0000000..1ac0ea4 --- /dev/null +++ b/tests/unit/Actions/NewUserTest.php @@ -0,0 +1,80 @@ +newUser = new NewUser(); + $this->newUser->setPlugin($this->plugin); + $this->newUser->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->newUser); + } + + public function tearDown(): void + { + unset($this->newUser); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->newUser, 'addHooks')); + $this->newUser->addHooks(); + } + + /** + * Test userRegisterAction(). + */ + public function testUserRegisterAction(): void + { + $this->assertTrue(\method_exists($this->newUser, 'userRegisterAction')); + try { + $userRegisterAction = $this->reflection->getMethod('userRegisterAction'); + $userRegisterAction->setAccessible(true); + $user = self::factory()->user->create_and_get(); + $userRegisterAction->invoke($this->newUser, $user->ID); + $actual = \get_user_meta($user->ID, LoginLocker::LAST_LOGIN_IP_META_KEY, true); + $this->assertSame($this->newUser->getIP(), $actual); + $this->deleteUserMeta($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * @param int $user_id + */ + protected function deleteUserMeta(int $user_id): void + { + \delete_user_meta($user_id, LoginLocker::LAST_LOGIN_IP_META_KEY); + \delete_user_meta($user_id, LoginLocker::LAST_LOGIN_TIME_META_KEY); + } +} From 61dabbf856d3f6f6cc4f56fd8661cb99005bf12a Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 20:30:58 -0700 Subject: [PATCH 07/33] Add LastLoginColumnsTest. --- tests/unit/Login/LastLoginColumnsTest.php | 134 ++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 tests/unit/Login/LastLoginColumnsTest.php diff --git a/tests/unit/Login/LastLoginColumnsTest.php b/tests/unit/Login/LastLoginColumnsTest.php new file mode 100644 index 0000000..149358c --- /dev/null +++ b/tests/unit/Login/LastLoginColumnsTest.php @@ -0,0 +1,134 @@ +lastLoginColumns = new LastLoginColumns(); + $this->reflection = $this->getReflection($this->lastLoginColumns); + } + + public function tearDown(): void + { + unset($this->lastLoginColumns); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + \wp_set_current_user(self::factory()->user->create(['role' => 'administrator'])); + $this->assertTrue(\method_exists($this->lastLoginColumns, 'addHooks')); + $this->lastLoginColumns->addHooks(); + } + + /** + * Test addColumn(). + */ + public function testAddColumn(): void + { + $this->assertTrue(\method_exists($this->lastLoginColumns, 'addColumn')); + try { + $addColumn = $this->reflection->getMethod('addColumn'); + $addColumn->setAccessible(true); + $actual = $addColumn->invoke($this->lastLoginColumns, []); + $this->assertIsArray($actual); + $this->assertCount(1, $actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test manageUsersCustomColumn(). + */ + public function testManageUsersCustomColumn(): void + { + $this->assertTrue(\method_exists($this->lastLoginColumns, 'manageUsersCustomColumn')); + try { + $manageUsersCustomColumn = $this->reflection->getMethod('manageUsersCustomColumn'); + $manageUsersCustomColumn->setAccessible(true); + $user = self::factory()->user->create_and_get(); + \delete_user_meta($user->ID, LoginLocker::LAST_LOGIN_TIME_META_KEY); + $actual = $manageUsersCustomColumn->invoke($this->lastLoginColumns, '', 'bad_key', $user->ID); + $this->assertIsString($actual); + $this->assertSame('', $actual); + $actual = $manageUsersCustomColumn->invoke($this->lastLoginColumns, '', LoginLocker::LAST_LOGIN, $user->ID); + $this->assertIsString($actual); + $this->assertSame('Unknown', $actual); + \add_user_meta($user->ID, LoginLocker::LAST_LOGIN_TIME_META_KEY, ''); + $actual = $manageUsersCustomColumn->invoke($this->lastLoginColumns, '', LoginLocker::LAST_LOGIN, $user->ID); + $this->assertIsString($actual); + $this->assertNotSame('Unknown', $actual); + \delete_user_meta($user->ID, LoginLocker::LAST_LOGIN_TIME_META_KEY); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test addSortable(). + */ + public function testAddSortable(): void + { + $this->assertTrue(\method_exists($this->lastLoginColumns, 'addSortable')); + try { + $addSortable = $this->reflection->getMethod('addSortable'); + $addSortable->setAccessible(true); + $actual = $addSortable->invoke($this->lastLoginColumns, []); + $this->assertIsArray($actual); + $this->assertCount(1, $actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test preGetUsers(). + */ + public function testPeGetUsers(): void + { + $this->assertTrue(\method_exists($this->lastLoginColumns, 'preGetUsers')); + try { + $preGetUsers = $this->reflection->getMethod('preGetUsers'); + $preGetUsers->setAccessible(true); + $WP_User_Query = new \WP_User_Query(); + $actual = $preGetUsers->invoke($this->lastLoginColumns, $WP_User_Query); + $this->assertInstanceOf(\WP_User_Query::class, $actual); + $WP_User_Query->query_vars['orderby'] = LoginLocker::LAST_LOGIN; + $actual = $preGetUsers->invoke($this->lastLoginColumns, $WP_User_Query); + $this->assertIsArray($actual->query_vars); + $this->assertArrayHasKey('meta_key', $actual->query_vars); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } +} From 1a1ef0152e3c1665fd234163acaa529ce6950961 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 20:31:45 -0700 Subject: [PATCH 08/33] Update dependencies, slowing bump test coverage. --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index d0e6f5d..345b3af 100755 --- a/composer.json +++ b/composer.json @@ -15,15 +15,15 @@ "php": ">=7.3", "ext-openssl": "*", "composer/installers": "~1.0", - "dwnload/wp-settings-api": "^3.1", + "dwnload/wp-settings-api": "^3.1.1", "pimple/pimple": "~3.0", "symfony/http-foundation": "^5.0.7", "thefrosty/wp-utilities": "^2.0" }, "require-dev": { "ext-simplexml": "*", - "phpunit/phpunit": "^7", "phpunit/php-code-coverage": "^6", + "phpunit/phpunit": "^7", "roave/security-advisories": "dev-master", "roots/wordpress": "^5.5.1", "slevomat/coding-standard": "~4.0", @@ -60,7 +60,7 @@ "bash ./bin/phpmd.sh" ], "phpunit": [ - "./vendor/bin/phpunit --colors --verbose --coverage-html ./coverage && php ./tests/clover-results.php ./tests/clover.xml 0" + "./vendor/bin/phpunit --colors --verbose --coverage-html ./coverage && php ./tests/clover-results.php ./tests/clover.xml 36" ], "tests": [ "@phpcs", From feb04dada97e08f6c515ca0041eaf530c49a7dc1 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sat, 12 Sep 2020 20:33:45 -0700 Subject: [PATCH 09/33] Build against proper branches. --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e8062be..723921a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,11 @@ php: branches: only: - - master - develop + - "/^.*[Ff]eature\\/.*$/" + - "/^.*[rR]elease\\/.*$/" + - "/^.*[bB]ug\\/.*$/" + - "/^.*[Hh]otfix\\/.*$/" jobs: fast_finish: true From e8f061dbc89cd1a07c89e3e5a1707c6305432b2c Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 08:10:14 -0700 Subject: [PATCH 10/33] Remove composer require from travis install as it's in composer require-dev --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 723921a..9cea969 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,6 @@ install: else echo "xdebug.ini does not exist" fi - - composer require --dev --no-update roots/wordpress:${WP_VERSION} wp-phpunit/wp-phpunit:${WP_VERSION} - composer update --prefer-dist --no-interaction --prefer-stable --no-suggest before_script: From 4fb9febaccff58d4c443a4c6977bdc25d24c97b3 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 09:43:43 -0700 Subject: [PATCH 11/33] Add Settings Link to plugin page. --- src/Settings/Settings.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Settings/Settings.php b/src/Settings/Settings.php index 300d878..530a4dd 100644 --- a/src/Settings/Settings.php +++ b/src/Settings/Settings.php @@ -56,6 +56,7 @@ public function addHooks(): void $this->addAction(WpSettingsApi::ACTION_PREFIX . 'init', [$this, 'init'], 10, 3); $this->addFilter(WpSettingsApi::FILTER_PREFIX . 'admin_scripts', [$this, 'adminScripts']); $this->addFilter(WpSettingsApi::FILTER_PREFIX . 'admin_styles', [$this, 'adminStyles']); + $this->addFilter('plugin_action_links_' . $this->getPlugin()->getBasename(), [$this, 'addSettingsLink']); } /** @@ -203,6 +204,25 @@ protected function adminStyles(array $styles): array return $styles; } + /** + * Add settings page link to the plugins page. + * @param array $actions + * @return array + */ + protected function addSettingsLink(array $actions): array + { + \array_unshift( + $actions, + \sprintf('%s', + \menu_page_url(self::MENU_SLUG, false), + \esc_attr__('Settings for Login Locker', 'wp-login-locker'), + \esc_html__('Settings', 'default') + ) + ); + + return $actions; + } + /** * Get the default pretext setting. * @return string From 4e571d264dcfbeff729aa29536d6fd4bea41ba11 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 10:50:57 -0700 Subject: [PATCH 12/33] Add asset for testing uploads, and enhancement to base testcase. --- tests/assets/300.jpg | Bin 0 -> 3397 bytes tests/unit/TestCase.php | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 tests/assets/300.jpg diff --git a/tests/assets/300.jpg b/tests/assets/300.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5ef05485f36c0a15f76f177ccf78355fb5a67c8f GIT binary patch literal 3397 zcmeH|dpOkF8pnUXxz8XCQ>0;L8keCVrjc?V8fNG+!zfC&ami3Qu2a~PaqGeqLrv-+ zCKaNd2`PDsZbKw&MO2JiJ9{T-quR-t_Bqc!&pH2`&iUi~)w`bWKWn|~eb(n)>-nz5 z{>5Qnsh0=a0{}q~2v!eZ@eAPW?!#p8w))r;Bf>1BV!|VcLK`b}-y4~bgjpN}+yN8Tuu8G_#Xj6pfETBi9%yE)Cu)V0SE|% zLSRrh90pToi`44?3=7vG*)R~gTt0GDERL2YEklvr6fMhqx~GlThQy_#G0XMzR~VR> zno-Ox=(g+Z)^D(P-^ldvWU;+`d4Air`v>d@RW~a_5E&JpkSN@LASpQ`GwaadBS*9I z3r?Jrohm$iMt-jR{Dq4Zl~q@3>*}vHT))wH=kC4x50tHK4|{&WuC*RGy|7~_|{=>&lU%5a4`gg29$^MNCtLB2hU{DzHD;EgauND*wgOh9!It(t7 zAFI2HmWIN)Ny`*1X!2U0>182t-I(RZbd|}wuV{aeeH&Q%|3vmD*gv@j08J>UzC0)v zZ~=aMNRf*%-}Su*|JOHAJ@LfbA!+&5%?3q!hAtSOTVB?1-A(W7Z5VE^O4^v= zx|~v_JL$1ufU@tUaOYjZvODt2A`62Rfm}WCo zpCqT2ZmhjCWPxuCj7QS}c-g`;%f7qD@1&bPO{*2Rl#6x2Y$qe+?vOGonC zGWYl8m7r6tQ!+MEO`k?2j)zJ-&;B?e)?=-xi%e-F+qfKCPHw+->xigpvRULwKjRBe z_t)0KNl?xwB40gfY$}SYR`EU!a}ZfLt}Fao#VPQt&v#n{?4vhW)QF=Na6sz_E5yIs zO)dd>i-6*d@_YdEXL=!uu5=~MGevBSO%e!E+jC1(Q`8ab?~ zu%qRcM>^dyTAXK{aA43xkAv)3;nk)Hi-$><)CXF2xtiZJI00FaOe?}NYb5K!CH$O* zZJ`C*1f`gUZA)KVt)1E)1SPdpTGq(xbJ~x_*ykq~_;OuwQwi-Ul|MdsdD#Q)E?J1` zuvWsn@q6k$8C$>G`SZ_9R_qh>Lh z$#_DCuk(whrP(w;p>CH#NjRi()uU9!?`kbzRb*uzfT5n{#Fn6j6Dz$R_U)!quzvpe zXfxw?eFI{%aw-Vqq1PMD`n;TC7caT1_(-_PZ@Av(XduWB2&l^L1X;E%5pI@nx z5iE*~joshE=PoPg#IwyCXg$!89% z*Ka>!=Q*s9$J5FRvD4Wa`&fTLP>5s61N4!i^KE0Ax_#640e^YLOw*4#zs! zK2D$J7c5pE4gZ}kQ}wfTYS(6TcF~rIJ}d~JTe2yokpHAJiYgB>cLuCm(&SrFiW+G+ zuH$76lf%h1hsnC5eKKc_!qyT8>JBQ&Q^E0Fsw0p43?o%IR-ksF7(KT_`JO&W zftWNLBP6G52S%?aFnXl>_yMpq=WyfP!r_R^$WgUXVB0n0;W}2r;=B3QshH3^K2SX9 z@#~nvF`{zb)(HpnkNThU@8a`$-6p2V5Chq$5PNH(${2 v2;v4_EVDJiftW$Jiq~~{qgpa!>8xY#@f_p55?VEiJW$L8a9k#ky7=sG1hf_> literal 0 HcmV?d00001 diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php index 4546415..b4b4aa7 100644 --- a/tests/unit/TestCase.php +++ b/tests/unit/TestCase.php @@ -12,6 +12,8 @@ class TestCase extends \WP_UnitTestCase { + public const METHOD_ADD_FILTER = 'addFilter'; + /** @var \TheFrosty\WpUtilities\Plugin\Container $container */ protected $container; @@ -59,4 +61,16 @@ protected function getReflection(object $argument): \ReflectionObject return $reflector[\get_class($argument)]; } + + /** + * Get a Mock Provider. + * @param string $className + * @return \PHPUnit\Framework\MockObject\MockObject + */ + protected function getMockProvider(string $className): \PHPUnit\Framework\MockObject\MockObject + { + return $this->getMockBuilder($className) + ->setMethods([self::METHOD_ADD_FILTER]) + ->getMock(); + } } From bd9b2579ed0ffa2dbf60ee311031eb59c983c8b1 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 10:51:07 -0700 Subject: [PATCH 13/33] Update columns test. --- tests/unit/Login/LastLoginColumnsTest.php | 24 ++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/unit/Login/LastLoginColumnsTest.php b/tests/unit/Login/LastLoginColumnsTest.php index 149358c..7f064a4 100644 --- a/tests/unit/Login/LastLoginColumnsTest.php +++ b/tests/unit/Login/LastLoginColumnsTest.php @@ -5,7 +5,6 @@ use TheFrosty\Tests\WpLoginLocker\TestCase; use TheFrosty\WpLoginLocker\Login\LastLoginColumns; use TheFrosty\WpLoginLocker\LoginLocker; -use TheFrosty\WpLoginLocker\WpMail\WpMail; /** * Class Login @@ -37,13 +36,32 @@ public function tearDown(): void } /** - * Test addHooks(). + * Test addHooks(). With user cap */ public function testAddHooks(): void { + $this->assertTrue(\method_exists($this->lastLoginColumns, 'addHooks')); \wp_set_current_user(self::factory()->user->create(['role' => 'administrator'])); + $provider = $this->getMockProvider(LastLoginColumns::class); + $provider->expects($this->exactly(7)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var LastLoginColumns $provider */ + $provider->addHooks(); + } + + /** + * Test addHooks(). No user cap + */ + public function testAddHooksNoCapability(): void + { $this->assertTrue(\method_exists($this->lastLoginColumns, 'addHooks')); - $this->lastLoginColumns->addHooks(); + $provider = $this->getMockProvider(LastLoginColumns::class); + $provider->expects($this->exactly(0)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var LastLoginColumns $provider */ + $provider->addHooks(); } /** From 0f5b3cfb7a99ce4e694a702fed924ba7f4439d1b Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 10:51:23 -0700 Subject: [PATCH 14/33] Add Login test to max without stubbing multisite. --- tests/unit/Login/LoginTest.php | 184 +++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 tests/unit/Login/LoginTest.php diff --git a/tests/unit/Login/LoginTest.php b/tests/unit/Login/LoginTest.php new file mode 100644 index 0000000..122196a --- /dev/null +++ b/tests/unit/Login/LoginTest.php @@ -0,0 +1,184 @@ +login = new Login(); + $this->reflection = $this->getReflection($this->login); + } + + public function tearDown(): void + { + unset($this->login); + parent::tearDown(); + } + + /** + * Test new Login(). + */ + public function testConstructor(): void + { + try { + $settings = $this->reflection->getProperty('settings'); + $settings->setAccessible(true); + $actual = $settings->getValue($this->login); + $this->assertIsArray($actual); + $this->assertCount(0, $actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->login, 'addHooks')); + $provider = $this->getMockProvider(Login::class); + $provider->expects($this->exactly(0)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var Login $provider */ + $provider->addHooks(); + } + + /** + * Test addHooks(). + */ + public function testAddHooksWithSettings(): void + { + $this->setUpSettingOptions(); + $this->assertTrue(\method_exists($this->login, 'addHooks')); + $provider = $this->getMockProvider(Login::class); + $provider->expects($this->exactly(3)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var Login $provider */ + $provider->addHooks(); + } + + /** + * Test wpAddInlineLoginStyle(). + */ + public function testWpAddInlineLoginStyle(): void + { + $this->assertTrue(\method_exists($this->login, 'wpAddInlineLoginStyle')); + try { + $wpAddInlineLoginStyle = $this->reflection->getMethod('wpAddInlineLoginStyle'); + $wpAddInlineLoginStyle->setAccessible(true); + $this->assertNull($wpAddInlineLoginStyle->invoke($this->login)); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test wpAddInlineLoginStyle(). + */ + public function testWpAddInlineLoginStyleWithFile(): void + { + try { + $this->setUpSettingOptions(); + $login = new Login(); + \do_action('login_enqueue_scripts'); + $wpAddInlineLoginStyle = $this->getReflection($login)->getMethod('wpAddInlineLoginStyle'); + $wpAddInlineLoginStyle->setAccessible(true); + $this->assertNull($wpAddInlineLoginStyle->invoke($login)); + // With attachment + $filename = \dirname(ABSPATH) . '/tests/assets/300.jpg'; + $contents = \file_get_contents($filename); + $upload = \wp_upload_bits(\wp_basename($filename), null, $contents); + $this->assertTrue(empty($upload['error'])); + $id = $this->_make_attachment($upload); + \update_option(Settings::LOGIN_SETTINGS, [ + Settings::LOGIN_SETTING_LOGO => \wp_get_attachment_url($id), + ]); + \ob_start(); + $wpAddInlineLoginStyle->invoke($login); + $html = \ob_get_clean(); + $this->assertIsString($html); + $this->setUpSettingOptions(true); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test loginHeaderUrl(). + */ + public function testLoginHeaderUrl(): void + { + $this->assertTrue(\method_exists($this->login, 'loginHeaderUrl')); + try { + $loginHeaderUrl = $this->reflection->getMethod('loginHeaderUrl'); + $loginHeaderUrl->setAccessible(true); + $actual = $loginHeaderUrl->invoke($this->login, ''); + $this->assertIsString($actual); + $this->assertNotSame('', $actual); + $this->assertSame(\home_url(), $actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test loginHeaderTitle(). + */ + public function testLoginHeaderTitle(): void + { + $this->assertTrue(\method_exists($this->login, 'loginHeaderTitle')); + try { + $loginHeaderTitle = $this->reflection->getMethod('loginHeaderTitle'); + $loginHeaderTitle->setAccessible(true); + $actual = $loginHeaderTitle->invoke($this->login, ''); + $this->assertIsString($actual); + $this->assertNotSame('', $actual); + $this->assertSame(\get_bloginfo('description'), $actual); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * @param bool $delete + */ + protected function setUpSettingOptions(bool $delete = false): void + { + if (!$delete) { + \update_option(Settings::LOGIN_SETTINGS, [ + Settings::LOGIN_SETTING_LOGO => 'https://via.placeholder.com/300.jpg', + ]); + } else { + \delete_option(Settings::LOGIN_SETTINGS); + } + } +} From 6d7d9e0d08befbae0814451bc2e175347a4df786 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 11:27:08 -0700 Subject: [PATCH 15/33] Add missing package for PHP version compatibility in PHPCS. --- composer.json | 2 ++ phpcs-ruleset.xml | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 345b3af..7126a30 100755 --- a/composer.json +++ b/composer.json @@ -21,8 +21,10 @@ "thefrosty/wp-utilities": "^2.0" }, "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3", "ext-simplexml": "*", "phpunit/php-code-coverage": "^6", + "phpcompatibility/php-compatibility": "*", "phpunit/phpunit": "^7", "roave/security-advisories": "dev-master", "roots/wordpress": "^5.5.1", diff --git a/phpcs-ruleset.xml b/phpcs-ruleset.xml index eb12956..e0d7229 100644 --- a/phpcs-ruleset.xml +++ b/phpcs-ruleset.xml @@ -23,12 +23,12 @@ - + - - + + From 53b30367f0a0480560a3554542f2772821d0ed22 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 12:01:05 -0700 Subject: [PATCH 16/33] Fix phpcs. --- src/Settings/Settings.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Settings/Settings.php b/src/Settings/Settings.php index 530a4dd..8a831b2 100644 --- a/src/Settings/Settings.php +++ b/src/Settings/Settings.php @@ -213,7 +213,8 @@ protected function addSettingsLink(array $actions): array { \array_unshift( $actions, - \sprintf('%s', + \sprintf( + '%s', \menu_page_url(self::MENU_SLUG, false), \esc_attr__('Settings for Login Locker', 'wp-login-locker'), \esc_html__('Settings', 'default') From 775c6b671f0651448211e80f32970925232c6dea Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 12:03:34 -0700 Subject: [PATCH 17/33] Add package.json and update linter for JS. --- .gitattributes | 2 + .gitignore | 1 + composer.json | 7 +- package-lock.json | 2221 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 23 + 5 files changed, 2252 insertions(+), 2 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitattributes b/.gitattributes index 1719bb9..a18de66 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,6 +6,8 @@ .gitattributes export-ignore .gitignore export-ignore .travis.yml export-ignore +package.json export-ignore +package-lock.json export-ignore phpcs-ruleset.xml export-ignore phpmd-ruleset.xml export-ignore phpunit.xml export-ignore diff --git a/.gitignore b/.gitignore index 6fc07d0..4c8aa6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store /.idea coverage/ +node_modules/ tests/clover.xml /wordpress /vendor diff --git a/composer.json b/composer.json index 7126a30..623af91 100755 --- a/composer.json +++ b/composer.json @@ -64,10 +64,13 @@ "phpunit": [ "./vendor/bin/phpunit --colors --verbose --coverage-html ./coverage && php ./tests/clover-results.php ./tests/clover.xml 36" ], + "eslint": [ + "npm run eslint" + ], "tests": [ "@phpcs", - "@phpmd", - "@phpunit" + "@phpunit", + "@eslint" ] } } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d8f6bfd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2221 @@ +{ + "name": "wp-login-locker", + "version": "2.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "ajv": { + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "debug-log": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", + "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "deglob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/deglob/-/deglob-4.0.1.tgz", + "integrity": "sha512-/g+RDZ7yf2HvoW+E5Cy+K94YhgcFgr6C8LuHZD1O5HoNPkf3KY6RfXJ0DBGlB/NkLi5gml+G9zqRzk9S0mHZCg==", + "dev": true, + "requires": { + "find-root": "^1.0.0", + "glob": "^7.0.5", + "ignore": "^5.0.0", + "pkg-config": "^1.1.0", + "run-parallel": "^1.1.2", + "uniq": "^1.0.1" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.4.0.tgz", + "integrity": "sha512-WTVEzK3lSFoXUovDHEbkJqCVPEPwbhCq4trDktNI6ygs7aO41d4cDT0JFAT5MivzZeVLWlg7vHL+bgrQv/t3vA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-config-standard": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz", + "integrity": "sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==", + "dev": true + }, + "eslint-config-standard-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-8.1.0.tgz", + "integrity": "sha512-ULVC8qH8qCqbU792ZOO6DaiaZyHNS/5CZt3hKqHkEhVlhPEPN3nfBqqxJCyp59XrjIBZPu1chMYe9T2DXZ7TMw==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-es": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz", + "integrity": "sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ==", + "dev": true, + "requires": { + "eslint-utils": "^1.4.2", + "regexpp": "^3.0.0" + }, + "dependencies": { + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", + "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.1", + "read-pkg-up": "^2.0.0", + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-node": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz", + "integrity": "sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ==", + "dev": true, + "requires": { + "eslint-plugin-es": "^2.0.0", + "eslint-utils": "^1.4.2", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, + "eslint-plugin-react": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", + "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.1.0", + "object.entries": "^1.1.0", + "object.fromentries": "^2.0.0", + "object.values": "^1.1.0", + "prop-types": "^15.7.2", + "resolve": "^1.10.1" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-plugin-standard": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", + "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "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", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-callable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsx-ast-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "object.assign": "^4.1.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "object.entries": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } + } + }, + "pkg-config": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", + "integrity": "sha1-VX7yLXPaPIg3EHdmxS6tq94pj+Q=", + "dev": true, + "requires": { + "debug-log": "^1.0.0", + "find-root": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, + "rxjs": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "standard": { + "version": "14.3.4", + "resolved": "https://registry.npmjs.org/standard/-/standard-14.3.4.tgz", + "integrity": "sha512-+lpOkFssMkljJ6eaILmqxHQ2n4csuEABmcubLTb9almFi1ElDzXb1819fjf/5ygSyePCq4kU2wMdb2fBfb9P9Q==", + "dev": true, + "requires": { + "eslint": "~6.8.0", + "eslint-config-standard": "14.1.1", + "eslint-config-standard-jsx": "8.1.0", + "eslint-plugin-import": "~2.18.0", + "eslint-plugin-node": "~10.0.0", + "eslint-plugin-promise": "~4.2.1", + "eslint-plugin-react": "~7.14.2", + "eslint-plugin-standard": "~4.0.0", + "standard-engine": "^12.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-plugin-import": { + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "standard-engine": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-12.1.0.tgz", + "integrity": "sha512-DVJnWM1CGkag4ucFLGdiYWa5/kJURPONmMmk17p8FT5NE4UnPZB1vxWnXnRo2sPSL78pWJG8xEM+1Tu19z0deg==", + "dev": true, + "requires": { + "deglob": "^4.0.1", + "get-stdin": "^7.0.0", + "minimist": "^1.2.5", + "pkg-conf": "^3.1.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5afa0dc --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "wp-login-locker", + "version": "2.0.0", + "private": true, + "author": "Austin Passy ", + "scripts": { + "travis": "if [ \"$TRAVIS_COMMIT_RANGE\" ]; then standard $TRAVIS_COMMIT_RANGE; else standard; fi", + "eslint": "./bin/eslint.sh" + }, + "standard": { + "ignore": [], + "globals": [ + "_", + "jQuery", + "wp" + ] + }, + "devDependencies": { + "eslint": "~6.4.0", + "eslint-plugin-import": "^2.22.0", + "standard": "^14.3.4" + } +} From ab02036958d191ae0ff76912d77c87dfaa28bdc1 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Sun, 13 Sep 2020 12:03:42 -0700 Subject: [PATCH 18/33] Add tests. --- helpers.php | 2 + tests/unit/Login/WpLoginTest.php | 127 +++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 tests/unit/Login/WpLoginTest.php diff --git a/helpers.php b/helpers.php index 97c80dc..986d783 100644 --- a/helpers.php +++ b/helpers.php @@ -7,6 +7,8 @@ */ function terminate(): void { +// $function = \apply_filters('thefrosty/wp-login-locker/exit_handler', 'exit'); +// \call_user_func($function); \session_write_close(); exit; } diff --git a/tests/unit/Login/WpLoginTest.php b/tests/unit/Login/WpLoginTest.php new file mode 100644 index 0000000..5989269 --- /dev/null +++ b/tests/unit/Login/WpLoginTest.php @@ -0,0 +1,127 @@ +wpLogin = new WpLogin(); + $this->wpLogin->setPlugin($this->plugin); + $this->wpLogin->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->wpLogin); + } + + public function tearDown(): void + { + unset($this->wpLogin); + parent::tearDown(); + } + + /** + * Test new Login(). + */ + public function testConstructor(): void + { + $constants = $this->reflection->getConstants(); + $this->assertIsArray($constants); + $this->assertCount(6, $constants); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'addHooks')); + $provider = $this->getMockProvider(WpLogin::class); + $provider->expects($this->exactly(2)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var WpLogin $provider */ + $provider->addHooks(); + } + + /** + * Test activate(). + */ + public function testActivateNoUser(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'activate')); + try { + $activate = $this->reflection->getMethod('activate'); + $activate->setAccessible(true); + \do_action('activate_' . $this->wpLogin->getPlugin()->getFile(), $activate->invoke($this->wpLogin)); + $this->assertNull($activate->invoke($this->wpLogin)); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test activate(). + */ + public function testActivate(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'activate')); + /** + * Need to test against headers already sent. + */ +// try { +// $user_id = self::factory()->user->create(); +// \wp_set_current_user($user_id); +// $activate = $this->reflection->getMethod('activate'); +// $activate->setAccessible(true); +// \do_action('activate_' . $this->wpLogin->getPlugin()->getFile(), $activate->invoke($this->wpLogin)); +// $this->assertNull($activate->invoke($this->wpLogin)); +// $actual = \get_user_meta($user_id, LoginLocker::LAST_LOGIN_IP_META_KEY, true); +// $this->assertNotEmpty($actual); +// \delete_user_meta($user_id, LoginLocker::LAST_LOGIN_IP_META_KEY); +// } catch (\ReflectionException $exception) { +// $this->assertInstanceOf(\ReflectionException::class, $exception); +// $this->markAsRisky(); +// } + } + + /** + * Test loginAuthCheck(). + */ + public function testLoginAuthCheck(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'loginAuthCheck')); + /** + * Need to fix terminate() -> exit(). + */ +// try { +// \do_action('login_init'); +// $loginAuthCheck = $this->reflection->getMethod('loginAuthCheck'); +// $loginAuthCheck->setAccessible(true); +// $this->assertNull($loginAuthCheck->invoke($this->wpLogin)); +// } catch (\ReflectionException $exception) { +// $this->assertInstanceOf(\ReflectionException::class, $exception); +// $this->markAsRisky(); +// } + } +} From 3c572b6946657b909ef9a1ba52cfeddbbcd807ad Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 09:13:56 -0700 Subject: [PATCH 19/33] Change coverage report location. --- .gitignore | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4c8aa6b..6a8d042 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .DS_Store /.idea -coverage/ +tests/results/ node_modules/ tests/clover.xml /wordpress diff --git a/composer.json b/composer.json index 623af91..89974f6 100755 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ "bash ./bin/phpmd.sh" ], "phpunit": [ - "./vendor/bin/phpunit --colors --verbose --coverage-html ./coverage && php ./tests/clover-results.php ./tests/clover.xml 36" + "./vendor/bin/phpunit --colors --verbose --coverage-html ./tests/results && php ./tests/clover-results.php ./tests/clover.xml 36" ], "eslint": [ "npm run eslint" From 69bdef6a6912f01d7a3b79b0920784225440ae17 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 09:17:27 -0700 Subject: [PATCH 20/33] Keep xdebug. --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9cea969..f481305 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,13 +34,6 @@ install: - export PATH="$HOME/.composer/vendor/bin:$PATH" - export DEV_BIN_PATH=bin - source $DEV_BIN_PATH/create-all-branches.sh - - | - # Remove Xdebug for a huge performance increase: - if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then - phpenv config-rm xdebug.ini - else - echo "xdebug.ini does not exist" - fi - composer update --prefer-dist --no-interaction --prefer-stable --no-suggest before_script: From 9cf5d72080a106fb12edf703e648d04df3fc8138 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:21:32 -0700 Subject: [PATCH 21/33] Switch exit to wp_die. --- helpers.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/helpers.php b/helpers.php index 986d783..3c84b08 100644 --- a/helpers.php +++ b/helpers.php @@ -7,8 +7,6 @@ */ function terminate(): void { -// $function = \apply_filters('thefrosty/wp-login-locker/exit_handler', 'exit'); -// \call_user_func($function); \session_write_close(); - exit; + \wp_die(); } From 3a2926c04132142556a2824c85c2329c4e6716cc Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:21:47 -0700 Subject: [PATCH 22/33] Code updates for PHP 7+ --- src/Login/WpLogin.php | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Login/WpLogin.php b/src/Login/WpLogin.php index e3173cf..e11a1fb 100644 --- a/src/Login/WpLogin.php +++ b/src/Login/WpLogin.php @@ -72,7 +72,7 @@ protected function loginAuthCheck(): void if ($this->getRequest()->query->has(self::AUTH_CHECK_KEY) && !empty($this->getRequest()->query->get(self::AUTH_CHECK_KEY)) ) { - list($user, $field) = \array_values( + [$user, $field] = \array_values( $this->getUserBy( \sanitize_text_field( \wp_unslash($this->getRequest()->query->get(self::AUTH_CHECK_KEY)) @@ -91,9 +91,9 @@ protected function loginAuthCheck(): void } elseif ($this->getRequest()->cookies->has(self::COOKIE_NAME)) { // Validate the cookie $cookie = $this->decrypt($this->getRequest()->cookies->get(self::COOKIE_NAME), self::ENCRYPTION_KEY); - list(, $cookie_value) = \explode(self::ENCRYPTION_DELIMITER, $cookie); + [, $cookie_value] = \explode(self::ENCRYPTION_DELIMITER, $cookie); if (!empty($cookie_value)) { - list($user) = \array_values( + [$user] = \array_values( $this->getUserBy($cookie_value) ); if ($user instanceof \WP_User) { @@ -101,7 +101,9 @@ protected function loginAuthCheck(): void } else { // Maybe the user changed their login name or email, delete the cookie. unset($_COOKIE[self::COOKIE_NAME]); - \setcookie(self::COOKIE_NAME, '', \time() - \HOUR_IN_SECONDS); + if (!\headers_sent()) { + \setcookie(self::COOKIE_NAME, '', \time() - \HOUR_IN_SECONDS); + } } } } @@ -204,19 +206,20 @@ private function getCookieValue(string $value): string private function setLoginCookie(\WP_User $user, string $field): void { /** - * Dev note, you can't use Symfony's - * Response()->headers->setCookie( new Cookie( 'name', 'value' ) ) because it - * sends the headers, which then makes the login page throw a message notice + * Dev note, you can't use Symfony's Response()->headers->setCookie( new Cookie( 'name', 'value' ) ) + * because it sends the headers, which then makes the login page throw a message notice * that the headers have already been sent. So, default `setcookie` is used. */ - \setcookie( - self::COOKIE_NAME, - $this->getCookieValue($user->$field), - \strtotime(self::COOKIE_EXPIRE), - \COOKIEPATH, - \is_string(\COOKIE_DOMAIN) ? \COOKIE_DOMAIN : \parse_url(\home_url(), PHP_URL_HOST), - ('https' === \parse_url(\wp_login_url(), \PHP_URL_SCHEME)) - ); + if (!\headers_sent()) { + \setcookie( + self::COOKIE_NAME, + $this->getCookieValue($user->$field), + \strtotime(self::COOKIE_EXPIRE), + \COOKIEPATH, + \is_string(\COOKIE_DOMAIN) ? \COOKIE_DOMAIN : \parse_url(\home_url(), PHP_URL_HOST), + ('https' === \parse_url(\wp_login_url(), \PHP_URL_SCHEME)) + ); + } } /** From 72e753cb583b6f2e365051e721fa0e4da3be2a5c Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:22:09 -0700 Subject: [PATCH 23/33] Change from exit to wp_die and add filter for unit tests. --- templates/login/wp-login.php | 2 +- tests/bootstrap.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/templates/login/wp-login.php b/templates/login/wp-login.php index 3fff676..7eb5294 100644 --- a/templates/login/wp-login.php +++ b/templates/login/wp-login.php @@ -1,5 +1,5 @@

Date: Mon, 14 Sep 2020 12:22:27 -0700 Subject: [PATCH 24/33] Update test. --- tests/unit/Login/WpLoginTest.php | 161 +++++++++++++++++++++++++------ 1 file changed, 130 insertions(+), 31 deletions(-) diff --git a/tests/unit/Login/WpLoginTest.php b/tests/unit/Login/WpLoginTest.php index 5989269..cb8e3f1 100644 --- a/tests/unit/Login/WpLoginTest.php +++ b/tests/unit/Login/WpLoginTest.php @@ -86,42 +86,141 @@ public function testActivateNoUser(): void public function testActivate(): void { $this->assertTrue(\method_exists($this->wpLogin, 'activate')); - /** - * Need to test against headers already sent. - */ -// try { -// $user_id = self::factory()->user->create(); -// \wp_set_current_user($user_id); -// $activate = $this->reflection->getMethod('activate'); -// $activate->setAccessible(true); -// \do_action('activate_' . $this->wpLogin->getPlugin()->getFile(), $activate->invoke($this->wpLogin)); -// $this->assertNull($activate->invoke($this->wpLogin)); -// $actual = \get_user_meta($user_id, LoginLocker::LAST_LOGIN_IP_META_KEY, true); -// $this->assertNotEmpty($actual); -// \delete_user_meta($user_id, LoginLocker::LAST_LOGIN_IP_META_KEY); -// } catch (\ReflectionException $exception) { -// $this->assertInstanceOf(\ReflectionException::class, $exception); -// $this->markAsRisky(); -// } + try { + $user_id = self::factory()->user->create(); + \wp_set_current_user($user_id); + $activate = $this->reflection->getMethod('activate'); + $activate->setAccessible(true); + \do_action('activate_' . $this->wpLogin->getPlugin()->getFile(), $activate->invoke($this->wpLogin)); + $this->assertNull($activate->invoke($this->wpLogin)); + $actual = \get_user_meta($user_id, LoginLocker::LAST_LOGIN_IP_META_KEY, true); + $this->assertNotEmpty($actual); + \delete_user_meta($user_id, LoginLocker::LAST_LOGIN_IP_META_KEY); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test loginAuthCheck(). With logout action + */ + public function testLoginAuthCheckWithLogoutAction(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'loginAuthCheck')); + try { + $this->wpLogin->getRequest()->query->set('action', 'logout'); + $this->wpLogin->getRequest()->query->set('_wpnonce', \wp_create_nonce('log-out')); + $loginAuthCheck = $this->reflection->getMethod('loginAuthCheck'); + $loginAuthCheck->setAccessible(true); + $this->assertNull($loginAuthCheck->invoke($this->wpLogin)); + $this->assertEquals(0, \did_action('wp_verify_nonce_failed')); + $this->wpLogin->getRequest()->query->remove('action'); + $this->wpLogin->getRequest()->query->remove('_wpnonce'); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test loginAuthCheck(). With auth check query key + */ + public function testLoginAuthCheckWithAuthKey(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'loginAuthCheck')); + try { + $user = self::factory()->user->create_and_get(); + $this->wpLogin->getRequest()->query->set(WpLogin::AUTH_CHECK_KEY, $user->user_login); + $loginAuthCheck = $this->reflection->getMethod('loginAuthCheck'); + $loginAuthCheck->setAccessible(true); + $this->assertNull($loginAuthCheck->invoke($this->wpLogin)); + $this->wpLogin->getRequest()->query->remove(WpLogin::AUTH_CHECK_KEY); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } } /** - * Test loginAuthCheck(). + * Test loginAuthCheck(). With saved cookie */ - public function testLoginAuthCheck(): void + public function testLoginAuthCheckWithCookie(): void { $this->assertTrue(\method_exists($this->wpLogin, 'loginAuthCheck')); - /** - * Need to fix terminate() -> exit(). - */ -// try { -// \do_action('login_init'); -// $loginAuthCheck = $this->reflection->getMethod('loginAuthCheck'); -// $loginAuthCheck->setAccessible(true); -// $this->assertNull($loginAuthCheck->invoke($this->wpLogin)); -// } catch (\ReflectionException $exception) { -// $this->assertInstanceOf(\ReflectionException::class, $exception); -// $this->markAsRisky(); -// } + try { + $user = self::factory()->user->create_and_get(); + $cookieValue = $this->reflection->getMethod('getCookieValue'); + $cookieValue->setAccessible(true); + $this->wpLogin->getRequest()->cookies->set( + WpLogin::COOKIE_NAME, + $cookieValue->invoke($this->wpLogin, $user->user_email) + ); + $loginAuthCheck = $this->reflection->getMethod('loginAuthCheck'); + $loginAuthCheck->setAccessible(true); + $this->assertNull($loginAuthCheck->invoke($this->wpLogin)); + \wp_update_user(['ID' => $user->ID, 'user_email' => 'anewemail@email.test']); + $this->go_to(\wp_login_url()); + try { + $loginAuthCheck->invoke($this->wpLogin); + } catch (\Throwable $exception) { + $this->assertInstanceOf(\WPDieException::class, $exception); + if (\ob_get_level()) { + \ob_get_clean(); + } + } + $this->wpLogin->getRequest()->cookies->remove(WpLogin::COOKIE_NAME); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test lostPasswordMessage(). + */ + public function testLostPasswordMessage(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'lostPasswordMessage')); + try { + $lostPasswordMessage = $this->reflection->getMethod('lostPasswordMessage'); + $lostPasswordMessage->setAccessible(true); + $expected = 'This is a message'; + $actual = $lostPasswordMessage->invoke($this->wpLogin, $expected); + $this->assertIsString($actual); + $this->assertSame($expected, $actual); + $_GET['action'] = 'lostpassword'; + $this->go_to(\add_query_arg('action', 'lostpassword', \wp_login_url())); + $actual = $lostPasswordMessage->invoke($this->wpLogin, \wpautop($expected)); + $this->assertIsString($actual); + $this->assertStringNotContainsString('class="message"', $actual); + unset($_GET['action']); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test noAuthLoginHtml(). + */ + public function testNoAuthLoginHtml(): void + { + $this->assertTrue(\method_exists($this->wpLogin, 'noAuthLoginHtml')); + try { + $noAuthLoginHtml = $this->reflection->getMethod('noAuthLoginHtml'); + $noAuthLoginHtml->setAccessible(true); + try { + $noAuthLoginHtml->invoke($this->wpLogin); + } catch (\Throwable $exception) { + $this->assertInstanceOf(\WPDieException::class, $exception); + if (\ob_get_level()) { + \ob_get_clean(); + } + } + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } } } From b7141d3efead800e9f817f103399b136b6b00ed3 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:25:52 -0700 Subject: [PATCH 25/33] phpcs cleanup. --- templates/login/wp-login.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/templates/login/wp-login.php b/templates/login/wp-login.php index 7eb5294..efffc11 100644 --- a/templates/login/wp-login.php +++ b/templates/login/wp-login.php @@ -3,8 +3,10 @@ function_exists('login_header') || wp_die(); login_header(__('Log In'), '', ''); ?>

From 8811d86bcc821b86ae1203466de527989692fe4e Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:44:18 -0700 Subject: [PATCH 26/33] Add tests for WpSignup. --- src/WpCore/WpSignup.php | 5 +- tests/unit/Login/WpLoginTest.php | 1 - tests/unit/WpCore/WpSignupTest.php | 75 ++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 tests/unit/WpCore/WpSignupTest.php diff --git a/src/WpCore/WpSignup.php b/src/WpCore/WpSignup.php index bc8f93a..b35fe75 100644 --- a/src/WpCore/WpSignup.php +++ b/src/WpCore/WpSignup.php @@ -34,7 +34,10 @@ protected function redirectWpSignup(): void { // Don't allow POST requests to the wp-signup.php page if (!empty($this->getRequest()->request->all())) { - \wp_die(\esc_html__('Ah ah ah, you didn\'t say the magic word.', 'wp-login-locker'), 'Access Denied'); + \wp_die( + \esc_html__('Ah ah ah, you didn\'t say the magic word.', 'wp-login-locker'), + \esc_html__('Access Denied', 'wp-login-locker') + ); } \wp_safe_redirect(\network_home_url(), Response::HTTP_PERMANENTLY_REDIRECT); terminate(); diff --git a/tests/unit/Login/WpLoginTest.php b/tests/unit/Login/WpLoginTest.php index cb8e3f1..b78af96 100644 --- a/tests/unit/Login/WpLoginTest.php +++ b/tests/unit/Login/WpLoginTest.php @@ -6,7 +6,6 @@ use TheFrosty\Tests\WpLoginLocker\TestCase; use TheFrosty\WpLoginLocker\Login\WpLogin; use TheFrosty\WpLoginLocker\LoginLocker; -use TheFrosty\WpLoginLocker\Settings\Settings; /** * Class WpLoginTest diff --git a/tests/unit/WpCore/WpSignupTest.php b/tests/unit/WpCore/WpSignupTest.php new file mode 100644 index 0000000..72a4b47 --- /dev/null +++ b/tests/unit/WpCore/WpSignupTest.php @@ -0,0 +1,75 @@ +wpSignup = new WpSignup(); + $this->wpSignup->setPlugin($this->plugin); + $this->wpSignup->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->wpSignup); + } + + public function tearDown(): void + { + unset($this->wpSignup); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->wpSignup, 'addHooks')); + $provider = $this->getMockProvider(WpSignup::class); + $provider->expects($this->exactly(1)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var WpSignup $provider */ + $provider->addHooks(); + } + + /** + * Test redirectWpSignup(). + */ + public function testRedirectWpSignup(): void + { + $this->assertTrue(\method_exists($this->wpSignup, 'redirectWpSignup')); + try { + $this->wpSignup->getRequest()->request->set('user_name', 'admin'); + $redirectWpSignup = $this->reflection->getMethod('redirectWpSignup'); + $redirectWpSignup->setAccessible(true); + try { + $redirectWpSignup->invoke($this->wpSignup); + } catch (\Throwable $exception) { + $this->assertInstanceOf(\WPDieException::class, $exception); + } + $this->wpSignup->getRequest()->request->remove('user_name'); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } +} From 0bf035dffa75aa975f2cf2dd18e993af9c2035c3 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 15:34:44 -0700 Subject: [PATCH 27/33] Fix malformed wpdb query, thanks unit tests. --- src/Utilities/UserMetaCleanup.php | 5 +- tests/unit/Utilities/GeoUtilTraitTest.php | 63 ++++++++++++++++++++ tests/unit/Utilities/UserMetaCleanupTest.php | 56 +++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 tests/unit/Utilities/GeoUtilTraitTest.php create mode 100644 tests/unit/Utilities/UserMetaCleanupTest.php diff --git a/src/Utilities/UserMetaCleanup.php b/src/Utilities/UserMetaCleanup.php index eb80d5a..b01e95f 100644 --- a/src/Utilities/UserMetaCleanup.php +++ b/src/Utilities/UserMetaCleanup.php @@ -104,7 +104,10 @@ private function delete(array $meta_ids): void { global $wpdb; $table = \_get_meta_table('user'); - $query = "DELETE FROM $table WHERE umeta_id IN( " . \implode(',', $meta_ids) . ' )'; + $query = $wpdb->prepare( + "DELETE FROM $table WHERE umeta_id IN(%s)", + \implode(',', \array_map('\trim', $meta_ids)) + ); $wpdb->query($query); } diff --git a/tests/unit/Utilities/GeoUtilTraitTest.php b/tests/unit/Utilities/GeoUtilTraitTest.php new file mode 100644 index 0000000..c7472f6 --- /dev/null +++ b/tests/unit/Utilities/GeoUtilTraitTest.php @@ -0,0 +1,63 @@ +class = new class() implements HttpFoundationRequestInterface { + use GeoUtilTrait, HttpFoundationRequestTrait; + }; + $this->class->setRequest(Request::createFromGlobals()); + } + + public function tearDown(): void + { + unset($this->class); + parent::tearDown(); + } + + /** + * Test getIP(). + */ + public function testGetIP(): void + { + $HTTP_CLIENT_IP = '127.0.0.1'; + $HTTP_X_FORWARDED_FOR = '127.0.0.1'; + $this->assertIsString($this->class->getIP()); + $this->class->getRequest()->server->set('HTTP_CLIENT_IP', $HTTP_CLIENT_IP); + $this->assertIsString($this->class->getIP()); + $this->class->getRequest()->server->remove('HTTP_CLIENT_IP'); + $this->class->getRequest()->server->set('HTTP_X_FORWARDED_FOR', $HTTP_X_FORWARDED_FOR); + $this->assertIsString($this->class->getIP()); + $this->class->getRequest()->server->remove('HTTP_X_FORWARDED_FOR'); + } + + /** + * Test getUserAgent(). + */ + public function testGetUserAgent(): void + { + $userAgent = $this->class->getUserAgent(); + $this->assertIsString($userAgent); + } +} diff --git a/tests/unit/Utilities/UserMetaCleanupTest.php b/tests/unit/Utilities/UserMetaCleanupTest.php new file mode 100644 index 0000000..2ebc330 --- /dev/null +++ b/tests/unit/Utilities/UserMetaCleanupTest.php @@ -0,0 +1,56 @@ +user_id = self::factory()->user->create(); + foreach (\range(1, 15) as $i) { + \update_user_meta($this->user_id, LoginLocker::LAST_LOGIN_IP_META_KEY, \sprintf('10.0.0.%d', $i)); + } + $this->userMetaCleanup = new UserMetaCleanup( $this->user_id); + } + + public function tearDown(): void + { + \delete_user_meta($this->user_id, LoginLocker::LAST_LOGIN_IP_META_KEY); + unset($this->userMetaCleanup, $this->user_id); + parent::tearDown(); + } + + /** + * Test cleanup(). + */ + public function testCleanup(): void + { + $this->assertTrue(\method_exists($this->userMetaCleanup, 'cleanup')); + $this->userMetaCleanup->cleanup(); + } +} From c54ccc304a8bfb54d09850edae7a94c1deb6ee5c Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 17:00:39 -0700 Subject: [PATCH 28/33] Add code coverage report and badge. --- .travis.yml | 3 +++ README.md | 1 + 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index f481305..d499dbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,6 +43,9 @@ before_script: script: - composer tests +after_success: + - bash <(curl -s https://codecov.io/bash) + notifications: email: on_success: never diff --git a/README.md b/README.md index e605eab..064fd8c 100755 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Total Downloads](https://img.shields.io/packagist/dt/thefrosty/wp-login-locker.svg)](https://packagist.org/packages/thefrosty/wp-login-locker) [![License](https://img.shields.io/packagist/l/thefrosty/wp-login-locker.svg)](https://packagist.org/thefrosty/thefrosty/wp-login-locker) [![Build Status](https://travis-ci.org/thefrosty/wp-login-locker.svg?branch=master)](https://travis-ci.org/thefrosty/wp-login-locker) +[![codecov](https://codecov.io/gh/thefrosty/wp-login-locker/branch/develop/graph/badge.svg)](https://codecov.io/gh/thefrosty/wp-login-locker) Disable direct access to your sites /wp-login.php script plus user notifications based on actions. From d41a4d3ea0da33742cafcb44bc3ff2fed081f1d8 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 17:21:48 -0700 Subject: [PATCH 29/33] Template cleanup. --- templates/user-profile/email-notification.php | 6 +++++- templates/user-profile/last-login.php | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/templates/user-profile/email-notification.php b/templates/user-profile/email-notification.php index a97c7dc..d0883e9 100644 --- a/templates/user-profile/email-notification.php +++ b/templates/user-profile/email-notification.php @@ -1,7 +1,11 @@ -

diff --git a/templates/user-profile/last-login.php b/templates/user-profile/last-login.php index f963e36..d1f353c 100644 --- a/templates/user-profile/last-login.php +++ b/templates/user-profile/last-login.php @@ -1,4 +1,9 @@ -getLastLoginIp($user->ID); From 7a81e7c20d499dc9061d4463c3a148cf5eed23e3 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 17:21:59 -0700 Subject: [PATCH 30/33] TEst cleanup. --- tests/unit/Utilities/UserMetaCleanupTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/Utilities/UserMetaCleanupTest.php b/tests/unit/Utilities/UserMetaCleanupTest.php index 2ebc330..8c541cf 100644 --- a/tests/unit/Utilities/UserMetaCleanupTest.php +++ b/tests/unit/Utilities/UserMetaCleanupTest.php @@ -33,9 +33,9 @@ public function setUp(): void parent::setUp(); $this->user_id = self::factory()->user->create(); foreach (\range(1, 15) as $i) { - \update_user_meta($this->user_id, LoginLocker::LAST_LOGIN_IP_META_KEY, \sprintf('10.0.0.%d', $i)); + \add_user_meta($this->user_id, LoginLocker::LAST_LOGIN_IP_META_KEY, \sprintf('10.0.0.%d', $i), false); } - $this->userMetaCleanup = new UserMetaCleanup( $this->user_id); + $this->userMetaCleanup = new UserMetaCleanup($this->user_id); } public function tearDown(): void From 5d6147e432f690f34953a0febe821c3c004cf6ff Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 17:22:05 -0700 Subject: [PATCH 31/33] Add more tests. --- .../EmailNotificationSettingTest.php | 83 ++++++++ tests/unit/UserProfile/LastLoginTest.php | 193 ++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 tests/unit/UserProfile/EmailNotificationSettingTest.php create mode 100644 tests/unit/UserProfile/LastLoginTest.php diff --git a/tests/unit/UserProfile/EmailNotificationSettingTest.php b/tests/unit/UserProfile/EmailNotificationSettingTest.php new file mode 100644 index 0000000..f9274ce --- /dev/null +++ b/tests/unit/UserProfile/EmailNotificationSettingTest.php @@ -0,0 +1,83 @@ +emailNotificationSetting = new EmailNotificationSetting(); + $this->emailNotificationSetting->setPlugin($this->plugin); + $this->emailNotificationSetting->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->emailNotificationSetting); + } + + public function tearDown(): void + { + unset($this->emailNotificationSetting); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->emailNotificationSetting, 'addHooks')); + $provider = $this->getMockProvider(EmailNotificationSetting::class); + $provider->expects($this->exactly(5)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var EmailNotificationSetting $provider */ + $provider->addHooks(); + } + + /** + * Test showExtraUserFields(). + */ + public function testShowExtraUserFields(): void + { + $this->assertTrue(\method_exists($this->emailNotificationSetting, 'showExtraUserFields')); + try { + $showExtraUserFields = $this->reflection->getMethod('showExtraUserFields'); + $showExtraUserFields->setAccessible(true); + \ob_start(); + $showExtraUserFields->invoke($this->emailNotificationSetting, null); + $actual = \ob_get_clean(); + $this->assertEmpty($actual); + $this->assertStringNotContainsString(LoginLocker::USER_EMAIL_META_KEY, $actual); + $this->assertStringNotContainsString('Login Notifications', $actual); + $user = self::factory()->user->create_and_get(); + \ob_start(); + $showExtraUserFields->invoke($this->emailNotificationSetting, $user); + $actual = \ob_get_clean(); + $this->assertIsString($actual); + $this->assertStringContainsString(LoginLocker::USER_EMAIL_META_KEY, $actual); + $this->assertStringContainsString('Login Notifications', $actual); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } +} diff --git a/tests/unit/UserProfile/LastLoginTest.php b/tests/unit/UserProfile/LastLoginTest.php new file mode 100644 index 0000000..909e02d --- /dev/null +++ b/tests/unit/UserProfile/LastLoginTest.php @@ -0,0 +1,193 @@ +lastLogin = new LastLogin(); + $this->lastLogin->setPlugin($this->plugin); + $this->lastLogin->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->lastLogin); + } + + public function tearDown(): void + { + unset($this->lastLogin); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->lastLogin, 'addHooks')); + $provider = $this->getMockProvider(LastLogin::class); + $provider->expects($this->exactly(5)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var LastLogin $provider */ + $provider->addHooks(); + } + + /** + * Test getLastLoginIp(). + */ + public function testGetLastLoginIp(): void + { + $this->assertTrue(\method_exists($this->lastLogin, 'getLastLoginIp')); + try { + $getLastLoginIp = $this->reflection->getMethod('getLastLoginIp'); + $getLastLoginIp->setAccessible(true); + $user = self::factory()->user->create_and_get(); + $actual = $getLastLoginIp->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + foreach (\range(1, 4) as $i) { + \add_user_meta($user->ID, LoginLocker::LAST_LOGIN_IP_META_KEY, \sprintf('10.0.0.%d', $i), false); + } + $actual = $getLastLoginIp->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + \delete_user_meta($user->ID, LoginLocker::LAST_LOGIN_IP_META_KEY); + $actual = $getLastLoginIp->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + $this->assertSame('No data', $actual); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test getCurrentLoginIp(). + */ + public function testGetCurrentLoginIp(): void + { + $this->assertTrue(\method_exists($this->lastLogin, 'getCurrentLoginIp')); + try { + $getCurrentLoginIp = $this->reflection->getMethod('getCurrentLoginIp'); + $getCurrentLoginIp->setAccessible(true); + $user = self::factory()->user->create_and_get(); + $actual = $getCurrentLoginIp->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + foreach (\range(1, 4) as $i) { + \add_user_meta($user->ID, LoginLocker::LAST_LOGIN_IP_META_KEY, \sprintf('10.0.0.%d', $i), false); + } + $actual = $getCurrentLoginIp->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + $this->assertSame('10.0.0.4', $actual); + \delete_user_meta($user->ID, LoginLocker::LAST_LOGIN_IP_META_KEY); + $actual = $getCurrentLoginIp->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + $this->assertSame('No data', $actual); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test getLastLogin(). + */ + public function testGetLastLogin(): void + { + $this->assertTrue(\method_exists($this->lastLogin, 'getLastLogin')); + try { + $getLastLogin = $this->reflection->getMethod('getLastLogin'); + $getLastLogin->setAccessible(true); + $user = self::factory()->user->create_and_get(); + $actual = $getLastLogin->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + foreach (\range(1, 4) as $i) { + \add_user_meta($user->ID, LoginLocker::LAST_LOGIN_TIME_META_KEY, \strtotime('-%s minutes', ++$i)); + } + $actual = $getLastLogin->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + \delete_user_meta($user->ID, LoginLocker::LAST_LOGIN_TIME_META_KEY); + $actual = $getLastLogin->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + $this->assertSame('No data', $actual); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test getCurrentLogin(). + */ + public function testGetCurrentLogin(): void + { + $this->assertTrue(\method_exists($this->lastLogin, 'getCurrentLogin')); + try { + $getCurrentLogin = $this->reflection->getMethod('getCurrentLogin'); + $getCurrentLogin->setAccessible(true); + $user = self::factory()->user->create_and_get(); + $actual = $getCurrentLogin->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + foreach (\range(1, 4) as $i) { + \add_user_meta($user->ID, LoginLocker::LAST_LOGIN_TIME_META_KEY, \strtotime('-%s minutes', ++$i)); + } + $actual = $getCurrentLogin->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + \delete_user_meta($user->ID, LoginLocker::LAST_LOGIN_TIME_META_KEY); + $actual = $getCurrentLogin->invoke($this->lastLogin, $user->ID); + $this->assertIsString($actual); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test showExtraUserFields(). + */ + public function testShowExtraUserFields(): void + { + $this->assertTrue(\method_exists($this->lastLogin, 'showExtraUserFields')); + try { + $showExtraUserFields = $this->reflection->getMethod('showExtraUserFields'); + $showExtraUserFields->setAccessible(true); + \ob_start(); + $showExtraUserFields->invoke($this->lastLogin, null); + $actual = \ob_get_clean(); + $this->assertEmpty($actual); + $this->assertStringNotContainsString('Your recent login data', $actual); + $user = self::factory()->user->create_and_get(); + \ob_start(); + $showExtraUserFields->invoke($this->lastLogin, $user); + $actual = \ob_get_clean(); + $this->assertIsString($actual); + $this->assertStringContainsString('Your recent login data', $actual); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } +} From 65a04ddf1177d03c39f377d249d11a5a51ac3c57 Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 18:13:49 -0700 Subject: [PATCH 32/33] Add user profile test, which exposed a user access bug. --- src/UserProfile/UserProfile.php | 5 +- tests/unit/UserProfile/UserProfileTest.php | 147 +++++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 tests/unit/UserProfile/UserProfileTest.php diff --git a/src/UserProfile/UserProfile.php b/src/UserProfile/UserProfile.php index 37e1122..81bce42 100644 --- a/src/UserProfile/UserProfile.php +++ b/src/UserProfile/UserProfile.php @@ -51,12 +51,13 @@ protected function doUserProfileAction(\WP_User $user = null): void } /** - * If the inherited class set's fields, save them. + * If the inherited class set's fields, save them. Set to current users who can `read` meaning log in + * to the admin. * @param int $user_id The current users ID. */ protected function saveExtraProfileFields($user_id): void { - if (empty($this->fields) || !current_user_can('edit_user', $user_id)) { + if (empty($this->fields) || !\current_user_can('read')) { return; } diff --git a/tests/unit/UserProfile/UserProfileTest.php b/tests/unit/UserProfile/UserProfileTest.php new file mode 100644 index 0000000..f4d0f8d --- /dev/null +++ b/tests/unit/UserProfile/UserProfileTest.php @@ -0,0 +1,147 @@ +userProfile = new class() extends UserProfile { + public function __construct() + { + $this->fields = [ + 'someDummyKeyToSave', + 'iShouldBeDeleted', + ]; + } + }; + $this->userProfile->setPlugin($this->plugin); + $this->userProfile->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->userProfile); + } + + public function tearDown(): void + { + unset($this->userProfile); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->userProfile, 'addHooks')); + $provider = $this->getMockProvider(UserProfile::class); + $provider->expects($this->exactly(4)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var UserProfile $provider */ + $provider->addHooks(); + } + + /** + * Test doUserProfileAction(). + */ + public function testDoUserProfileAction(): void + { + $this->assertTrue(\method_exists($this->userProfile, 'doUserProfileAction')); + try { + $doUserProfileAction = $this->reflection->getMethod('doUserProfileAction'); + $doUserProfileAction->setAccessible(true); + $user = self::factory()->user->create_and_get(); + \ob_start(); + $doUserProfileAction->invoke($this->userProfile, null); + $actual = \ob_get_clean(); + $this->assertStringContainsString('Login Locker Settings', $actual); + $this->assertEquals(1, \did_action(UserProfile::USER_PROFILE_HOOK)); + \ob_start(); + $doUserProfileAction->invoke($this->userProfile, $user); + $actual = \ob_get_clean(); + $this->assertStringNotContainsString('Login Locker Settings', $actual); + $this->assertEquals(1, \did_action(UserProfile::USER_PROFILE_HOOK)); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test saveExtraProfileFields(). + */ + public function testSaveExtraProfileFields(): void + { + $this->assertTrue(\method_exists($this->userProfile, 'saveExtraProfileFields')); + try { + $getUserMeta = $this->reflection->getMethod('getUserMeta'); + $getUserMeta->setAccessible(true); + $fields = $this->reflection->getProperty('fields'); + $fields->setAccessible(true); + $key = $fields->getValue($this->userProfile)[0]; + $saveExtraProfileFields = $this->reflection->getMethod('saveExtraProfileFields'); + $saveExtraProfileFields->setAccessible(true); + $user = self::factory()->user->create_and_get(); + \wp_set_current_user($user->ID); + $this->assertFalse( + \in_array( + 'value', + $getUserMeta->invoke($this->userProfile, $user->ID, $key), + true + ) + ); + $this->userProfile->getRequest()->request->set($key, 'value'); + $saveExtraProfileFields->invoke($this->userProfile, $user->ID); + $this->assertTrue( + \in_array( + 'value', + $getUserMeta->invoke($this->userProfile, $user->ID, $key), + true + ) + ); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } + + /** + * Test saveExtraProfileFields(). + */ + public function testSaveExtraProfileFieldsEmpty(): void + { + $this->assertTrue(\method_exists($this->userProfile, 'saveExtraProfileFields')); + try { + $saveExtraProfileFields = $this->reflection->getMethod('saveExtraProfileFields'); + $saveExtraProfileFields->setAccessible(true); + $user = self::factory()->user->create_and_get(); + $saveExtraProfileFields->invoke($this->userProfile, $user->ID); + \wp_delete_user($user->ID); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } +} From c2dd9303e822ec8e14dede37dcbe4eb4430688fc Mon Sep 17 00:00:00 2001 From: Austin Passy <367897+thefrosty@users.noreply.github.com> Date: Mon, 14 Sep 2020 18:34:48 -0700 Subject: [PATCH 33/33] Add settings test, and set min coverage to 75%. --- composer.json | 2 +- tests/unit/Settings/SettingsTest.php | 88 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/unit/Settings/SettingsTest.php diff --git a/composer.json b/composer.json index 89974f6..9761ba7 100755 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ "bash ./bin/phpmd.sh" ], "phpunit": [ - "./vendor/bin/phpunit --colors --verbose --coverage-html ./tests/results && php ./tests/clover-results.php ./tests/clover.xml 36" + "./vendor/bin/phpunit --colors --verbose --coverage-html ./tests/results && php ./tests/clover-results.php ./tests/clover.xml 75" ], "eslint": [ "npm run eslint" diff --git a/tests/unit/Settings/SettingsTest.php b/tests/unit/Settings/SettingsTest.php new file mode 100644 index 0000000..49aeb4f --- /dev/null +++ b/tests/unit/Settings/SettingsTest.php @@ -0,0 +1,88 @@ +settings = new Settings(); + $this->settings->setPlugin($this->plugin); + $this->settings->setRequest(Request::createFromGlobals()); + $this->reflection = $this->getReflection($this->settings); + } + + public function tearDown(): void + { + unset($this->settings); + parent::tearDown(); + } + + /** + * Test addHooks(). + */ + public function testAddHooks(): void + { + $this->assertTrue(\method_exists($this->settings, 'addHooks')); + $provider = $this->getMockBuilder(Settings::class) + ->setMethods([self::METHOD_ADD_FILTER, 'getPlugin']) + ->getMock();; + $provider->expects($this->once()) + ->method('getPlugin') + ->willReturn($this->plugin); + $provider->expects($this->exactly(4)) + ->method(self::METHOD_ADD_FILTER) + ->willReturn(true); + /** @var LastLogin $provider */ + $provider->addHooks(); + } + + /** + * Test init(). + */ + public function testInit(): void + { + $this->assertTrue(\method_exists($this->settings, 'init')); + try { + $init = $this->reflection->getMethod('init'); + $init->setAccessible(true); + $WpSettingsApi = $this->getMockBuilder(WpSettingsApi::class) + ->setConstructorArgs([Settings::factory()]) + ->getMock(); + $SectionManager = $this->getMockBuilder(SectionManager::class) + ->setConstructorArgs([$WpSettingsApi]) + ->getMock(); + $FieldManager = $this->getMockBuilder(FieldManager::class)->getMock(); + $this->assertNull($init->invoke($this->settings, $SectionManager, $FieldManager, $WpSettingsApi)); + $this->go_to(\menu_page_url($this->reflection->getConstant('MENU_SLUG'))); + $init->invoke($this->settings, $SectionManager, $FieldManager, $WpSettingsApi); + } catch (\ReflectionException $exception) { + $this->assertInstanceOf(\ReflectionException::class, $exception); + $this->markAsRisky(); + } + } +}