diff --git a/.env.dusk.testing b/.env.dusk.testing new file mode 100644 index 00000000..756f4074 --- /dev/null +++ b/.env.dusk.testing @@ -0,0 +1,14 @@ +APP_ENV=testing +APP_DEBUG=true +APP_KEY=base64:6DJhvZLVjE6dD4Cqrteh+6Z5vZlG+v/soCKcDHLOAH0= +APP_URL=http://localhost:8000 +APP_LONGURL=localhost +APP_SHORTURL=local + +DB_CONNECTION=travis + +CACHE_DRIVER=array +SESSION_DRIVER=file +QUEUE_DRIVER=sync + +SCOUT_DRIVER=pgsql diff --git a/.env.travis b/.env.travis index eaecc399..a160c615 100644 --- a/.env.travis +++ b/.env.travis @@ -1,5 +1,6 @@ APP_ENV=testing -APP_KEY= +APP_DEBUG=true +APP_KEY=base64:6DJhvZLVjE6dD4Cqrteh+6Z5vZlG+v/soCKcDHLOAH0= APP_URL=http://localhost:8000 APP_LONGURL=localhost APP_SHORTURL=local diff --git a/.gitignore b/.gitignore index 0970c3bb..c8cae252 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Homestead.json /public/files /public/keybase.txt /coverage +/LegacyTests diff --git a/.travis.yml b/.travis.yml index 6033e8fa..ca85154b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,22 @@ language: php sudo: false dist: trusty +cache: + - apt + addons: - postgresql: "9.5" + postgresql: "9.6" + apt: + sources: + - sourceline: 'deb http://ppa.launchpad.net/nginx/development/ubuntu trusty main' + packages: + - nginx + - realpath + artifacts: + s3_region: "eu-west-1" + paths: + - $(ls tests/Browser/screenshots/*.png | tr "\n" ":") + - $(ls /tmp/*.log | tr "\n" ":") services: - postgresql @@ -22,6 +36,10 @@ matrix: - php: nightly before_install: + - mkdir travis-phantomjs + - wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 + - tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs + - export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH - phpenv config-rm xdebug.ini || echo "xdebug already absent" - travis_retry composer self-update --preview @@ -29,6 +47,7 @@ install: - if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-dist; fi - if [[ $setup = 'stable' ]]; then travis_retry composer update --no-interaction --prefer-dist --prefer-stable; fi - if [[ $setup = 'lowest' ]]; then travis_retry composer update --no-interaction --prefer-dist --prefer-lowest --prefer-stable; fi + - travis/install-nginx.sh before_script: - psql -U travis -c 'create database travis_ci_test' @@ -37,10 +56,10 @@ before_script: - php artisan key:generate - php artisan migrate - php artisan db:seed - - php artisan token:generate - - php artisan serve & + - phantomjs --webdriver=127.0.0.1:9515 --webdriver-loglevel=DEBUG & - sleep 5 # Give artisan some time to start serving script: - phpdbg -qrr vendor/bin/phpunit --coverage-text + - php artisan dusk --filter syndication - php artisan security:check diff --git a/Makefile b/Makefile index b181abc5..17b8e7cc 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ .PHONY: sass frontend js compress lint-sass lint-js jsfiles := $(wildcard resources/assets/js/*.js) sassfiles := $(wildcard resources/assets/sass/*.scss) -yarnfiles:= node_modules/mapbox-gl/dist/mapbox-gl.css +yarnfiles:= node_modules/mapbox-gl/dist/mapbox-gl.css \ +node_modules/alertify.js/dist/css/alertify.css assets := public/assets/css/app.css \ public/assets/prism/prism.css public/assets/prism/prism.js \ $(wildcard public/assets/js/*.js) \ diff --git a/app/Console/Commands/GenerateToken.php b/app/Console/Commands/GenerateToken.php index 9299206c..72bea542 100644 --- a/app/Console/Commands/GenerateToken.php +++ b/app/Console/Commands/GenerateToken.php @@ -48,7 +48,7 @@ class GenerateToken extends Command { $data = [ 'me' => config('app.url'), - 'client_id' => config('app.url') . '/notes/new', + 'client_id' => route('micropub-client'), 'scope' => 'post', ]; $token = $tokenService->getNewToken($data); diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/Admin/AdminController.php similarity index 90% rename from app/Http/Controllers/AdminController.php rename to app/Http/Controllers/Admin/AdminController.php index a587c266..70118e51 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/Admin/AdminController.php @@ -1,6 +1,8 @@ $message]); - } - /** * List the articles that can be edited. * * @return \Illuminate\View\Factory view */ - public function listArticles() + public function index() { $posts = Article::select('id', 'title', 'published')->orderBy('id', 'desc')->get(); @@ -32,32 +21,15 @@ class ArticlesAdminController extends Controller } /** - * Show the edit form for an existing article. + * Show the new article form. * - * @param string The article id * @return \Illuminate\View\Factory view */ - public function editArticle($articleId) + public function create() { - $post = Article::select( - 'title', - 'main', - 'url', - 'published' - )->where('id', $articleId)->get(); + $message = session('message'); - return view('admin.editarticle', ['id' => $articleId, 'post' => $post]); - } - - /** - * Show the delete confirmation form for an article. - * - * @param string The article id - * @return \Illuminate\View\Factory view - */ - public function deleteArticle($articleId) - { - return view('admin.deletearticle', ['id' => $articleId]); + return view('admin.newarticle', ['message' => $message]); } /** @@ -66,7 +38,7 @@ class ArticlesAdminController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\View\Factory view */ - public function postNewArticle(Request $request) + public function store(Request $request) { $published = $request->input('published'); if ($published == null) { @@ -102,14 +74,32 @@ class ArticlesAdminController extends Controller return view('admin.newarticlesuccess', ['id' => $article->id, 'title' => $article->title]); } + /** + * Show the edit form for an existing article. + * + * @param string The article id + * @return \Illuminate\View\Factory view + */ + public function edit($articleId) + { + $post = Article::select( + 'title', + 'main', + 'url', + 'published' + )->where('id', $articleId)->get(); + + return view('admin.editarticle', ['id' => $articleId, 'post' => $post]); + } + /** * Process an incoming request to edit an article. * - * @param string * @param \Illuminate\Http\Request $request + * @param string * @return \Illuminate|View\Factory view */ - public function postEditArticle($articleId, Request $request) + public function update(Request $request, $articleId) { $published = $request->input('published'); if ($published == null) { @@ -125,13 +115,24 @@ class ArticlesAdminController extends Controller return view('admin.editarticlesuccess', ['id' => $articleId]); } + /** + * Show the delete confirmation form for an article. + * + * @param string The article id + * @return \Illuminate\View\Factory view + */ + public function delete($articleId) + { + return view('admin.deletearticle', ['id' => $articleId]); + } + /** * Process a request to delete an aricle. * * @param string The article id * @return \Illuminate\View\Factory view */ - public function postDeleteArticle($articleId) + public function destroy($articleId) { Article::where('id', $articleId)->delete(); diff --git a/app/Http/Controllers/ClientsAdminController.php b/app/Http/Controllers/Admin/ClientsAdminController.php similarity index 87% rename from app/Http/Controllers/ClientsAdminController.php rename to app/Http/Controllers/Admin/ClientsAdminController.php index 80de8503..2d5c0199 100644 --- a/app/Http/Controllers/ClientsAdminController.php +++ b/app/Http/Controllers/Admin/ClientsAdminController.php @@ -1,8 +1,9 @@ $request->input('client_url'), - 'client_name' => $request->input('client_name'), - ]); - - return view('admin.newclientsuccess'); - } - /** * Show a form to edit a client name. * * @param string The client id * @return \Illuminate\View\Factory view */ - public function editClient($clientId) + public function edit($clientId) { $client = MicropubClient::findOrFail($clientId); @@ -61,6 +46,22 @@ class ClientsAdminController extends Controller ]); } + /** + * Process the request to adda new client name. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\View\Factory view + */ + public function store(Request $request) + { + MicropubClient::create([ + 'client_url' => $request->input('client_url'), + 'client_name' => $request->input('client_name'), + ]); + + return view('admin.newclientsuccess'); + } + /** * Process the request to edit a client name. * @@ -68,7 +69,7 @@ class ClientsAdminController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\View\Factory view */ - public function postEditClient($clientId, Request $request) + public function update($clientId, Request $request) { $client = MicropubClient::findOrFail($clientId); if ($request->input('edit')) { diff --git a/app/Http/Controllers/ContactsAdminController.php b/app/Http/Controllers/Admin/ContactsAdminController.php similarity index 93% rename from app/Http/Controllers/ContactsAdminController.php rename to app/Http/Controllers/Admin/ContactsAdminController.php index 038cca54..bea7c669 100644 --- a/app/Http/Controllers/ContactsAdminController.php +++ b/app/Http/Controllers/Admin/ContactsAdminController.php @@ -1,43 +1,44 @@ $contacts]); } + /** + * Display the form to add a new contact. + * + * @return \Illuminate\View\Factory view + */ + public function create() + { + return view('admin.newcontact'); + } + /** * Show the form to edit an existing contact. * * @param string The contact id * @return \Illuminate\View\Factory view */ - public function editContact($contactId) + public function edit($contactId) { $contact = Contact::findOrFail($contactId); @@ -49,7 +50,7 @@ class ContactsAdminController extends Controller * * @return \Illuminate\View\Factory view */ - public function deleteContact($contactId) + public function delete($contactId) { return view('admin.deletecontact', ['id' => $contactId]); } @@ -60,7 +61,7 @@ class ContactsAdminController extends Controller * @param \Illuminate\Http|request $request * @return \Illuminate\View\Factory view */ - public function postNewContact(Request $request) + public function store(Request $request) { $contact = new Contact(); $contact->name = $request->input('name'); @@ -82,7 +83,7 @@ class ContactsAdminController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\View\Factory view */ - public function postEditContact($contactId, Request $request) + public function update($contactId, Request $request) { $contact = Contact::findOrFail($contactId); $contact->name = $request->input('name'); @@ -113,7 +114,7 @@ class ContactsAdminController extends Controller * @param string The contact id * @return \Illuminate\View\Factory view */ - public function postDeleteContact($contactId) + public function destroy($contactId) { $contact = Contact::findOrFail($contactId); $contact->delete(); diff --git a/app/Http/Controllers/NotesAdminController.php b/app/Http/Controllers/Admin/NotesAdminController.php similarity index 89% rename from app/Http/Controllers/NotesAdminController.php rename to app/Http/Controllers/Admin/NotesAdminController.php index fc1e626f..0ddbd977 100644 --- a/app/Http/Controllers/NotesAdminController.php +++ b/app/Http/Controllers/Admin/NotesAdminController.php @@ -1,12 +1,13 @@ noteService = $noteService ?? new NoteService(); } - /** - * Show the form to make a new note. - * - * @return \Illuminate\View\Factory view - */ - public function newNotePage() - { - return view('admin.newnote'); - } - /** * List the notes that can be edited. * * @return \Illuminate\View\Factory view */ - public function listNotesPage() + public function index() { $notes = Note::select('id', 'note')->orderBy('id', 'desc')->get(); foreach ($notes as $note) { @@ -43,14 +34,13 @@ class NotesAdminController extends Controller } /** - * The delete note page. + * Show the form to make a new note. * - * @param int id - * @return view + * @return \Illuminate\View\Factory view */ - public function deleteNotePage($id) + public function create() { - return view('admin.deletenote', ['id' => $id]); + return view('admin.newnote'); } /** @@ -59,7 +49,7 @@ class NotesAdminController extends Controller * @param string The note id * @return \Illuminate\View\Factory view */ - public function editNotePage($noteId) + public function edit($noteId) { $note = Note::find($noteId); $note->originalNote = $note->getOriginal('note'); @@ -67,13 +57,24 @@ class NotesAdminController extends Controller return view('admin.editnote', ['id' => $noteId, 'note' => $note]); } + /** + * The delete note page. + * + * @param int id + * @return view + */ + public function delete($noteId) + { + return view('admin.deletenote', ['id' => $id]); + } + /** * Process a request to make a new note. * * @param Illuminate\Http\Request $request * @todo Sort this mess out */ - public function createNote(Request $request) + public function store(Request $request) { $validator = Validator::make( $request->all(), @@ -101,7 +102,7 @@ class NotesAdminController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\View\Factory view */ - public function editNote($noteId, Request $request) + public function update($noteId, Request $request) { //update note data $note = Note::findOrFail($noteId); @@ -122,7 +123,7 @@ class NotesAdminController extends Controller * @param int id * @return view */ - public function deleteNote($id) + public function destroy($id) { $note = Note::findOrFail($id); $note->delete(); diff --git a/app/Http/Controllers/PlacesAdminController.php b/app/Http/Controllers/Admin/PlacesAdminController.php similarity index 87% rename from app/Http/Controllers/PlacesAdminController.php rename to app/Http/Controllers/Admin/PlacesAdminController.php index 24f492f9..0a93ad81 100644 --- a/app/Http/Controllers/PlacesAdminController.php +++ b/app/Http/Controllers/Admin/PlacesAdminController.php @@ -1,9 +1,10 @@ placeService->createPlace($request); @@ -72,7 +73,7 @@ class PlacesAdminController extends Controller * @param Illuminate\Http\Request $request * @return Illuminate\View\Factory view */ - public function editPlace($placeId, Request $request) + public function update($placeId, Request $request) { $place = Place::findOrFail($placeId); $place->name = $request->name; diff --git a/app/Http/Controllers/ContactsController.php b/app/Http/Controllers/ContactsController.php index 46573423..6d07234f 100644 --- a/app/Http/Controllers/ContactsController.php +++ b/app/Http/Controllers/ContactsController.php @@ -39,11 +39,11 @@ class ContactsController extends Controller $contact = Contact::where('nick', '=', $nick)->firstOrFail(); $contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST); $file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image'; - $contact->image = ($filesystem->exists($file)) ? + $image = ($filesystem->exists($file)) ? '/assets/profile-images/' . $contact->homepageHost . '/image' : '/assets/profile-images/default-image'; - return view('contacts.show', ['contact' => $contact]); + return view('contacts.show', compact('contact', 'image')); } } diff --git a/app/Http/Controllers/MicropubClientController.php b/app/Http/Controllers/MicropubClientController.php index 25336aa1..d93612f9 100644 --- a/app/Http/Controllers/MicropubClientController.php +++ b/app/Http/Controllers/MicropubClientController.php @@ -62,7 +62,7 @@ class MicropubClientController extends Controller $this->indieClient ); if (! $micropubEndpoint) { - return redirect(route('micropub-client'))->withErrors('Unable to determine micropub API endpoint', 'endpoint'); + return redirect(route('micropub-client'))->with('error', 'Unable to determine micropub API endpoint'); } $response = $this->postNoteRequest($request, $micropubEndpoint, $token); @@ -76,7 +76,7 @@ class MicropubClientController extends Controller return redirect($location); } - return redirect(route('micropub-client'))->withErrors('Endpoint didn’t create the note.', 'endpoint'); + return redirect(route('micropub-client'))->with('error', 'Endpoint didn’t create the note.'); } /** @@ -87,8 +87,6 @@ class MicropubClientController extends Controller * and syndicate-to * * @param \Illuminate\Http\Request $request - * @param \IndieAuth\Client $indieClient - * @param \GuzzleHttp\Client $guzzleClient * @return \Illuminate\Routing\Redirector redirect */ public function refreshSyndicationTargets(Request $request) @@ -97,7 +95,7 @@ class MicropubClientController extends Controller $token = $request->session()->get('token'); $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient); if (! $micropubEndpoint) { - return redirect(route('micropub-client'))->withErrors('Unable to determine micropub API endpoint', 'endpoint'); + return redirect(route('micropub-client'))->with('error', 'Unable to determine micropub API endpoint'); } try { @@ -106,7 +104,7 @@ class MicropubClientController extends Controller 'query' => ['q' => 'syndicate-to'], ]); } catch (\GuzzleHttp\Exception\BadResponseException $e) { - return redirect(route('micropub-client'))->withErrors('Bad response when refreshing syndication targets', 'endpoint'); + return redirect(route('micropub-client'))->with('error', 'Bad response when refreshing syndication targets'); } $body = (string) $response->getBody(); $syndication = $this->parseSyndicationTargets($body); @@ -180,8 +178,7 @@ class MicropubClientController extends Controller 'headers' => $headers, ]); } catch (\GuzzleHttp\Exception\BadResponseException $e) { - return redirect(route('micropub-client')) - ->withErrors('There was a bad response from the micropub endpoint.', 'endpoint'); + return redirect(route('micropub-client'))->with('error', 'There was a bad response from the micropub endpoint.'); } return $response; @@ -335,6 +332,8 @@ class MicropubClientController extends Controller 'name' => $syn['name'], ]; } + } else { + $syndicateTo[] = ['target' => 'http://example.org', 'name' => 'Joe Bloggs on Example']; } if (count($syndicateTo) > 0) { return $syndicateTo; diff --git a/app/Http/Controllers/PlacesController.php b/app/Http/Controllers/PlacesController.php index d28a6852..ba8d5b42 100644 --- a/app/Http/Controllers/PlacesController.php +++ b/app/Http/Controllers/PlacesController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers; use App\Place; -use Illuminate\Http\Request; class PlacesController extends Controller { @@ -19,27 +18,6 @@ class PlacesController extends Controller return view('allplaces', ['places' => $places]); } - /** - * Show the form for creating a new resource. - * - * @return \Illuminate\Http\Response - */ - public function create() - { - // - } - - /** - * Store a newly created resource in storage. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function store(Request $request) - { - // - } - /** * Display the specified resource. * @@ -48,42 +26,8 @@ class PlacesController extends Controller */ public function show($slug) { - $place = Place::where('slug', '=', $slug)->first(); + $place = Place::where('slug', '=', $slug)->firstOrFail(); return view('singleplace', ['place' => $place]); } - - /** - * Show the form for editing the specified resource. - * - * @param int $id - * @return \Illuminate\Http\Response - */ - public function edit($id) - { - // - } - - /** - * Update the specified resource in storage. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response - */ - public function update(Request $request, $id) - { - // - } - - /** - * Remove the specified resource from storage. - * - * @param int $id - * @return \Illuminate\Http\Response - */ - public function destroy($id) - { - // - } } diff --git a/app/Http/Middleware/DevTokenMiddleware.php b/app/Http/Middleware/DevTokenMiddleware.php index a711142b..dabc2ca2 100644 --- a/app/Http/Middleware/DevTokenMiddleware.php +++ b/app/Http/Middleware/DevTokenMiddleware.php @@ -20,6 +20,14 @@ class DevTokenMiddleware session(['me' => config('app.url')]); if (Storage::exists('dev-token')) { session(['token' => Storage::get('dev-token')]); + } else { + $data = [ + 'me' => config('app.url'), + 'client_id' => route('micropub-client'), + 'scope' => 'post', + ]; + $tokenService = new \App\Services\TokenService(); + session(['token' => $tokenService->getNewToken($data)]); } } 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..8ef1f8cf 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", @@ -2965,16 +2965,16 @@ }, { "name": "sensiolabs/security-checker", - "version": "v4.0.0", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/sensiolabs/security-checker.git", - "reference": "116027b57b568ed61b7b1c80eeb4f6ee9e8c599c" + "reference": "f2ce0035fc512287978510ca1740cd111d60f89f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/116027b57b568ed61b7b1c80eeb4f6ee9e8c599c", - "reference": "116027b57b568ed61b7b1c80eeb4f6ee9e8c599c", + "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/f2ce0035fc512287978510ca1740cd111d60f89f", + "reference": "f2ce0035fc512287978510ca1740cd111d60f89f", "shasum": "" }, "require": { @@ -3005,7 +3005,7 @@ } ], "description": "A security checker for your composer.lock", - "time": "2016-09-23T18:09:57+00:00" + "time": "2017-02-18T17:53:25+00:00" }, { "name": "spatie/laravel-glide", @@ -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", @@ -5108,16 +5165,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.7.13", + "version": "5.7.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34" + "reference": "4906b8faf23e42612182fd212eb6f4c0f2954b57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60ebeed87a35ea46fd7f7d8029df2d6f013ebb34", - "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4906b8faf23e42612182fd212eb6f4c0f2954b57", + "reference": "4906b8faf23e42612182fd212eb6f4c0f2954b57", "shasum": "" }, "require": { @@ -5141,7 +5198,7 @@ "sebastian/global-state": "^1.1", "sebastian/object-enumerator": "~2.0", "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", + "sebastian/version": "~1.0.3|~2.0", "symfony/yaml": "~2.1|~3.0" }, "conflict": { @@ -5186,7 +5243,7 @@ "testing", "xunit" ], - "time": "2017-02-10T09:05:10+00:00" + "time": "2017-02-19T07:22:16+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -5578,16 +5635,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35" + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", - "reference": "96f8a3f257b69e8128ad74d3a7fd464bcbaa3b35", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", "shasum": "" }, "require": { @@ -5620,7 +5677,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-11-19T07:35:10+00:00" + "time": "2017-02-18T15:18:39+00:00" }, { "name": "sebastian/recursion-context", @@ -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/config/admin.php b/config/admin.php index b11b7dd5..9e778544 100644 --- a/config/admin.php +++ b/config/admin.php @@ -10,7 +10,7 @@ return [ | The username of the admin account. */ - 'name' => env('ADMIN_NAME'), + 'user' => env('ADMIN_USER'), /* |-------------------------------------------------------------------------- diff --git a/config/session.php b/config/session.php index cb7ff016..2ea2eed5 100644 --- a/config/session.php +++ b/config/session.php @@ -161,7 +161,7 @@ return [ | */ - 'secure' => true, + 'secure' => (config('app.env') != 'testing'), /* |-------------------------------------------------------------------------- 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/public/assets/css/app.css b/public/assets/css/app.css index a36e7347..a97bf32b 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -1,2 +1,2 @@ -html{box-sizing:border-box;font-size:24px}*,*::before,*::after{box-sizing:inherit}body{max-width:25em;margin:0 auto;padding-left:5px;padding-right:5px;word-wrap:break-word}#topheader{text-align:center}.h-entry{padding-top:1rem}.note{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}.note-metadata{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row;-webkit-box-pack:justify;justify-content:space-between;font-size:0.75em}.social-links{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center}.social-links svg{padding-left:3px}.mini-h-card img{display:inline-block;height:1rem}body>.h-card{margin-top:5px;border-top:1px solid grey}footer{margin-top:1rem}footer button{margin-left:5px}.u-comment{margin-top:1em;padding:0 1em;font-size:0.75rem}.u-comment.h-cite img{height:0.75rem}.u-comment .e-content{margin-top:0.5em;font-size:1rem}.container{position:relative;width:100%;height:0;padding-bottom:56.25%}.youtube{position:absolute;top:0;left:0;width:100%;height:100%}body{font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif}a{text-decoration:none;border-bottom:1px solid;color:blue}.social-links a{border-bottom:none}.icon{height:1em;width:auto}footer{font-size:0.5rem;text-align:center}footer p>a{border-bottom:none}.iwc-logo{width:100px;height:auto}.pagination{width:100%;height:3rem;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row;-webkit-box-pack:justify;justify-content:space-between;-webkit-box-align:center;align-items:center}.pagination li{list-style-type:none}.note-ui{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}@media (min-width: 600px){.note-ui>div{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row;padding:0.2rem}}@media (max-width: 599px){input[name="photo[]"]{width:100%}}.note-ui label{width:5em;margin-right:0.5rem;text-align:right}.note-ui input:not([type=submit]),.note-ui textarea{-webkit-box-flex:1;flex:1}.note-ui textarea{padding:0.1rem 0.3rem}#locate{margin-right:0.4rem}.map{margin-top:4px;height:200px}.marker{background-image:url(data:image/png;base64,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);background-size:contain;width:20px;height:20px}.map-menu{position:absolute;top:0;left:0;background:white;padding:0.4rem}.map-menu label{margin-left:3px;margin-right:3px}.contact{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row;margin-top:1em;border-bottom:1px dashed grey}.contact img{margin-right:0.2rem;width:100px;height:100px}span[role=img][aria-label],span[role=img][aria-label]{position:relative}span[role=img][aria-label]:focus::after,span[role=img][aria-label]:hover::after{position:absolute;display:block;z-index:1;bottom:1.5em;left:0;max-width:5em;padding:0.5em 0.75em;border:0.05em solid #fff;border-radius:0.2em;box-shadow:0.15em 0.15em 0.5em #000;content:attr(aria-label);background-color:rgba(0,0,0,0.85);color:#fff;font-size:80%;-webkit-animation:TOOLTIP 0.1s ease-out 1;animation:TOOLTIP 0.1s ease-out 1}@-webkit-keyframes TOOLTIP{from{bottom:0.5em;background-color:transparent;border:0.05em solid rgba(255,255,255,0);color:rgba(255,255,255,0);box-shadow:0 0 0 #000}to{bottom:1.5em;background-color:rgba(0,0,0,0.85);border:0.05em solid #fff;color:#fff;box-shadow:0.15em 0.15em 0.5em #000}}@keyframes TOOLTIP{from{bottom:0.5em;background-color:transparent;border:0.05em solid rgba(255,255,255,0);color:rgba(255,255,255,0);box-shadow:0 0 0 #000}to{bottom:1.5em;background-color:rgba(0,0,0,0.85);border:0.05em solid #fff;color:#fff;box-shadow:0.15em 0.15em 0.5em #000}}@media print{span[role=img][aria-label]::after{content:" (" attr(aria-label) ") "}} -/*# sourceMappingURL=app.css.map */ \ No newline at end of file +html{box-sizing:border-box;font-size:24px}*,*::before,*::after{box-sizing:inherit}body{max-width:25em;margin:0 auto;padding-left:5px;padding-right:5px;word-wrap:break-word}#topheader{text-align:center}.h-entry{padding-top:1rem}.note{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.note-metadata{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:0.75em}.social-links{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.social-links svg{padding-left:3px}.mini-h-card img{display:inline-block;height:1rem}body>.h-card{margin-top:5px;border-top:1px solid grey}footer{margin-top:1rem}footer button{margin-left:5px}.u-comment{margin-top:1em;padding:0 1em;font-size:0.75rem}.u-comment.h-cite img{height:0.75rem}.u-comment .e-content{margin-top:0.5em;font-size:1rem}.container{position:relative;width:100%;height:0;padding-bottom:56.25%}.youtube{position:absolute;top:0;left:0;width:100%;height:100%}body{font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif}a{text-decoration:none;border-bottom:1px solid;color:blue}.social-links a{border-bottom:none}.icon{height:1em;width:auto}footer{font-size:0.5rem;text-align:center}footer p>a{border-bottom:none}.iwc-logo{width:100px;height:auto}.pagination{width:100%;height:3rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.pagination li{list-style-type:none}.note-ui{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}@media (min-width: 600px){.note-ui>div{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;padding:0.2rem}}@media (max-width: 599px){input[name="photo[]"]{width:100%}}.note-ui label{width:5em;margin-right:0.5rem;text-align:right}.note-ui input:not([type=submit]),.note-ui textarea{-webkit-box-flex:1;-ms-flex:1;flex:1}.note-ui textarea{padding:0.1rem 0.3rem}#locate{margin-right:0.4rem}.map{margin-top:4px;height:200px}.marker{background-image:url(data:image/png;base64,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);background-size:contain;width:20px;height:20px}.map-menu{position:absolute;top:0;left:0;background:white;padding:0.4rem}.map-menu label{margin-left:3px;margin-right:3px}.contact{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;margin-top:1em;border-bottom:1px dashed grey}.contact img{margin-right:0.2rem;width:100px;height:100px}span[role=img][aria-label],span[role=img][aria-label]{position:relative}span[role=img][aria-label]:focus::after,span[role=img][aria-label]:hover::after{position:absolute;display:block;z-index:1;bottom:1.5em;left:0;max-width:5em;padding:0.5em 0.75em;border:0.05em solid #fff;border-radius:0.2em;box-shadow:0.15em 0.15em 0.5em #000;content:attr(aria-label);background-color:rgba(0,0,0,0.85);color:#fff;font-size:80%;-webkit-animation:TOOLTIP 0.1s ease-out 1;animation:TOOLTIP 0.1s ease-out 1}@-webkit-keyframes TOOLTIP{from{bottom:0.5em;background-color:transparent;border:0.05em solid rgba(255,255,255,0);color:rgba(255,255,255,0);box-shadow:0 0 0 #000}to{bottom:1.5em;background-color:rgba(0,0,0,0.85);border:0.05em solid #fff;color:#fff;box-shadow:0.15em 0.15em 0.5em #000}}@keyframes TOOLTIP{from{bottom:0.5em;background-color:transparent;border:0.05em solid rgba(255,255,255,0);color:rgba(255,255,255,0);box-shadow:0 0 0 #000}to{bottom:1.5em;background-color:rgba(0,0,0,0.85);border:0.05em solid #fff;color:#fff;box-shadow:0.15em 0.15em 0.5em #000}}@media print{span[role=img][aria-label]::after{content:" (" attr(aria-label) ") "}} +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Jlc291cmNlcy9hc3NldHMvc2Fzcy9hcHAuc2NzcyIsIi4uLy4uLy4uL3Jlc291cmNlcy9hc3NldHMvc2Fzcy9sYXlvdXQuc2NzcyIsIi4uLy4uLy4uL3Jlc291cmNlcy9hc3NldHMvc2Fzcy9zdHlsZXMuc2NzcyIsIi4uLy4uLy4uL3Jlc291cmNlcy9hc3NldHMvc2Fzcy9wYWdpbmF0aW9uLnNjc3MiLCIuLi8uLi8uLi9yZXNvdXJjZXMvYXNzZXRzL3Nhc3Mvbm90ZS1mb3JtLnNjc3MiLCIuLi8uLi8uLi9yZXNvdXJjZXMvYXNzZXRzL3Nhc3MvbWFwYm94LnNjc3MiLCIuLi8uLi8uLi9yZXNvdXJjZXMvYXNzZXRzL3Nhc3MvY29udGFjdHMuc2NzcyIsIi4uLy4uLy4uL3Jlc291cmNlcy9hc3NldHMvc2Fzcy9lbW9qaS5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlBLEtBQ0ksc0JBQ0EsY0FBZSxDQUNsQixxQkFLRyxrQkFBbUIsQ0FDdEIsS0NWRyxlQUNBLGNBQ0EsaUJBQ0Esa0JBQ0Esb0JBQXFCLENBQ3hCLFdBR0csaUJBQWtCLENBQ3JCLFNBR0csZ0JBQWlCLENBQ3BCLE1BR0csb0JBQ0EsQUFEQSxvQkFDQSxBQURBLGFBQ0EsNEJBQXNCLEFBQXRCLDZCQUFzQixBQUF0QiwwQkFBc0IsQUFBdEIscUJBQXNCLENBQ3pCLGVBR0csb0JBQ0EsQUFEQSxvQkFDQSxBQURBLGFBQ0EsOEJBQ0EsQUFEQSw2QkFDQSxBQURBLHVCQUNBLEFBREEsbUJBQ0EseUJBQ0EsQUFEQSxzQkFDQSxBQURBLDhCQUNBLGdCQUFpQixDQUNwQixjQUdHLG9CQUNBLEFBREEsb0JBQ0EsQUFEQSxhQUNBLHlCQUFtQixBQUFuQixzQkFBbUIsQUFBbkIsa0JBQW1CLENBQ3RCLGtCQUdHLGdCQUFpQixDQUNwQixpQkFHRyxxQkFDQSxXQUFZLENBQ2YsYUFHRyxlQUNBLHlCQUEwQixDQUM3QixPQUdHLGVBQWdCLENBQ25CLGNBR0csZUFBZ0IsQ0FDbkIsV0FHRyxlQUNBLGNBQ0EsaUJBQWtCLENBQ3JCLHNCQUdHLGNBQWUsQ0FDbEIsc0JBR0csaUJBQ0EsY0FBZSxDQUNsQixXQUdHLGtCQUNBLFdBQ0EsU0FDQSxxQkFBc0IsQ0FDekIsU0FHRyxrQkFDQSxNQUNBLE9BQ0EsV0FDQSxXQUFZLENBQ2YsS0NqRkcsNkpBV2MsQ0FDakIsRUFHRyxxQkFDQSx3QkFDQSxVQUFXLENBQ2QsZ0JBR0csa0JBQW1CLENBQ3RCLE1BR0csV0FDQSxVQUFXLENBQ2QsT0FHRyxpQkFDQSxpQkFBa0IsQ0FDckIsV0FHRyxrQkFBbUIsQ0FDdEIsVUFHRyxZQUNBLFdBQVksQ0FDZixZQzFDRyxXQUNBLFlBQ0Esb0JBQ0EsQUFEQSxvQkFDQSxBQURBLGFBQ0EsOEJBQ0EsQUFEQSw2QkFDQSxBQURBLHVCQUNBLEFBREEsbUJBQ0EseUJBQ0EsQUFEQSxzQkFDQSxBQURBLDhCQUNBLHlCQUFtQixBQUFuQixzQkFBbUIsQUFBbkIsa0JBQW1CLENBQ3RCLGVBR0csb0JBQXFCLENBQ3hCLFNDVkcsb0JBQ0EsQUFEQSxvQkFDQSxBQURBLGFBQ0EsNEJBQXNCLEFBQXRCLDZCQUFzQixBQUF0QiwwQkFBc0IsQUFBdEIscUJBQXNCLENBQ3pCLDBCQUdHLGFBQ0ksb0JBQ0EsQUFEQSxvQkFDQSxBQURBLGFBQ0EsOEJBQ0EsQUFEQSw2QkFDQSxBQURBLHVCQUNBLEFBREEsbUJBQ0EsY0FBZSxDQUNsQixDQUdMLDBCQUNJLHNCQUNJLFVBQVcsQ0FDZCxDQUdMLGVBQ0ksVUFDQSxvQkFDQSxnQkFBaUIsQ0FDcEIsb0RBSUcsbUJBQU8sQUFBUCxXQUFPLEFBQVAsTUFBTyxDQUNWLGtCQUdHLHFCQUFzQixDQUN6QixRQUdHLG1CQUFvQixDQUN2QixLQ25DRyxlQUNBLFlBQWEsQ0FDaEIsUUFHRyx5NEhBQ0Esd0JBQ0EsV0FDQSxXQUFZLENBQ2YsVUFHRyxrQkFDQSxNQUNBLE9BQ0EsaUJBQ0EsY0FBZSxDQUNsQixnQkFHRyxnQkFDQSxnQkFBaUIsQ0FDcEIsU0N0Qkcsb0JBQ0EsQUFEQSxvQkFDQSxBQURBLGFBQ0EsOEJBQ0EsQUFEQSw2QkFDQSxBQURBLHVCQUNBLEFBREEsbUJBQ0EsZUFDQSw2QkFBOEIsQ0FDakMsYUFHRyxvQkFDQSxZQUNBLFlBQWEsQ0FDaEIsc0RDUEcsaUJBQWtCLENBQ3JCLGdGQUlHLGtCQUNBLGNBQ0EsVUFDQSxhQUNBLE9BQ0EsY0FDQSxxQkFDQSx5QkFDQSxvQkFDQSxvQ0FDQSx5QkFDQSxrQ0FDQSxXQUNBLGNBQ0EsMENBQWtDLEFBQWxDLGlDQUFrQyxDQUNyQywyQkFHRyxLQUNJLGFBQ0EsNkJBQ0Esd0NBQ0EsMEJBQ0EscUJBQWtDLENBR3RDLEdBQ0ksYUFDQSxrQ0FDQSx5QkFDQSxXQUNBLG1DQUFnRCxDQUFBLENBSXhELEFBcEJDLG1CQUdHLEtBQ0ksYUFDQSw2QkFDQSx3Q0FDQSwwQkFDQSxxQkFBa0MsQ0FHdEMsR0FDSSxhQUNBLGtDQUNBLHlCQUNBLFdBQ0EsbUNBQWdELENBQUEsQ0FJeEQsYUFDSSxrQ0FDSSxrQ0FBbUMsQ0FDdEMsQ0FBQSIsImZpbGUiOiJhcHAuY3NzIn0= */ \ No newline at end of file diff --git a/public/assets/css/app.css.br b/public/assets/css/app.css.br index e6e90469..075df169 100644 Binary files a/public/assets/css/app.css.br and b/public/assets/css/app.css.br differ diff --git a/public/assets/css/app.css.gz b/public/assets/css/app.css.gz index 0440f211..861ad285 100644 Binary files a/public/assets/css/app.css.gz and b/public/assets/css/app.css.gz differ diff --git a/public/assets/css/app.css.map b/public/assets/css/app.css.map index 48f81bf5..9b81d251 100644 --- a/public/assets/css/app.css.map +++ b/public/assets/css/app.css.map @@ -1 +1,16 @@ -{"version":3,"sources":["../../../resources/assets/sass/app.scss","../../../resources/assets/sass/layout.scss","../../../resources/assets/sass/styles.scss","../../../resources/assets/sass/pagination.scss","../../../resources/assets/sass/note-form.scss","../../../resources/assets/sass/mapbox.scss","../../../resources/assets/sass/contacts.scss","../../../resources/assets/sass/emoji.scss"],"names":[],"mappings":"AAIA,KACI,sBACA,cAAe,CAClB,qBAKG,kBAAmB,CACtB,KCVG,eACA,cACA,iBACA,kBACA,oBAAqB,CACxB,WAGG,iBAAkB,CACrB,SAGG,gBAAiB,CACpB,MAGG,oBACA,AADA,aACA,4BAAsB,AAAtB,6BAAsB,AAAtB,qBAAsB,CACzB,eAGG,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,mBACA,yBACA,AADA,8BACA,gBAAiB,CACpB,cAGG,oBACA,AADA,aACA,yBAAmB,AAAnB,kBAAmB,CACtB,kBAGG,gBAAiB,CACpB,iBAGG,qBACA,WAAY,CACf,aAGG,eACA,yBAA0B,CAC7B,OAGG,eAAgB,CACnB,cAGG,eAAgB,CACnB,WAGG,eACA,cACA,iBAAkB,CACrB,sBAGG,cAAe,CAClB,sBAGG,iBACA,cAAe,CAClB,WAGG,kBACA,WACA,SACA,qBAAsB,CACzB,SAGG,kBACA,MACA,OACA,WACA,WAAY,CACf,KCjFG,6JAWc,CACjB,EAGG,qBACA,wBACA,UAAW,CACd,gBAGG,kBAAmB,CACtB,MAGG,WACA,UAAW,CACd,OAGG,iBACA,iBAAkB,CACrB,WAGG,kBAAmB,CACtB,UAGG,YACA,WAAY,CACf,YC1CG,WACA,YACA,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,mBACA,yBACA,AADA,8BACA,yBAAmB,AAAnB,kBAAmB,CACtB,eAGG,oBAAqB,CACxB,SCVG,oBACA,AADA,aACA,4BAAsB,AAAtB,6BAAsB,AAAtB,qBAAsB,CACzB,0BAGG,aACI,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,mBACA,cAAe,CAClB,CAGL,0BACI,sBACI,UAAW,CACd,CAGL,eACI,UACA,oBACA,gBAAiB,CACpB,oDAIG,mBAAO,AAAP,MAAO,CACV,kBAGG,qBAAsB,CACzB,QAGG,mBAAoB,CACvB,KCnCG,eACA,YAAa,CAChB,QAGG,y4HACA,wBACA,WACA,WAAY,CACf,UAGG,kBACA,MACA,OACA,iBACA,cAAe,CAClB,gBAGG,gBACA,gBAAiB,CACpB,SCtBG,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,mBACA,eACA,6BAA8B,CACjC,aAGG,oBACA,YACA,YAAa,CAChB,sDCPG,iBAAkB,CACrB,gFAIG,kBACA,cACA,UACA,aACA,OACA,cACA,qBACA,yBACA,oBACA,oCACA,yBACA,kCACA,WACA,cACA,0CAAkC,AAAlC,iCAAkC,CACrC,2BAGG,KACI,aACA,6BACA,wCACA,0BACA,qBAAkC,CAGtC,GACI,aACA,kCACA,yBACA,WACA,mCAAgD,CAAA,CAIxD,AApBC,mBAGG,KACI,aACA,6BACA,wCACA,0BACA,qBAAkC,CAGtC,GACI,aACA,kCACA,yBACA,WACA,mCAAgD,CAAA,CAIxD,aACI,kCACI,kCAAmC,CACtC,CAAA","file":"app.css"} \ No newline at end of file +{ + "version": 3, + "file": "app.css", + "sources": [ + "../../../resources/assets/sass/app.scss", + "../../../resources/assets/sass/layout.scss", + "../../../resources/assets/sass/styles.scss", + "../../../resources/assets/sass/pagination.scss", + "../../../resources/assets/sass/note-form.scss", + "../../../resources/assets/sass/mapbox.scss", + "../../../resources/assets/sass/contacts.scss", + "../../../resources/assets/sass/emoji.scss" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/public/assets/frontend/alertify.css b/public/assets/frontend/alertify.css new file mode 100644 index 00000000..a49a7e6a --- /dev/null +++ b/public/assets/frontend/alertify.css @@ -0,0 +1 @@ +.alertify-logs>*{padding:12px 24px;color:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.2);border-radius:1px}.alertify-logs>*,.alertify-logs>.default{background:rgba(0,0,0,.8)}.alertify-logs>.error{background:rgba(244,67,54,.8)}.alertify-logs>.success{background:rgba(76,175,80,.9)}.alertify{position:fixed;background-color:rgba(0,0,0,.3);left:0;right:0;top:0;bottom:0;width:100%;height:100%;z-index:1}.alertify.hide{opacity:0;pointer-events:none}.alertify,.alertify.show{box-sizing:border-box;transition:all .33s cubic-bezier(.25,.8,.25,1)}.alertify,.alertify *{box-sizing:border-box}.alertify .dialog{padding:12px}.alertify .alert,.alertify .dialog{width:100%;margin:0 auto;position:relative;top:50%;transform:translateY(-50%)}.alertify .alert>*,.alertify .dialog>*{width:400px;max-width:95%;margin:0 auto;text-align:center;padding:12px;background:#fff;box-shadow:0 2px 4px -1px rgba(0,0,0,.14),0 4px 5px 0 rgba(0,0,0,.098),0 1px 10px 0 rgba(0,0,0,.084)}.alertify .alert .msg,.alertify .dialog .msg{padding:12px;margin-bottom:12px;margin:0;text-align:left}.alertify .alert input:not(.form-control),.alertify .dialog input:not(.form-control){margin-bottom:15px;width:100%;font-size:100%;padding:12px}.alertify .alert input:not(.form-control):focus,.alertify .dialog input:not(.form-control):focus{outline-offset:-2px}.alertify .alert nav,.alertify .dialog nav{text-align:right}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button),.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button){background:transparent;box-sizing:border-box;color:rgba(0,0,0,.87);position:relative;outline:0;border:0;display:inline-block;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-size:14px;text-decoration:none;cursor:pointer;border:1px solid transparent;border-radius:2px}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover{background-color:rgba(0,0,0,.05)}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus{border:1px solid rgba(0,0,0,.1)}.alertify .alert nav button.btn,.alertify .dialog nav button.btn{margin:6px 4px}.alertify-logs{position:fixed;z-index:1}.alertify-logs.bottom,.alertify-logs:not(.top){bottom:16px}.alertify-logs.left,.alertify-logs:not(.right){left:16px}.alertify-logs.left>*,.alertify-logs:not(.right)>*{float:left;transform:translateZ(0);height:auto}.alertify-logs.left>.show,.alertify-logs:not(.right)>.show{left:0}.alertify-logs.left>*,.alertify-logs.left>.hide,.alertify-logs:not(.right)>*,.alertify-logs:not(.right)>.hide{left:-110%}.alertify-logs.right{right:16px}.alertify-logs.right>*{float:right;transform:translateZ(0)}.alertify-logs.right>.show{right:0;opacity:1}.alertify-logs.right>*,.alertify-logs.right>.hide{right:-110%;opacity:0}.alertify-logs.top{top:0}.alertify-logs>*{box-sizing:border-box;transition:all .4s cubic-bezier(.25,.8,.25,1);position:relative;clear:both;backface-visibility:hidden;perspective:1000;max-height:0;margin:0;padding:0;overflow:hidden;opacity:0;pointer-events:none}.alertify-logs>.show{margin-top:12px;opacity:1;max-height:1000px;padding:12px;pointer-events:auto} \ No newline at end of file diff --git a/public/assets/frontend/alertify.css.br b/public/assets/frontend/alertify.css.br new file mode 100644 index 00000000..a5b0b540 Binary files /dev/null and b/public/assets/frontend/alertify.css.br differ diff --git a/public/assets/frontend/alertify.css.gz b/public/assets/frontend/alertify.css.gz new file mode 100644 index 00000000..2387b322 Binary files /dev/null and b/public/assets/frontend/alertify.css.gz differ diff --git a/public/assets/frontend/mapbox-gl.css.gz b/public/assets/frontend/mapbox-gl.css.gz index c93a4906..691372b9 100644 Binary files a/public/assets/frontend/mapbox-gl.css.gz and b/public/assets/frontend/mapbox-gl.css.gz differ diff --git a/public/assets/frontend/normalize.css.gz b/public/assets/frontend/normalize.css.gz index 3fd2df53..0930586f 100644 Binary files a/public/assets/frontend/normalize.css.gz and b/public/assets/frontend/normalize.css.gz differ diff --git a/public/assets/js/links.js.gz b/public/assets/js/links.js.gz index c3df3ed8..dac558e5 100644 Binary files a/public/assets/js/links.js.gz and b/public/assets/js/links.js.gz differ diff --git a/public/assets/js/maps.js.br b/public/assets/js/maps.js.br index c0dc5ace..89576f48 100644 Binary files a/public/assets/js/maps.js.br and b/public/assets/js/maps.js.br differ diff --git a/public/assets/js/maps.js.gz b/public/assets/js/maps.js.gz index 8be727ed..3097a304 100644 Binary files a/public/assets/js/maps.js.gz and b/public/assets/js/maps.js.gz differ diff --git a/public/assets/js/newnote.js.gz b/public/assets/js/newnote.js.gz index 4418616c..8da37c42 100644 Binary files a/public/assets/js/newnote.js.gz and b/public/assets/js/newnote.js.gz differ diff --git a/public/assets/prism/prism.css.gz b/public/assets/prism/prism.css.gz index 652d5e2b..03dc538d 100644 Binary files a/public/assets/prism/prism.css.gz and b/public/assets/prism/prism.css.gz differ diff --git a/public/assets/prism/prism.js.gz b/public/assets/prism/prism.js.gz index aaeb5ca2..40b9bc77 100644 Binary files a/public/assets/prism/prism.js.gz and b/public/assets/prism/prism.js.gz differ diff --git a/resources/views/contacts/index.blade.php b/resources/views/contacts/index.blade.php index 31427ad5..737ac239 100644 --- a/resources/views/contacts/index.blade.php +++ b/resources/views/contacts/index.blade.php @@ -6,6 +6,6 @@ Contacts « @section('content') @foreach($contacts as $contact) - @include('templates.contact', ['contact' => $contact]) + @include('templates.contact', ['contact' => $contact, 'image' => $contact->image]) @endforeach @stop diff --git a/resources/views/contacts/show.blade.php b/resources/views/contacts/show.blade.php index 98c61339..796485ea 100644 --- a/resources/views/contacts/show.blade.php +++ b/resources/views/contacts/show.blade.php @@ -1,9 +1,9 @@ @extends('master') @section('title') -Contacts « +Contacts « @stop @section('content') -@include('templates.contact', array('contact' => $contact)) +@include('templates.contact', ['contact' => $contact, 'image' => $image]) @stop diff --git a/resources/views/micropub/create.blade.php b/resources/views/micropub/create.blade.php index b1aeff9a..532912eb 100644 --- a/resources/views/micropub/create.blade.php +++ b/resources/views/micropub/create.blade.php @@ -5,13 +5,10 @@ New Note « @stop @section('content') +@if (session('error')) +

