From 94969e7f9737dc5813072a521c2659c5d159a44c Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Sat, 18 Feb 2017 12:27:21 +0000 Subject: [PATCH] Get Laravel Dusk installed using PhantomJS so it can work with Travis CI easily --- .travis.yml | 2 + app/Providers/AppServiceProvider.php | 5 +- composer.json | 17 +- composer.lock | 249 ++++++++-------- phpunit.xml | 10 +- tests/ArticlesTest.php | 78 ----- tests/Browser/ExampleTest.php | 23 ++ tests/Browser/Pages/HomePage.php | 40 +++ tests/Browser/Pages/Page.php | 20 ++ tests/Browser/screenshots/.gitignore | 2 + tests/ContactsTest.php | 52 ---- ...wserKitTest.php => CreatesApplication.php} | 14 +- tests/DuskTestCase.php | 35 +++ tests/Feature/ExampleTest.php | 22 ++ tests/IndieAuthTest.php | 79 ----- tests/MicropubClientTest.php | 69 ----- tests/MicropubTest.php | 271 ------------------ tests/NotesAdminTest.php | 33 --- tests/NotesTest.php | 182 ------------ tests/PlacesTest.php | 52 ---- tests/TestCase.php | 27 +- tests/TokenServiceTest.php | 43 --- tests/Unit/ExampleTest.php | 20 ++ tests/WebMentionsTest.php | 92 ------ tests/aaron.png | Bin 6527 -> 0 bytes 25 files changed, 314 insertions(+), 1123 deletions(-) delete mode 100644 tests/ArticlesTest.php create mode 100644 tests/Browser/ExampleTest.php create mode 100644 tests/Browser/Pages/HomePage.php create mode 100644 tests/Browser/Pages/Page.php create mode 100644 tests/Browser/screenshots/.gitignore delete mode 100644 tests/ContactsTest.php rename tests/{BrowserKitTest.php => CreatesApplication.php} (56%) create mode 100644 tests/DuskTestCase.php create mode 100644 tests/Feature/ExampleTest.php delete mode 100644 tests/IndieAuthTest.php delete mode 100644 tests/MicropubClientTest.php delete mode 100644 tests/MicropubTest.php delete mode 100644 tests/NotesAdminTest.php delete mode 100644 tests/NotesTest.php delete mode 100644 tests/PlacesTest.php delete mode 100644 tests/TokenServiceTest.php create mode 100644 tests/Unit/ExampleTest.php delete mode 100644 tests/WebMentionsTest.php delete mode 100644 tests/aaron.png diff --git a/.travis.yml b/.travis.yml index 6033e8fa..7c82d2a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,9 +38,11 @@ before_script: - php artisan migrate - php artisan db:seed - php artisan token:generate + - phantomjs --webserver=127.0.0.1:9515 & - php artisan serve & - sleep 5 # Give artisan some time to start serving script: - phpdbg -qrr vendor/bin/phpunit --coverage-text + - php artisan dusk - php artisan security:check diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3c5c8377..d3aa0488 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,7 @@ namespace App\Providers; use App\Tag; use App\Note; use Validator; +use Laravel\Dusk\DuskServiceProvider; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -54,6 +55,8 @@ class AppServiceProvider extends ServiceProvider */ public function register() { - // + if ($this->app->environment('local', 'testing')) { + $this->app->register(DuskServiceProvider::class); + } } } diff --git a/composer.json b/composer.json index 8b6e90f1..d37abaea 100644 --- a/composer.json +++ b/composer.json @@ -29,14 +29,12 @@ "laravel/tinker": "^1.0" }, "require-dev": { - "fzaninotto/faker": "~1.4", - "mockery/mockery": "0.9.*", - "phpunit/phpunit": "~5.7", - "symfony/css-selector": "3.1.*", - "symfony/dom-crawler": "3.1.*", "barryvdh/laravel-debugbar": "~2.0", + "fzaninotto/faker": "~1.4", "jakub-onderka/php-parallel-lint": "^0.9.2", - "laravel/browser-kit-testing": "^1.0" + "laravel/dusk": "^1.0", + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~5.7" }, "autoload": { "classmap": [ @@ -47,10 +45,9 @@ } }, "autoload-dev": { - "classmap": [ - "tests/TestCase.php", - "tests/BrowserKitTest.php" - ] + "psr-4": { + "Tests\\": "tests" + } }, "scripts": { "post-root-package-install": [ diff --git a/composer.lock b/composer.lock index eae3833f..56ab84cb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "16af842252275cac279c7f118ad6e6d4", + "content-hash": "e4236aef74a9e56de4a85dc50bf32dcf", "packages": [ { "name": "anahkiasen/underscore-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.22.4", + "version": "3.22.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "916f708c1a643f86f74eacd3c5be787b40d814f8" + "reference": "eea83aaac2b6c86f72a5c85c54d1839b70d4fd21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/916f708c1a643f86f74eacd3c5be787b40d814f8", - "reference": "916f708c1a643f86f74eacd3c5be787b40d814f8", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eea83aaac2b6c86f72a5c85c54d1839b70d4fd21", + "reference": "eea83aaac2b6c86f72a5c85c54d1839b70d4fd21", "shasum": "" }, "require": { @@ -134,7 +134,7 @@ "s3", "sdk" ], - "time": "2017-02-14T21:23:54+00:00" + "time": "2017-02-17T20:09:40+00:00" }, { "name": "barnabywalters/mf-cleaner", @@ -3280,16 +3280,16 @@ }, { "name": "symfony/console", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7a8405a9fc175f87fed8a3c40856b0d866d61936" + "reference": "0e5e6899f82230fcb1153bcaf0e106ffaa44b870" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7a8405a9fc175f87fed8a3c40856b0d866d61936", - "reference": "7a8405a9fc175f87fed8a3c40856b0d866d61936", + "url": "https://api.github.com/repos/symfony/console/zipball/0e5e6899f82230fcb1153bcaf0e106ffaa44b870", + "reference": "0e5e6899f82230fcb1153bcaf0e106ffaa44b870", "shasum": "" }, "require": { @@ -3339,20 +3339,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-02-06T12:04:21+00:00" + "time": "2017-02-16T14:07:22+00:00" }, { "name": "symfony/css-selector", - "version": "v3.1.10", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d" + "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d", - "reference": "722a87478a72d95dc2a3bcf41dc9c2d13fd4cb2d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f0e628f04fc055c934b3211cfabdb1c59eefbfaa", + "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa", "shasum": "" }, "require": { @@ -3361,7 +3361,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -3392,20 +3392,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2017-01-02T20:31:54+00:00" + "time": "2017-01-02T20:32:22+00:00" }, { "name": "symfony/debug", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "b4d9818f127c60ce21ed62c395da7df868dc8477" + "reference": "9b98854cb45bc59d100b7d4cc4cf9e05f21026b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/b4d9818f127c60ce21ed62c395da7df868dc8477", - "reference": "b4d9818f127c60ce21ed62c395da7df868dc8477", + "url": "https://api.github.com/repos/symfony/debug/zipball/9b98854cb45bc59d100b7d4cc4cf9e05f21026b9", + "reference": "9b98854cb45bc59d100b7d4cc4cf9e05f21026b9", "shasum": "" }, "require": { @@ -3449,11 +3449,11 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-01-28T02:37:08+00:00" + "time": "2017-02-16T16:34:18+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -3513,7 +3513,7 @@ }, { "name": "symfony/finder", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -3562,16 +3562,16 @@ }, { "name": "symfony/http-foundation", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e192b04de44aa1ed0e39d6793f7e06f5e0b672a0" + "reference": "a90da6dd679605d88c9803a57a6fc1fb7a19a6e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e192b04de44aa1ed0e39d6793f7e06f5e0b672a0", - "reference": "e192b04de44aa1ed0e39d6793f7e06f5e0b672a0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a90da6dd679605d88c9803a57a6fc1fb7a19a6e0", + "reference": "a90da6dd679605d88c9803a57a6fc1fb7a19a6e0", "shasum": "" }, "require": { @@ -3611,20 +3611,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-02-02T13:47:35+00:00" + "time": "2017-02-16T22:46:52+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "96443239baf674b143604fb87cb27cb01672ab77" + "reference": "4cd0d4bc31819095c6ef13573069f621eb321081" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/96443239baf674b143604fb87cb27cb01672ab77", - "reference": "96443239baf674b143604fb87cb27cb01672ab77", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4cd0d4bc31819095c6ef13573069f621eb321081", + "reference": "4cd0d4bc31819095c6ef13573069f621eb321081", "shasum": "" }, "require": { @@ -3693,7 +3693,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-02-06T13:15:19+00:00" + "time": "2017-02-16T23:59:56+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -3756,16 +3756,16 @@ }, { "name": "symfony/process", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32646a7cf53f3956c76dcb5c82555224ae321858" + "reference": "0ab87c1e7570b3534a6e51eb4ca8e9f6d7327856" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32646a7cf53f3956c76dcb5c82555224ae321858", - "reference": "32646a7cf53f3956c76dcb5c82555224ae321858", + "url": "https://api.github.com/repos/symfony/process/zipball/0ab87c1e7570b3534a6e51eb4ca8e9f6d7327856", + "reference": "0ab87c1e7570b3534a6e51eb4ca8e9f6d7327856", "shasum": "" }, "require": { @@ -3801,11 +3801,11 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-02-03T12:11:38+00:00" + "time": "2017-02-16T14:07:22+00:00" }, { "name": "symfony/routing", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", @@ -3880,16 +3880,16 @@ }, { "name": "symfony/translation", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "ca032cc56976d88b85e7386b17020bc6dc95dbc5" + "reference": "d6825c6bb2f1da13f564678f9f236fe8242c0029" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/ca032cc56976d88b85e7386b17020bc6dc95dbc5", - "reference": "ca032cc56976d88b85e7386b17020bc6dc95dbc5", + "url": "https://api.github.com/repos/symfony/translation/zipball/d6825c6bb2f1da13f564678f9f236fe8242c0029", + "reference": "d6825c6bb2f1da13f564678f9f236fe8242c0029", "shasum": "" }, "require": { @@ -3940,20 +3940,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-01-21T17:06:35+00:00" + "time": "2017-02-16T22:46:52+00:00" }, { "name": "symfony/var-dumper", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5bb4435a03a4f05c211f4a9a8ee2756965924511" + "reference": "cb50260b674ee1c2d4ab49f2395a42e0b4681e20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5bb4435a03a4f05c211f4a9a8ee2756965924511", - "reference": "5bb4435a03a4f05c211f4a9a8ee2756965924511", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cb50260b674ee1c2d4ab49f2395a42e0b4681e20", + "reference": "cb50260b674ee1c2d4ab49f2395a42e0b4681e20", "shasum": "" }, "require": { @@ -4003,7 +4003,7 @@ "debug", "dump" ], - "time": "2017-01-24T12:58:58+00:00" + "time": "2017-02-16T22:46:52+00:00" }, { "name": "themattharris/tmhoauth", @@ -4298,6 +4298,55 @@ ], "time": "2015-06-14T21:17:01+00:00" }, + { + "name": "facebook/webdriver", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "77300c4ab2025d4316635f592ec849ca7323bd8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/77300c4ab2025d4316635f592ec849ca7323bd8c", + "reference": "77300c4ab2025d4316635f592ec849ca7323bd8c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^5.5 || ~7.0", + "symfony/process": "^2.8 || ^3.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.11", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "4.6.* || ~5.0", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "^2.6" + }, + "suggest": { + "phpdocumentor/phpdocumentor": "2.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2017-01-13T15:48:08+00:00" + }, { "name": "fzaninotto/faker", "version": "v1.6.0", @@ -4439,23 +4488,30 @@ "time": "2015-12-15T10:42:16+00:00" }, { - "name": "laravel/browser-kit-testing", - "version": "v1.0.3", + "name": "laravel/dusk", + "version": "v1.0.6", "source": { "type": "git", - "url": "https://github.com/laravel/browser-kit-testing.git", - "reference": "0adfb725147815bff5516d157577f375a6e66ebd" + "url": "https://github.com/laravel/dusk.git", + "reference": "804bf2ef20de7d86caac1aff433c761399b55e56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/0adfb725147815bff5516d157577f375a6e66ebd", - "reference": "0adfb725147815bff5516d157577f375a6e66ebd", + "url": "https://api.github.com/repos/laravel/dusk/zipball/804bf2ef20de7d86caac1aff433c761399b55e56", + "reference": "804bf2ef20de7d86caac1aff433c761399b55e56", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/css-selector": "~3.1", - "symfony/dom-crawler": "~3.1" + "facebook/webdriver": "~1.0", + "illuminate/support": "~5.3", + "nesbot/carbon": "~1.20", + "php": ">=5.6.4", + "symfony/console": "~3.2", + "symfony/process": "~3.2" + }, + "require-dev": { + "mockery/mockery": "^0.9.6", + "phpunit/phpunit": "^5.7" }, "type": "library", "extra": { @@ -4465,7 +4521,7 @@ }, "autoload": { "psr-4": { - "Laravel\\BrowserKitTesting\\": "src/" + "Laravel\\Dusk\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4478,12 +4534,13 @@ "email": "taylor@laravel.com" } ], - "description": "Provides backwards compatibility for BrowserKit testing in Laravel 5.4.", + "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", "keywords": [ "laravel", - "testing" + "testing", + "webdriver" ], - "time": "2017-02-08T22:32:37+00:00" + "time": "2017-02-15T20:21:39+00:00" }, { "name": "maximebf/debugbar", @@ -5760,74 +5817,18 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, - { - "name": "symfony/dom-crawler", - "version": "v3.1.10", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "7eede2a901a19928494194f7d1815a77b9a473a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7eede2a901a19928494194f7d1815a77b9a473a0", - "reference": "7eede2a901a19928494194f7d1815a77b9a473a0", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "symfony/css-selector": "~2.8|~3.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com", - "time": "2017-01-21T17:13:55+00:00" - }, { "name": "symfony/yaml", - "version": "v3.2.3", + "version": "v3.2.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e1718c6bf57e1efbb8793ada951584b2ab27775b" + "reference": "9724c684646fcb5387d579b4bfaa63ee0b0c64c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e1718c6bf57e1efbb8793ada951584b2ab27775b", - "reference": "e1718c6bf57e1efbb8793ada951584b2ab27775b", + "url": "https://api.github.com/repos/symfony/yaml/zipball/9724c684646fcb5387d579b4bfaa63ee0b0c64c8", + "reference": "9724c684646fcb5387d579b4bfaa63ee0b0c64c8", "shasum": "" }, "require": { @@ -5869,7 +5870,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-01-21T17:06:35+00:00" + "time": "2017-02-16T22:46:52+00:00" }, { "name": "webmozart/assert", diff --git a/phpunit.xml b/phpunit.xml index 3e884d17..88860829 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,16 +9,16 @@ processIsolation="false" stopOnFailure="false"> - - ./tests + + ./tests/Feature + + + ./tests/Unit ./app - - ./app/Http/routes.php - diff --git a/tests/ArticlesTest.php b/tests/ArticlesTest.php deleted file mode 100644 index 850adeee..00000000 --- a/tests/ArticlesTest.php +++ /dev/null @@ -1,78 +0,0 @@ -appurl = config('app.url'); - } - - /** - * Test the `/blog` page returns the article, this - * means the database is being hit. - * - * @return void - */ - public function testArticlesPage() - { - $this->visit($this->appurl . '/blog') - ->see('My New Blog'); - } - - /** - * Test the `/blog/{year}` page returns the article, this - * means the database is being hit. - * - * @return void - */ - public function testArticlesYearPage() - { - $this->visit($this->appurl . '/blog/2016') - ->see('My New Blog'); - } - - /** - * Test the `/blog/{year}/{month}` page returns the article, - * this means the database is being hit. - * - * @return void - */ - public function testArticlesMonthPage() - { - $this->visit($this->appurl . '/blog/2016/01') - ->see('My New Blog'); - } - - /** - * Test a single article page. - * - * @return void - */ - public function testSingleArticlePage() - { - $this->visit($this->appurl . '/blog/2016/01/my-new-blog') - ->see('My New Blog'); - } - - /** - * Test the RSS feed. - * - * @return void - */ - public function testRSSFeed() - { - $response = $this->call('GET', $this->appurl . '/feed'); - - $this->assertEquals('application/rss+xml', $response->headers->get('Content-Type')); - } -} diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php new file mode 100644 index 00000000..b388384d --- /dev/null +++ b/tests/Browser/ExampleTest.php @@ -0,0 +1,23 @@ +browse(function (Browser $browser) { + $browser->visit('/') + ->assertSee('Jonny Barnes'); + }); + } +} diff --git a/tests/Browser/Pages/HomePage.php b/tests/Browser/Pages/HomePage.php new file mode 100644 index 00000000..c04a7f0a --- /dev/null +++ b/tests/Browser/Pages/HomePage.php @@ -0,0 +1,40 @@ + '#selector', + ]; + } +} diff --git a/tests/Browser/Pages/Page.php b/tests/Browser/Pages/Page.php new file mode 100644 index 00000000..f8d76222 --- /dev/null +++ b/tests/Browser/Pages/Page.php @@ -0,0 +1,20 @@ + '#selector', + ]; + } +} diff --git a/tests/Browser/screenshots/.gitignore b/tests/Browser/screenshots/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/tests/Browser/screenshots/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/ContactsTest.php b/tests/ContactsTest.php deleted file mode 100644 index 6dddbfae..00000000 --- a/tests/ContactsTest.php +++ /dev/null @@ -1,52 +0,0 @@ -appurl = config('app.url'); - } - - /** - * Test the `/contacts` page and see if response is OK. - * - * @return void - */ - public function testContactsPage() - { - $this->visit($this->appurl . '/contacts') - ->assertResponseOK(); - } - - /** - * Test an individual contact page with default profile image. - * - * @return void - */ - public function testContactPageWithDefaultPic() - { - $this->visit($this->appurl . '/contacts/tantek') - ->see(''); - } - - /** - * Test an individual contact page with a specific profile image. - * - * @return void - */ - public function testContactPageWithSpecificPic() - { - $this->visit($this->appurl . '/contacts/aaron') - ->see(''); - } -} diff --git a/tests/BrowserKitTest.php b/tests/CreatesApplication.php similarity index 56% rename from tests/BrowserKitTest.php rename to tests/CreatesApplication.php index a2429b4f..a2e89522 100644 --- a/tests/BrowserKitTest.php +++ b/tests/CreatesApplication.php @@ -1,17 +1,11 @@ make(Kernel::class)->bootstrap(); - return $app; } } diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php new file mode 100644 index 00000000..6b71b600 --- /dev/null +++ b/tests/DuskTestCase.php @@ -0,0 +1,35 @@ +get('/'); + $response->assertStatus(200); + } +} diff --git a/tests/IndieAuthTest.php b/tests/IndieAuthTest.php deleted file mode 100644 index 97fdadf7..00000000 --- a/tests/IndieAuthTest.php +++ /dev/null @@ -1,79 +0,0 @@ -appurl = config('app.url'); - } - - /** - * Test the getAuthorizationEndpoint calls the correct service methods, - * though these methods are actually mocked. - * - * @return void - */ - public function testIndieAuthServiceDiscoversEndpoint() - { - $service = new \App\Services\IndieAuthService(); - $client = new \IndieAuth\Client(); - $result = $service->getAuthorizationEndpoint($this->appurl, $client); - $this->assertSame('https://indieauth.com/auth', $result); - } - - /** - * Test that the Service build the correct redirect URL. - * - * @return void - */ - public function testIndieAuthServiceBuildRedirectURL() - { - $client = new \IndieAuth\Client(); - $service = new \App\Services\IndieAuthService(); - $result = $service->buildAuthorizationURL( - 'https://indieauth.com/auth', - $this->appurl, - $client - ); - $this->assertSame( - 'https://indieauth.com/auth?me=', - substr($result, 0, 30) - ); - } - - /** - * Test the `start` method redirects to the client on error. - * - * @return void - */ - public function testIndieAuthControllerBeginAuthRedirectsToClientOnFail() - { - $response = $this->call('GET', $this->appurl . '/indieauth/start', ['me' => 'http://example.org']); - $this->assertSame($this->appurl . '/micropub/create', $response->headers->get('Location')); - } - - /** - * Now we test the `start` method as a whole. - * - * @return void - */ - public function testIndieAuthControllerBeginAuthRedirectsToEndpoint() - { - $response = $this->call('GET', $this->appurl . '/indieauth/start', ['me' => $this->appurl]); - $this->assertSame( - 'https://indieauth.com/auth?me=', - substr($response->headers->get('Location'), 0, 30) - ); - $response = null; - } -} diff --git a/tests/MicropubClientTest.php b/tests/MicropubClientTest.php deleted file mode 100644 index 67299155..00000000 --- a/tests/MicropubClientTest.php +++ /dev/null @@ -1,69 +0,0 @@ -appurl = config('app.url'); - } - - /** - * Test the client gets shown for an unauthorised request. - * - * @return void - */ - public function testClientPageUnauthorised() - { - $this->visit($this->appurl . '/micropub/create') - ->see('IndieAuth'); - } - - public function testClientPageRecentAuth() - { - $this->visit($this->appurl . '/micropub/create') - ->see($this->appurl); - } - - public function testClientCreatesNewNoteWithTag() - { - //in this test, the syndication targets are blank - $faker = \Faker\Factory::create(); - $note = 'Fake note from #PHPUnit: ' . $faker->text; - $this->visit($this->appurl . '/micropub/create') - ->type($note, 'content') - ->press('Submit'); - $this->seeInDatabase('notes', ['note' => $note]); - $this->visit($this->appurl . '/notes/tagged/PHPUnit') - ->see('PHPUnit'); - //my client has made a request to my endpoint, which then adds - //to the db, so database transaction don’t work - //so lets manually delete the new entry - //first, if we are using algolia, we need to delete it - if (env('SCOUT_DRIVER') == 'algolia') { - //we need to allow the index to update in order to query it - sleep(2); - $client = new \AlgoliaSearch\Client(env('ALGOLIA_APP_ID'), env('ALGOLIA_SECRET')); - $index = $client->initIndex('notes'); - //here we query for the new note and tell algolia too delete it - $res = $index->deleteByQuery('Fake note from'); - if ($res == 0) { - //somehow the new not didn’t get deleted - $this->fail('Didn’t delete the note from the index'); - } - } - $newNote = \App\Note::where('note', $note)->first(); - $newNote->forceDelete(); - } -} diff --git a/tests/MicropubTest.php b/tests/MicropubTest.php deleted file mode 100644 index de9e3f7f..00000000 --- a/tests/MicropubTest.php +++ /dev/null @@ -1,271 +0,0 @@ -appurl = config('app.url'); - } - - public function testMicropubRequestWithoutToken() - { - $this->call('GET', $this->appurl . '/api/post'); - $this->assertResponseStatus(400); - $this->seeJson(['error_description' => 'No token provided with request']); - } - - public function testMicropubRequestWithoutValidToken() - { - $this->call('GET', $this->appurl . '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer abc123']); - $this->assertResponseStatus(400); - $this->seeJson(['error_description' => 'The provided token did not pass validation']); - } - - public function testMicropubRequestWithValidToken() - { - $this->call('GET', $this->appurl . '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); - $this->seeJson(['response' => 'token']); - } - - public function testMicropubRequestForSyndication() - { - $this->call('GET', $this->appurl . '/api/post', ['q' => 'syndicate-to'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); - $this->seeJson(['uid' => 'https://twitter.com/jonnybarnes']); - } - - public function testMicropubRequestForNearbyPlacesThatExist() - { - $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:53.5,-2.38'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); - $this->see('the-bridgewater-pub'); - } - - public function testMicropubRequestForNearbyPlacesThatExistWithUncertaintyParameter() - { - $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:53.5,-2.38;u=35'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); - $this->see('the-bridgewater-pub'); - } - - public function testMicropubRequestForNearbyPlacesThatDoNotExist() - { - $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:1.23,4.56'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); - $this->see('[]'); - } - - public function testMicropubRequestForConfig() - { - $this->call('GET', $this->appurl . '/api/post', ['q' => 'config'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); - $this->seeJson(['uid' => 'https://twitter.com/jonnybarnes']); - } - - public function testMicropubRequestCreateNewNote() - { - $faker = \Faker\Factory::create(); - $note = $faker->text; - $this->call( - 'POST', - $this->appurl . '/api/post', - [ - 'h' => 'entry', - 'content' => $note - ], - [], - [], - ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] - ); - $this->seeInDatabase('notes', ['note' => $note]); - } - - public function testMicropubRequestCreateNewPlace() - { - $this->call( - 'POST', - $this->appurl . '/api/post', - [ - 'h' => 'card', - 'name' => 'The Barton Arms', - 'geo' => 'geo:53.4974,-2.3768' - ], - [], - [], - ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] - ); - $this->seeInDatabase('places', ['slug' => 'the-barton-arms']); - } - - public function testMicropubJSONRequestCreateNewNote() - { - $faker = \Faker\Factory::create(); - $note = $faker->text; - $this->json( - 'POST', - $this->appurl . '/api/post', - [ - 'type' => ['h-entry'], - 'properties' => [ - 'content' => [$note], - ], - ], - ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] - )->seeJson([ - 'response' => 'created' - ])->assertResponseStatus(201); - } - - public function testMicropubJSONRequestCreateNewNoteWithoutToken() - { - $faker = \Faker\Factory::create(); - $note = $faker->text; - $this->json( - 'POST', - $this->appurl . '/api/post', - [ - 'type' => ['h-entry'], - 'properties' => [ - 'content' => [$note], - ], - ] - )->seeJson([ - 'response' => 'error', - 'error' => 'no_token' - ])->assertResponseStatus(400); - } - - public function testMicropubJSONRequestCreateNewNoteWithInvalidToken() - { - $faker = \Faker\Factory::create(); - $note = $faker->text; - $this->json( - 'POST', - $this->appurl . '/api/post', - [ - 'type' => ['h-entry'], - 'properties' => [ - 'content' => [$note], - ], - ], - ['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()] - )->seeJson([ - 'response' => 'error', - 'error' => 'invalid_token' - ]); - } - - public function testMicropubJSONRequestCreateNewPlace() - { - $faker = \Faker\Factory::create(); - $this->json( - 'POST', - $this->appurl . '/api/post', - [ - 'type' => ['h-card'], - 'properties' => [ - 'name' => $faker->name, - 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude - ], - ], - ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] - )->seeJson([ - 'response' => 'created' - ])->assertResponseStatus(201); - } - - public function testMicropubJSONRequestCreateNewPlaceWithoutToken() - { - $faker = \Faker\Factory::create(); - $this->json( - 'POST', - $this->appurl . '/api/post', - [ - 'type' => ['h-entry'], - 'properties' => [ - 'name' => $faker->name, - 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude - ], - ] - )->seeJson([ - 'response' => 'error', - 'error' => 'no_token' - ])->assertResponseStatus(400); - } - - public function testMicropubJSONRequestCreateNewPlaceWithInvalidToken() - { - $faker = \Faker\Factory::create(); - $this->json( - 'POST', - $this->appurl . '/api/post', - [ - 'type' => ['h-entry'], - 'properties' => [ - 'name' => $faker->name, - 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude - ], - ], - ['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()] - )->seeJson([ - 'response' => 'error', - 'error' => 'invalid_token' - ]); - } - - public function testMicropubJSONRequestCreateNewPlaceWithUncertaintyParam() - { - $faker = \Faker\Factory::create(); - $this->json( - 'POST', - $this->appurl . '/api/post', - [ - 'type' => ['h-card'], - 'properties' => [ - 'name' => $faker->name, - 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude . ';u=35' - ], - ], - ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] - )->seeJson([ - 'response' => 'created' - ])->assertResponseStatus(201); - } - - private function getToken() - { - $signer = new Sha256(); - $token = (new Builder()) - ->set('client_id', 'https://quill.p3k.io') - ->set('me', 'https://jonnybarnes.localhost') - ->set('scope', 'post') - ->set('issued_at', time()) - ->sign($signer, env('APP_KEY')) - ->getToken(); - - return $token; - } - - private function getInvalidToken() - { - $signer = new Sha256(); - $token = (new Builder()) - ->set('client_id', 'https://quill.p3k.io') - ->set('me', 'https://jonnybarnes.localhost') - ->set('scope', 'view') - ->set('issued_at', time()) - ->sign($signer, env('APP_KEY')) - ->getToken(); - - return $token; - } -} diff --git a/tests/NotesAdminTest.php b/tests/NotesAdminTest.php deleted file mode 100644 index be476736..00000000 --- a/tests/NotesAdminTest.php +++ /dev/null @@ -1,33 +0,0 @@ -appurl = config('app.url'); - $this->notesAdminController = new \App\Http\Controllers\NotesAdminController(); - } - - public function testCreatedNoteDispatchesSendWebmentionsJob() - { - $this->expectsJobs(\App\Jobs\SendWebMentions::class); - - $this->withSession(['loggedin' => true]) - ->visit($this->appurl . '/admin/note/new') - ->type('Mentioning', 'content') - ->press('Submit'); - } -} diff --git a/tests/NotesTest.php b/tests/NotesTest.php deleted file mode 100644 index 8112b32f..00000000 --- a/tests/NotesTest.php +++ /dev/null @@ -1,182 +0,0 @@ -appurl = config('app.url'); - $this->notesController = new \App\Http\Controllers\NotesController(); - } - - /** - * Test the `/notes` page returns 200, this should - * mean the database is being hit. - * - * @return void - */ - public function testNotesPage() - { - $this->visit($this->appurl . '/notes') - ->assertResponseOk(); - } - - /** - * Test a specific note so that `singleNote()` get called. - * - * @return void - */ - public function testSpecificNote() - { - $this->visit($this->appurl . '/notes/B') - ->see('#beer'); - } - - /** - * Test that `/note/{decID}` redirects to `/notes/{nb60id}`. - * - * @return void - */ - public function testDecIDRedirect() - { - $this->get($this->appurl . '/note/11') - ->assertRedirectedTo(config('app.url') . '/notes/B'); - } - - /** - * Visit the tagged page and see text from the note. - * - * @return void - */ - public function testTaggedNotesPage() - { - $this->visit($this->appurl . '/notes/tagged/beer') - ->see('at the local.'); - } - - /** - * Look for a default image in the contact’s h-card. - * - * @return void - */ - public function testDefaultImageUsed() - { - $this->visit($this->appurl . '/notes/C') - ->see(''); - } - - /** - * Look for a specific profile image in the contact’s h-card. - * - * @return void - */ - public function testProfileImageUsed() - { - $this->visit($this->appurl . '/notes/D') - ->see(''); - } - - /** - * Look for twitter URL when there’s no associated contact. - * - * @return void - */ - public function testTwitterLinkCreatedWhenNoContactFound() - { - $this->visit($this->appurl . '/notes/E') - ->see('@bob'); - } - - /** - * Test hashtag linking. - * - * @return void - */ - public function testHashtags() - { - $this->visit($this->appurl . '/notes/B') - ->see(''); - } - - /** - * Look for the client name after the note. - * - * @return void - */ - public function testClientNameDisplayed() - { - $this->visit($this->appurl . '/notes/D') - ->see('JBL5'); - } - - /** - * Look for the client URL after the note. - * - * @return void - */ - public function testClientURLDisplayed() - { - $this->visit($this->appurl . '/notes/E') - ->see('quill.p3k.io'); - } - - /** - * Test a correct profile link is formed from a generic URL. - * - * @return void - */ - public function testCreatePhotoLinkWithNonCachedImage() - { - $homepage = 'https://example.org/profile.png'; - $expected = 'https://example.org/profile.png'; - $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage)); - } - - /** - * Test a correct profile link is formed from a generic URL. - * - * @return void - */ - public function testCreatePhotoLinkWithCachedImage() - { - $homepage = 'https://aaronparecki.com/profile.png'; - $expected = '/assets/profile-images/aaronparecki.com/image'; - $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage)); - } - - /** - * Test a correct profile link is formed from a twitter URL. - * - * @return void - */ - public function testCreatePhotoLinkWithTwimgProfileImageURL() - { - $twitterProfileImage = 'http://pbs.twimg.com/1234'; - $expected = 'https://pbs.twimg.com/1234'; - $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterProfileImage)); - } - - /** - * Test `null` is returned for a twitter profile. - * - * @return void - */ - public function testCreatePhotoLinkWithCachedTwitterURL() - { - $twitterURL = 'https://twitter.com/example'; - $expected = 'https://pbs.twimg.com/static_profile_link.jpg'; - Cache::put($twitterURL, $expected, 1); - $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterURL)); - } -} diff --git a/tests/PlacesTest.php b/tests/PlacesTest.php deleted file mode 100644 index 7af4c6ac..00000000 --- a/tests/PlacesTest.php +++ /dev/null @@ -1,52 +0,0 @@ -appurl = config('app.url'); - } - - /** - * Test the `/places` page for OK response. - * - * @return void - */ - public function testPlacesPage() - { - $this->visit($this->appurl . '/places') - ->assertResponseOK(); - } - - /** - * Test a specific place. - * - * @return void - */ - public function testSinglePlace() - { - $this->visit($this->appurl . '/places/the-bridgewater-pub') - ->see('The Bridgewater Pub'); - } - - /** - * Test the nearby method returns a collection. - * - * @return void - */ - public function testNearbyMethod() - { - $nearby = \App\Place::near(53.5, -2.38, 1000); - $this->assertEquals('the-bridgewater-pub', $nearby[0]->slug); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 8208edca..2932d4a6 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,25 +1,10 @@ make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); - - return $app; - } + use CreatesApplication; } diff --git a/tests/TokenServiceTest.php b/tests/TokenServiceTest.php deleted file mode 100644 index 8ca43fd7..00000000 --- a/tests/TokenServiceTest.php +++ /dev/null @@ -1,43 +0,0 @@ -appurl = config('app.url'); - $this->tokenService = new \App\Services\TokenService(); - } - - /** - * Given the token is dependent on a random nonce, the time of creation and - * the APP_KEY, to test, we shall create a token, and then verify it. - * - * @return void - */ - public function testTokenCreationAndValidation() - { - $data = [ - 'me' => 'https://example.org', - 'client_id' => 'https://quill.p3k.io', - 'scope' => 'post' - ]; - $token = $this->tokenService->getNewToken($data); - $valid = $this->tokenService->validateToken($token); - $validData = [ - 'me' => $valid->getClaim('me'), - 'client_id' => $valid->getClaim('client_id'), - 'scope' => $valid->getClaim('scope') - ]; - $this->assertSame($data, $validData); - } -} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php new file mode 100644 index 00000000..5663bb49 --- /dev/null +++ b/tests/Unit/ExampleTest.php @@ -0,0 +1,20 @@ +assertTrue(true); + } +} diff --git a/tests/WebMentionsTest.php b/tests/WebMentionsTest.php deleted file mode 100644 index e296f96e..00000000 --- a/tests/WebMentionsTest.php +++ /dev/null @@ -1,92 +0,0 @@ -appurl = config('app.url'); - } - - /** - * Test webmentions without source and target are rejected. - * - * @return void - */ - public function testWebmentionsWithoutSourceAndTargetAreRejected() - { - $this->call('POST', $this->appurl . '/webmention', ['source' => 'https://example.org/post/123']); - $this->assertResponseStatus(400) - ->see('You need both the target and source parameters'); - } - - /** - * Test invalid target gets a 400 response. - * - * @return void - */ - public function testInvalidTargetReturns400Response() - { - $this->call('POST', $this->appurl . '/webmention', [ - 'source' => 'https://example.org/post/123', - 'target' => $this->appurl . '/invalid/target' - ]); - $this->assertResponseStatus(400) - ->see('Invalid request'); - } - - /** - * Test blog target gets a 501 response. - * - * @return void - */ - public function testBlogpostTargetReturns501Response() - { - $this->call('POST', $this->appurl . '/webmention', [ - 'source' => 'https://example.org/post/123', - 'target' => $this->appurl . '/blog/target' - ]); - $this->assertResponseStatus(501) - ->see('I don’t accept webmentions for blog posts yet.'); - } - - /** - * Test that a non-existant note gives a 400 response. - * - * @return void - */ - public function testNonexistantNoteReturns400Response() - { - $this->call('POST', $this->appurl . '/webmention', [ - 'source' => 'https://example.org/post/123', - 'target' => $this->appurl . '/notes/ZZZZZ' - ]); - $this->assertResponseStatus(400) - ->see('This note doesn’t exist.'); - } - - /** - * Test a legit webmention triggers the ProcessWebMention job. - * - * @return void - */ - public function testLegitimateWebmnetionTriggersProcessWebMentionJob() - { - $this->expectsJobs(\App\Jobs\ProcessWebMention::class); - $this->call('POST', $this->appurl . '/webmention', [ - 'source' => 'https://example.org/post/123', - 'target' => $this->appurl . '/notes/B' - ]); - $this->assertResponseStatus(202) - ->see('Webmention received, it will be processed shortly'); - } -} diff --git a/tests/aaron.png b/tests/aaron.png deleted file mode 100644 index 289234388005ba7bc557b74edc5d48ce2d1115f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6527 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