{{ session('error') }}

+@endif

This is my UI for posting new notes, hopefully you’ll soon be able to use this if your site supports the micropub API.

-@if($errors->endpoint->first() != '') -

{{ $errors->endpoint->first() }}

-@endif -@if($errors->indieauth->first() != '') -

{{ $errors->indieauth->first() }}

-@endif @if($url === null)
diff --git a/resources/views/templates/contact.blade.php b/resources/views/templates/contact.blade.php index aff9cf46..2ac599d4 100644 --- a/resources/views/templates/contact.blade.php +++ b/resources/views/templates/contact.blade.php @@ -1,6 +1,6 @@
- +
{{ $contact->name }} {{ '@' . $contact->nick }} diff --git a/resources/views/templates/new-note-form.blade.php b/resources/views/templates/new-note-form.blade.php index ff9fae83..50267cf9 100644 --- a/resources/views/templates/new-note-form.blade.php +++ b/resources/views/templates/new-note-form.blade.php @@ -36,7 +36,7 @@
@endif
- Refresh Syndication Targets + Refresh Syndication Targets
@endif
diff --git a/routes/web.php b/routes/web.php index 8ab10f7e..115cf04b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -31,26 +31,26 @@ Route::group(['domain' => config('url.longurl')], function () { Route::post('login', 'AuthController@login'); //Admin pages grouped for filter - Route::group(['middleware' => 'myauth'], function () { + Route::group(['middleware' => 'myauth', 'namespace' => 'Admin'], function () { Route::get('admin', 'AdminController@showWelcome'); //Articles - Route::get('admin/blog/new', 'ArticlesAdminController@newArticle'); - Route::get('admin/blog/edit', 'ArticlesAdminController@listArticles'); - Route::get('admin/blog/edit/{id}', 'ArticlesAdminController@editArticle'); - Route::get('admin/blog/delete/{id}', 'ArticlesAdminController@deleteArticle'); - Route::post('admin/blog/new', 'ArticlesAdminController@postNewArticle'); - Route::post('admin/blog/edit/{id}', 'ArticlesAdminController@postEditArticle'); - Route::post('admin/blog/delete/{id}', 'ArticlesAdminController@postDeleteArticle'); + Route::get('admin/blog/new', 'ArticlesAdminController@create'); + Route::get('admin/blog/edit', 'ArticlesAdminController@index'); + Route::get('admin/blog/edit/{id}', 'ArticlesAdminController@edit'); + Route::get('admin/blog/delete/{id}', 'ArticlesAdminController@delete'); + Route::post('admin/blog/new', 'ArticlesAdminController@store'); + Route::post('admin/blog/edit/{id}', 'ArticlesAdminController@update'); + Route::post('admin/blog/delete/{id}', 'ArticlesAdminController@detroy'); //Notes - Route::get('admin/note/new', 'NotesAdminController@newNotePage'); - Route::get('admin/note/edit', 'NotesAdminController@listNotesPage'); - Route::get('admin/note/edit/{id}', 'NotesAdminController@editNotePage'); - Route::get('admin/note/delete/{id}', 'NotesAdminController@deleteNotePage'); - Route::post('admin/note/new', 'NotesAdminController@createNote'); - Route::post('admin/note/edit/{id}', 'NotesAdminController@editNote'); - Route::post('admin/note/delete/{id}', 'NotesAdminController@deleteNote'); + Route::get('admin/note/edit', 'NotesAdminController@index'); + Route::get('admin/note/new', 'NotesAdminController@create'); + Route::get('admin/note/edit/{id}', 'NotesAdminController@edit'); + Route::get('admin/note/delete/{id}', 'NotesAdminController@delete'); + Route::post('admin/note/new', 'NotesAdminController@store'); + Route::post('admin/note/edit/{id}', 'NotesAdminController@update'); + Route::post('admin/note/delete/{id}', 'NotesAdminController@destroy'); //Tokens Route::get('admin/tokens', 'TokensController@showTokens'); @@ -58,28 +58,28 @@ Route::group(['domain' => config('url.longurl')], function () { Route::post('admin/tokens/delete/{id}', 'TokensController@postDeleteToken'); //Micropub Clients - Route::get('admin/clients', 'ClientsAdminController@listClients'); - Route::get('admin/clients/new', 'ClientsAdminController@newClient'); - Route::get('admin/clients/edit/{id}', 'ClientsAdminController@editClient'); - Route::post('admin/clients/new', 'ClientsAdminController@postNewClient'); - Route::post('admin/clients/edit/{id}', 'ClientsAdminController@postEditClient'); + Route::get('admin/clients', 'ClientsAdminController@index'); + Route::get('admin/clients/new', 'ClientsAdminController@create'); + Route::get('admin/clients/edit/{id}', 'ClientsAdminController@edit'); + Route::post('admin/clients/new', 'ClientsAdminController@store'); + Route::post('admin/clients/edit/{id}', 'ClientsAdminController@update'); //Contacts - Route::get('admin/contacts/new', 'ContactsAdminController@newContact'); - Route::get('admin/contacts/edit', 'ContactsAdminController@listContacts'); - Route::get('admin/contacts/edit/{id}', 'ContactsAdminController@editContact'); + Route::get('admin/contacts/edit', 'ContactsAdminController@index'); + Route::get('admin/contacts/new', 'ContactsAdminController@create'); + Route::get('admin/contacts/edit/{id}', 'ContactsAdminController@edit'); + Route::get('admin/contacts/delete/{id}', 'ContactsAdminController@delete'); + Route::post('admin/contacts/new', 'ContactsAdminController@store'); + Route::post('admin/contacts/edit/{id}', 'ContactsAdminController@update'); + Route::post('admin/contacts/delete/{id}', 'ContactsAdminController@destroy'); Route::get('admin/contacts/edit/{id}/getavatar', 'ContactsAdminController@getAvatar'); - Route::get('admin/contacts/delete/{id}', 'ContactsAdminController@deleteContact'); - Route::post('admin/contacts/new', 'ContactsAdminController@postNewContact'); - Route::post('admin/contacts/edit/{id}', 'ContactsAdminController@postEditContact'); - Route::post('admin/contacts/delete/{id}', 'ContactsAdminController@postDeleteContact'); //Places - Route::get('admin/places/new', 'PlacesAdminController@newPlacePage'); - Route::get('admin/places/edit', 'PlacesAdminController@listPlacesPage'); - Route::get('admin/places/edit/{id}', 'PlacesAdminController@editPlacePage'); - Route::post('admin/places/new', 'PlacesAdminController@createPlace'); - Route::post('admin/places/edit/{id}', 'PlacesAdminController@editPlace'); + Route::get('admin/places/edit', 'PlacesAdminController@index'); + Route::get('admin/places/new', 'PlacesAdminController@create'); + Route::get('admin/places/edit/{id}', 'PlacesAdminController@edit'); + Route::post('admin/places/new', 'PlacesAdminController@store'); + Route::post('admin/places/edit/{id}', 'PlacesAdminController@update'); }); //Blog pages using ArticlesController 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/ArticlesTest.php b/tests/Browser/ArticlesTest.php new file mode 100644 index 00000000..2bb7cba2 --- /dev/null +++ b/tests/Browser/ArticlesTest.php @@ -0,0 +1,61 @@ +browse(function ($browser) { + $browser->visit('/blog') + ->assertSee('My New Blog'); + }); + } + + /** + * Test the `/blog` page with a year scoping results. + * + * @return void + */ + public function test_articles_page_with_specified_year() + { + $this->browse(function ($browser) { + $browser->visit('/blog/2016') + ->assertSee('My New Blog'); + }); + } + + /** + * Test the `/blog` page with a year and month scoping results. + * + * @return void + */ + public function test_articles_page_with_specified_year_and_month() + { + $this->browse(function ($browser) { + $browser->visit('/blog/2016/01') + ->assertSee('My New Blog'); + }); + } + + /** + * Test a single article page. + * + * @return void + */ + public function test_single_article_page() + { + $this->browse(function ($browser) { + $browser->visit('/blog/2016/01/my-new-blog') + ->assertSee('My New Blog'); + }); + } +} diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php new file mode 100644 index 00000000..5430fc54 --- /dev/null +++ b/tests/Browser/ExampleTest.php @@ -0,0 +1,23 @@ +browse(function (Browser $browser) { + $browser->visit('/') + ->assertSee('Built with love'); + }); + } +} diff --git a/tests/Browser/MicropubClientTest.php b/tests/Browser/MicropubClientTest.php new file mode 100644 index 00000000..25cece44 --- /dev/null +++ b/tests/Browser/MicropubClientTest.php @@ -0,0 +1,47 @@ +browse(function ($browser) { + $browser->visit(route('micropub-client')) + ->assertSee('You are authenticated'); + }); + } + + public function test_client_page_creates_new_note() + { + $faker = \Faker\Factory::create(); + $note = 'Fake note from #LaravelDusk: ' . $faker->text; + $this->browse(function ($browser) use ($note) { + $browser->visit(route('micropub-client')) + ->type('content', $note) + ->press('Submit'); + }); + $this->assertDatabaseHas('notes', ['note' => $note]); + $newNote = \App\Note::where('note', $note)->first(); + $newNote->forceDelete(); + } + + public function test_client_page_updates_syndication() + { + $this->browse(function ($browser) { + $browser->visit(route('micropub-client')) + ->assertDontSee('jonnybarnes on Twitter') + ->clickLink('Refresh Syndication Targets') + ->pause(5000) + ->assertSee('jonnybarnes on Twitter'); + }); + } +} diff --git a/tests/Browser/NotesTest.php b/tests/Browser/NotesTest.php new file mode 100644 index 00000000..81a59b3f --- /dev/null +++ b/tests/Browser/NotesTest.php @@ -0,0 +1,35 @@ +browse(function ($browser) { + $browser->visit('/notes/D') + ->assertSee('JBL5'); + }); + } + + /** + * Look for the client URL after the note. + * + * @return void + */ + public function test_client_url_displayed() + { + $this->browse(function ($browser) { + $browser->visit('/notes/E') + ->assertSee('quill.p3k.io'); + }); + } +} 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('/feed'); + $response->assertHeader('Content-Type', 'application/rss+xml'); + } +} diff --git a/tests/Feature/ContactsTest.php b/tests/Feature/ContactsTest.php new file mode 100644 index 00000000..643d37dc --- /dev/null +++ b/tests/Feature/ContactsTest.php @@ -0,0 +1,44 @@ +get('/contacts'); + $response->assertStatus(200); + } + + /** + * Test an individual contact page with default profile image. + * + * @return void + */ + public function test_contact_page_with_default_pic() + { + $response = $this->get('/contacts/tantek'); + $response->assertViewHas('image', '/assets/profile-images/default-image'); + } + + /** + * Test an individual contact page with a specific profile image. + * + * @return void + */ + public function test_contact_page_with_specific_pic() + { + $response = $this->get('/contacts/aaron'); + $response->assertViewHas('image', '/assets/profile-images/aaronparecki.com/image'); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 00000000..ed0d1cef --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,22 @@ +get('/'); + $response->assertStatus(200); + } +} diff --git a/tests/Feature/IndieAuthControllerTest.php b/tests/Feature/IndieAuthControllerTest.php new file mode 100644 index 00000000..c5ed4de1 --- /dev/null +++ b/tests/Feature/IndieAuthControllerTest.php @@ -0,0 +1,51 @@ +call('GET', '/indieauth/start', ['me' => 'http://example.org']); + $this->assertSame(config('app.url') . '/micropub/create', $response->headers->get('Location')); + } + + /** + * Now we test the `start` method as a whole. + * + * @return void + */ + public function test_indieauthcontroller_begin_auth_redirects_to_endpoint() + { + $response = $this->call('GET', '/indieauth/start', ['me' => config('app.url')]); + $this->assertSame( + 'https://indieauth.com/auth?me=', + substr($response->headers->get('Location'), 0, 30) + ); + } + + /** + * Test the `callback` method. + * + * @return void + */ + public function test_indieauthcontroller_callback_method_gives_error_with_mismatched_state() + { + $response = $this->withSession(['state' => 'state-session']) + ->call( + 'GET', + 'indieauth/callback', + ['me', config('app.url'), 'state' => 'request-session'] + ); + $response->assertSessionHasErrors(); + } +} diff --git a/tests/Feature/MicropubControllerTest.php b/tests/Feature/MicropubControllerTest.php new file mode 100644 index 00000000..536e08f1 --- /dev/null +++ b/tests/Feature/MicropubControllerTest.php @@ -0,0 +1,316 @@ +get('/api/post'); + $response->assertStatus(400); + $response->assertJsonFragment(['error_description' => 'No token provided with request']); + } + + /** + * Test a GET request for the micropub endpoint without a valid token gives + * a 400 response. Also check the error message. + * + * @return void + */ + public function test_micropub_request_without_valid_token_returns_400_response() + { + $response = $this->call('GET', '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer abc123']); + $response->assertStatus(400); + $response->assertJsonFragment(['error_description' => 'The provided token did not pass validation']); + } + + /** + * Test a GET request for the micropub endpoint with a valid token gives a + * 200 response. Check token information is returned in the response. + * + * @return void + */ + public function test_micropub_request_with_valid_token_returns_200_response() + { + $response = $this->call('GET', '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $response->assertStatus(200); + $response->assertJsonFragment(['response' => 'token']); + } + + /** + * Test a GET request for syndication targets. + * + * @return void + */ + public function test_micropub_request_for_syndication_targets() + { + $response = $this->call('GET', '/api/post', ['q' => 'syndicate-to'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $response->assertJsonFragment(['uid' => 'https://twitter.com/jonnybarnes']); + } + + /** + * Test a request for places. + * + * @return void + */ + public function test_micropub_request_for_nearby_places() + { + $response = $this->call('GET', '/api/post', ['q' => 'geo:53.5,-2.38'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $response->assertJson(['places' => [['slug' =>'the-bridgewater-pub']]]); + } + + /** + * Test a request for places, this time with an uncertainty parameter. + * + * @return void + */ + public function test_micropub_request_for_nearby_places_with_uncertainty_parameter() + { + $response = $this->call('GET', '/api/post', ['q' => 'geo:53.5,-2.38;u=35'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $response->assertJson(['places' => [['slug' => 'the-bridgewater-pub']]]); + } + + /** + * Test a request for places, where there will be an “empty” response. + * + * @return void + */ + public function test_micropub_request_for_nearby_places_where_non_exist() + { + $response = $this->call('GET', '/api/post', ['q' => 'geo:1.23,4.56'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $response->assertJson(['places' => []]); + } + + /** + * Test a request for the micropub config. + * + * @return void + */ + public function test_micropub_request_for_config() + { + $response = $this->call('GET', '/api/post', ['q' => 'config'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $response->assertJsonFragment(['uid' => 'https://twitter.com/jonnybarnes']); + } + + /** + * Test a valid micropub requests creates a new note. + * + * @return void + */ + public function test_micropub_request_creates_new_note() + { + $faker = \Faker\Factory::create(); + $note = $faker->text; + $response = $this->call( + 'POST', + '/api/post', + [ + 'h' => 'entry', + 'content' => $note + ], + [], + [], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $response->assertJson(['response' => 'created']); + $this->assertDatabaseHas('notes', ['note' => $note]); + } + + /** + * Test a valid micropub requests creates a new place. + * + * @return void + */ + public function test_micropub_request_creates_new_place() + { + $response = $this->call( + 'POST', + '/api/post', + [ + 'h' => 'card', + 'name' => 'The Barton Arms', + 'geo' => 'geo:53.4974,-2.3768' + ], + [], + [], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $response->assertJson(['response' => 'created']); + $this->assertDatabaseHas('places', ['slug' => 'the-barton-arms']); + } + + /** + * Test a valid micropub requests using JSON syntax creates a new note. + * + * @return void + */ + public function test_micropub_request_with_json_syntax_creates_new_note() + { + $faker = \Faker\Factory::create(); + $note = $faker->text; + $response = $this->json( + 'POST', + '/api/post', + [ + 'type' => ['h-entry'], + 'properties' => [ + 'content' => [$note], + ], + ], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $response + ->assertStatus(201) + ->assertJson(['response' => 'created']); + } + + /** + * Test a micropub requests using JSON syntax without a token returns an + * error. Also check the message. + * + * @return void + */ + public function test_micropub_request_with_json_syntax_without_token_returns_error() + { + $faker = \Faker\Factory::create(); + $note = $faker->text; + $response = $this->json( + 'POST', + '/api/post', + [ + 'type' => ['h-entry'], + 'properties' => [ + 'content' => [$note], + ], + ] + ); + $response + ->assertJson([ + 'response' => 'error', + 'error' => 'no_token' + ]) + ->assertStatus(400); + } + + /** + * Test a micropub requests using JSON syntax without a valis token returns + * an error. Also check the message. + * + * @return void + */ + public function test_micropub_request_with_json_syntax_with_invalid_token_returns_error() + { + $faker = \Faker\Factory::create(); + $note = $faker->text; + $response = $this->json( + 'POST', + '/api/post', + [ + 'type' => ['h-entry'], + 'properties' => [ + 'content' => [$note], + ], + ], + ['HTTP_Authorization' => 'Bearer ' . $this->getInvalidToken()] + ); + $response + ->assertJson([ + 'response' => 'error', + 'error' => 'invalid_token' + ]) + ->assertStatus(400); + } + + public function test_micropub_request_with_json_syntax_creates_new_place() + { + $faker = \Faker\Factory::create(); + $response = $this->json( + 'POST', + '/api/post', + [ + 'type' => ['h-card'], + 'properties' => [ + 'name' => $faker->name, + 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude + ], + ], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $response + ->assertJson(['response' => 'created']) + ->assertStatus(201); + } + + public function test_micropub_request_with_json_syntax_and_uncertainty_parameter_creates_new_place() + { + $faker = \Faker\Factory::create(); + $response = $this->json( + 'POST', + '/api/post', + [ + 'type' => ['h-card'], + 'properties' => [ + 'name' => $faker->name, + 'geo' => 'geo:' . $faker->latitude . ',' . $faker->longitude . ';u=35' + ], + ], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $response + ->assertJson(['response' => 'created']) + ->assertStatus(201); + } + + /** + * Generate a valid token to be used in the tests. + * + * @return Lcobucci\JWT\Token\Plain $token + */ + 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; + } + + /** + * Generate an invalid token to be used in the tests. + * + * @return Lcobucci\JWT\Token\Plain $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') //error here + ->set('issued_at', time()) + ->sign($signer, env('APP_KEY')) + ->getToken(); + + return $token; + } +} diff --git a/tests/Feature/NotesControllerTest.php b/tests/Feature/NotesControllerTest.php new file mode 100644 index 00000000..df1518bb --- /dev/null +++ b/tests/Feature/NotesControllerTest.php @@ -0,0 +1,57 @@ +get('/notes'); + $response->assertStatus(200); + } + + /** + * Test a specific note. + * + * @return void + */ + public function test_specific_note() + { + $response = $this->get('/notes/D'); + $response->assertViewHas('note'); + } + + /** + * Test that `/note/{decID}` redirects to `/notes/{nb60id}`. + * + * @return void + */ + public function test_dec_id_redirect() + { + $response = $this->get('/note/11'); + $response->assertRedirect(config('app.url') . '/notes/B'); + } + + /** + * Visit the tagged page and check the tag view data. + * + * @return void + */ + public function test_tagged_notes_page() + { + $response = $this->get('/notes/tagged/beer'); + $response->assertViewHas('tag', 'beer'); + } +} diff --git a/tests/Feature/PlacesTest.php b/tests/Feature/PlacesTest.php new file mode 100644 index 00000000..fca57e0f --- /dev/null +++ b/tests/Feature/PlacesTest.php @@ -0,0 +1,34 @@ +get('/places'); + $response->assertStatus(200); + } + + /** + * Test a specific place. + * + * @return void + */ + public function test_single_place() + { + $place = \App\Place::where('slug', 'the-bridgewater-pub')->first(); + $response = $this->get('/places/the-bridgewater-pub'); + $response->assertViewHas('place', $place); + } +} diff --git a/tests/TokenServiceTest.php b/tests/Feature/TokenServiceTest.php similarity index 62% rename from tests/TokenServiceTest.php rename to tests/Feature/TokenServiceTest.php index 8ca43fd7..01ac54d0 100644 --- a/tests/TokenServiceTest.php +++ b/tests/Feature/TokenServiceTest.php @@ -1,38 +1,30 @@ 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() + public function test_token_creation_and_validation() { + $tokenService = new \App\Services\TokenService(); $data = [ 'me' => 'https://example.org', 'client_id' => 'https://quill.p3k.io', 'scope' => 'post' ]; - $token = $this->tokenService->getNewToken($data); - $valid = $this->tokenService->validateToken($token); + $token = $tokenService->getNewToken($data); + $valid = $tokenService->validateToken($token); $validData = [ 'me' => $valid->getClaim('me'), 'client_id' => $valid->getClaim('client_id'), diff --git a/tests/Feature/WebMentionsControllerTest.php b/tests/Feature/WebMentionsControllerTest.php new file mode 100644 index 00000000..163eb4ce --- /dev/null +++ b/tests/Feature/WebMentionsControllerTest.php @@ -0,0 +1,84 @@ +call('POST', '/webmention', ['source' => 'https://example.org/post/123']); + $response->assertStatus(400); + } + + /** + * Test invalid target gets a 400 response. + * + * @return void + */ + public function test_invalid_target_returns_400_response() + { + $response = $this->call('POST', '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => config('app.url') . '/invalid/target' + ]); + $response->assertStatus(400); + } + + /** + * Test blog target gets a 501 response due to me not supporting it. + * + * @return void + */ + public function test_blog_target_returns_501_response() + { + $response = $this->call('POST', '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => config('app.url') . '/blog/target' + ]); + $response->assertStatus(501); + } + + /** + * Test that a non-existant note gives a 400 response. + * + * @return void + */ + public function test_nonexistant_note_returns_400_response() + { + $response = $this->call('POST', '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => config('app.url') . '/notes/ZZZZZ' + ]); + $response->assertStatus(400); + } + + /** + * Test a legit webmention triggers the ProcessWebMention job. + * + * @return void + */ + public function test_legitimate_webmnetion_triggers_processwebmention_job() + { + Queue::fake(); + + $response = $this->call('POST', '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => config('app.url') . '/notes/B' + ]); + $response->assertStatus(202); + + Queue::assertPushed(ProcessWebMention::class); + } +} 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/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/Unit/IndieAuthServiceTest.php b/tests/Unit/IndieAuthServiceTest.php new file mode 100644 index 00000000..3035d8f5 --- /dev/null +++ b/tests/Unit/IndieAuthServiceTest.php @@ -0,0 +1,69 @@ +getAuthorizationEndpoint(config('app.url'), $client); + $this->assertEquals('https://indieauth.com/auth', $result); + } + + /** + * Test that the Service build the correct redirect URL. + * + * @return void + */ + public function test_indieauthservice_builds_correct_redirect_url() + { + $service = new \App\Services\IndieAuthService(); + $client = new \IndieAuth\Client(); + $result = $service->buildAuthorizationURL( + 'https://indieauth.com/auth', + config('app.url'), + $client + ); + $this->assertEquals( + 'https://indieauth.com/auth?me=', + substr($result, 0, 30) + ); + } + + /** + * Test the getTokenEndpoint method. + * + * @return void + */ + public function test_indieauthservice_gettokenendpoint_method() + { + $service = new \App\Services\IndieAuthService(); + $client = new \IndieAuth\Client(); + $result = $service->getTokenEndpoint(config('app.url'), $client); + $this->assertEquals(config('app.url') . '/api/token', $result); + } + + /** + * Test the discoverMicropubEndpoint method. + * + * @return void + */ + public function test_indieauthservice_discovermicropubendpoint_method() + { + $service = new \App\Services\IndieAuthService(); + $client = new \IndieAuth\Client(); + $result = $service->discoverMicropubEndpoint(config('app.url'), $client); + $this->assertEquals(config('app.url') . '/api/post', $result); + } +} diff --git a/tests/Unit/NotesControllerTest.php b/tests/Unit/NotesControllerTest.php new file mode 100644 index 00000000..2e654f5e --- /dev/null +++ b/tests/Unit/NotesControllerTest.php @@ -0,0 +1,71 @@ +notesController = new NotesController(); + } + + /** + * Test a correct profile link is formed from a generic URL. + * + * @return void + */ + public function test_create_photo_link_with_non_cached_image() + { + $notesController = new \App\Http\Controllers\NotesController(); + $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 (cached). + * + * @return void + */ + public function test_create_photo_link_with_cached_image() + { + $notesController = new \App\Http\Controllers\NotesController(); + $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 test_create_photo_link_with_twimg_profile_image_url() + { + $notesController = new \App\Http\Controllers\NotesController(); + $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 test_create_photo_link_with_cached_twitter_url() + { + $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/Unit/NotesTest.php b/tests/Unit/NotesTest.php new file mode 100644 index 00000000..eef40e72 --- /dev/null +++ b/tests/Unit/NotesTest.php @@ -0,0 +1,70 @@ +Having a at the local. 🍺

' . PHP_EOL; + $note = Note::find(11); + $this->assertEquals($expected, $note->note); + } + + /** + * Look for a default image in the contact’s h-card for the makeHCards method. + * + * @return void + */ + public function test_default_image_used_in_makehcards_method() + { + $expected = '

Hi + + + Tantek Çelik + +

' . PHP_EOL; + $note = Note::find(12); + $this->assertEquals($expected, $note->note); + } + + /** + * Look for a specific profile image in the contact’s h-card. + * + * @return void + */ + public function test_specific_profile_image_used_in_makehcards_method() + { + $expected = '

Hi + + + Aaron Parecki + +

' . PHP_EOL; + $note = Note::find(13); + $this->assertEquals($expected, $note->note); + } + + /** + * Look for twitter URL when there’s no associated contact. + * + * @return void + */ + public function test_twitter_link_created_when_no_contact_found() + { + $expected = '

Hi @bob

' . PHP_EOL; + $note = Note::find(14); + $this->assertEquals($expected, $note->note); + } +} diff --git a/tests/Unit/PlacesTest.php b/tests/Unit/PlacesTest.php new file mode 100644 index 00000000..0a981725 --- /dev/null +++ b/tests/Unit/PlacesTest.php @@ -0,0 +1,22 @@ +assertEquals('the-bridgewater-pub', $nearby[0]->slug); + } +} 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/travis/default-site.tpl.conf b/travis/default-site.tpl.conf new file mode 100644 index 00000000..3e41e4e8 --- /dev/null +++ b/travis/default-site.tpl.conf @@ -0,0 +1,20 @@ +server { + listen 8000 default_server; + listen [::]:8000 default_server ipv6only=on; + + root {ROOT}/public; + index index.php; + + access_log /tmp/access.log; + error_log /tmp/error.log; + + location / { + # First attempt to serve request as file, then as directory, then fall back to index.php. + try_files $uri $uri/ /index.php$is_args$args; + } + + location ~* "\.php(/|$)" { + include fastcgi.conf; + fastcgi_pass php; + } +} diff --git a/travis/fastcgi.tpl.conf b/travis/fastcgi.tpl.conf new file mode 100644 index 00000000..28ccdeb6 --- /dev/null +++ b/travis/fastcgi.tpl.conf @@ -0,0 +1,39 @@ +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; + +fastcgi_split_path_info ^(.+\.php)(.*)$; +fastcgi_param PATH_INFO $fastcgi_path_info; +#fastcgi_index index.php; +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + +# fastcgi_intercept_errors on; +fastcgi_ignore_client_abort off; +fastcgi_connect_timeout 60; +fastcgi_send_timeout 1800; +fastcgi_read_timeout 1800; +fastcgi_buffer_size 128k; +fastcgi_buffers 4 256k; +fastcgi_busy_buffers_size 256k; +fastcgi_temp_file_write_size 256k; +fastcgi_keep_conn on; diff --git a/travis/install-nginx.sh b/travis/install-nginx.sh new file mode 100755 index 00000000..d74b44a8 --- /dev/null +++ b/travis/install-nginx.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e +set -x + +DIR=$(realpath $(dirname "$0")) +USER=$(whoami) +PHP_VERSION=$(phpenv version-name) +ROOT=$(realpath "$DIR/..") +PORT=9000 +SERVER="/tmp/php.sock" + +function tpl { + sed \ + -e "s|{DIR}|$DIR|g" \ + -e "s|{USER}|$USER|g" \ + -e "s|{PHP_VERSION}|$PHP_VERSION|g" \ + -e "s|{ROOT}|$ROOT|g" \ + -e "s|{PORT}|$PORT|g" \ + -e "s|{SERVER}|$SERVER|g" \ + < $1 > $2 +} + +# Make some working directories. +mkdir "$DIR/nginx" +mkdir "$DIR/nginx/sites-enabled" +mkdir "$DIR/var" + +PHP_FPM_BIN="$HOME/.phpenv/versions/$PHP_VERSION/sbin/php-fpm" +PHP_FPM_CONF="$DIR/nginx/php-fpm.conf" + +# Build the php-fpm.conf. +tpl "$DIR/php-fpm.tpl.conf" "$PHP_FPM_CONF" + +# Start php-fpm +"$PHP_FPM_BIN" --fpm-config "$PHP_FPM_CONF" + +# Build the default nginx config files. +tpl "$DIR/nginx.tpl.conf" "$DIR/nginx/nginx.conf" +tpl "$DIR/fastcgi.tpl.conf" "$DIR/nginx/fastcgi.conf" +tpl "$DIR/default-site.tpl.conf" "$DIR/nginx/sites-enabled/default-site.conf" + +# Start nginx. +nginx -c "$DIR/nginx/nginx.conf" diff --git a/travis/nginx.tpl.conf b/travis/nginx.tpl.conf new file mode 100644 index 00000000..13a26ad4 --- /dev/null +++ b/travis/nginx.tpl.conf @@ -0,0 +1,52 @@ +error_log /tmp/error.log; +pid /tmp/nginx.pid; +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + # Set an array of temp and cache file options that will otherwise default to restricted locations accessible only to root. + client_body_temp_path /tmp/client_body; + fastcgi_temp_path /tmp/fastcgi_temp; + proxy_temp_path /tmp/proxy_temp; + scgi_temp_path /tmp/scgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + + ## + # Basic Settings + ## + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + # server_tokens off; + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # Logging Settings + ## + access_log /tmp/access.log; + error_log /tmp/error.log; + + ## + # Gzip Settings + ## + gzip on; + gzip_disable "msie6"; + + ## + # Virtual Host Configs + ## + # include {DIR}/nginx/conf.d/*.conf; + include {DIR}/nginx/sites-enabled/*; + + upstream php { + server 127.0.0.1:{PORT}; + } +} diff --git a/travis/php-fpm.tpl.conf b/travis/php-fpm.tpl.conf new file mode 100644 index 00000000..fdbf2aa8 --- /dev/null +++ b/travis/php-fpm.tpl.conf @@ -0,0 +1,9 @@ +[global] + +[travis] +user = {USER} +listen = {PORT} +listen.mode = 0666 +pm = static +pm.max_children = 5 +php_admin_value[memory_limit] = 32M