diff --git a/CHANGELOG.md b/CHANGELOG.md index f2312db..7ee14b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [unreleased] +### Added + +- Add a graphical interface to download themes in the admin dashboard. + ### Fixed - Fix admin pannel's highlight preview overflow on mobile screens. diff --git a/assets/Makefile b/assets/Makefile index fe922b6..2d103a7 100644 --- a/assets/Makefile +++ b/assets/Makefile @@ -3,7 +3,7 @@ JSREV = 9-18-stable REPO = https://github.com/highlightjs/cdn-release THEMES = github github-dark base16/monokai -all: highlight.min.js $(THEMES:=.min.css) +all: highlight.min.js styles.min.js $(THEMES:=.min.css) %.min.js: .FORCE wget -q $(REPO)/raw/$(JSREV)/build/$(@F) -O $@ @@ -11,5 +11,11 @@ all: highlight.min.js $(THEMES:=.min.css) %.min.css: .FORCE wget -q $(REPO)/raw/$(CSSREV)/build/styles/$*.min.css -O $@ +styles.min.js: .FORCE + printf 'let hljsStyles = ' > $@ + curl -s https://api.github.com/repos/highlightjs/cdn-release/git/trees/main?recursive=1 \ + | jq -c '[.tree[].path|select(.|startswith("build/styles/") and endswith(".min.css"))|ltrimstr("build/styles/")|rtrimstr(".min.css")]' \ + >> $@ + .FORCE:; .PHONY: .FORCE diff --git a/assets/styles.min.js b/assets/styles.min.js new file mode 100644 index 0000000..6f482c2 --- /dev/null +++ b/assets/styles.min.js @@ -0,0 +1 @@ +let hljsStyles = ["a11y-dark","a11y-light","agate","an-old-hope","androidstudio","arduino-light","arta","ascetic","atom-one-dark-reasonable","atom-one-dark","atom-one-light","base16/3024","base16/apathy","base16/apprentice","base16/ashes","base16/atelier-cave-light","base16/atelier-cave","base16/atelier-dune-light","base16/atelier-dune","base16/atelier-estuary-light","base16/atelier-estuary","base16/atelier-forest-light","base16/atelier-forest","base16/atelier-heath-light","base16/atelier-heath","base16/atelier-lakeside-light","base16/atelier-lakeside","base16/atelier-plateau-light","base16/atelier-plateau","base16/atelier-savanna-light","base16/atelier-savanna","base16/atelier-seaside-light","base16/atelier-seaside","base16/atelier-sulphurpool-light","base16/atelier-sulphurpool","base16/atlas","base16/bespin","base16/black-metal-bathory","base16/black-metal-burzum","base16/black-metal-dark-funeral","base16/black-metal-gorgoroth","base16/black-metal-immortal","base16/black-metal-khold","base16/black-metal-marduk","base16/black-metal-mayhem","base16/black-metal-nile","base16/black-metal-venom","base16/black-metal","base16/brewer","base16/bright","base16/brogrammer","base16/brush-trees-dark","base16/brush-trees","base16/chalk","base16/circus","base16/classic-dark","base16/classic-light","base16/codeschool","base16/colors","base16/cupcake","base16/cupertino","base16/danqing","base16/darcula","base16/dark-violet","base16/darkmoss","base16/darktooth","base16/decaf","base16/default-dark","base16/default-light","base16/dirtysea","base16/dracula","base16/edge-dark","base16/edge-light","base16/eighties","base16/embers","base16/equilibrium-dark","base16/equilibrium-gray-dark","base16/equilibrium-gray-light","base16/equilibrium-light","base16/espresso","base16/eva-dim","base16/eva","base16/flat","base16/framer","base16/fruit-soda","base16/gigavolt","base16/github","base16/google-dark","base16/google-light","base16/grayscale-dark","base16/grayscale-light","base16/green-screen","base16/gruvbox-dark-hard","base16/gruvbox-dark-medium","base16/gruvbox-dark-pale","base16/gruvbox-dark-soft","base16/gruvbox-light-hard","base16/gruvbox-light-medium","base16/gruvbox-light-soft","base16/hardcore","base16/harmonic16-dark","base16/harmonic16-light","base16/heetch-dark","base16/heetch-light","base16/helios","base16/hopscotch","base16/horizon-dark","base16/horizon-light","base16/humanoid-dark","base16/humanoid-light","base16/ia-dark","base16/ia-light","base16/icy-dark","base16/ir-black","base16/isotope","base16/kimber","base16/london-tube","base16/macintosh","base16/marrakesh","base16/materia","base16/material-darker","base16/material-lighter","base16/material-palenight","base16/material-vivid","base16/material","base16/mellow-purple","base16/mexico-light","base16/mocha","base16/monokai","base16/nebula","base16/nord","base16/nova","base16/ocean","base16/oceanicnext","base16/one-light","base16/onedark","base16/outrun-dark","base16/papercolor-dark","base16/papercolor-light","base16/paraiso","base16/pasque","base16/phd","base16/pico","base16/pop","base16/porple","base16/qualia","base16/railscasts","base16/rebecca","base16/ros-pine-dawn","base16/ros-pine-moon","base16/ros-pine","base16/sagelight","base16/sandcastle","base16/seti-ui","base16/shapeshifter","base16/silk-dark","base16/silk-light","base16/snazzy","base16/solar-flare-light","base16/solar-flare","base16/solarized-dark","base16/solarized-light","base16/spacemacs","base16/summercamp","base16/summerfruit-dark","base16/summerfruit-light","base16/synth-midnight-terminal-dark","base16/synth-midnight-terminal-light","base16/tango","base16/tender","base16/tomorrow-night","base16/tomorrow","base16/twilight","base16/unikitty-dark","base16/unikitty-light","base16/vulcan","base16/windows-10-light","base16/windows-10","base16/windows-95-light","base16/windows-95","base16/windows-high-contrast-light","base16/windows-high-contrast","base16/windows-nt-light","base16/windows-nt","base16/woodland","base16/xcode-dusk","base16/zenburn","brown-paper","codepen-embed","color-brewer","dark","default","devibeans","docco","far","foundation","github-gist","github","gml","googlecode","gradient-dark","gradient-light","grayscale","hybrid","idea","ir-black","isbl-editor-dark","isbl-editor-light","kimbie-dark","kimbie-light","lightfair","lioshi","magula","mono-blue","monokai-sublime","monokai","night-owl","nnfx-dark","nnfx-light","nord","obsidian","paraiso-dark","paraiso-light","pojoaque","purebasic","qtcreator-dark","qtcreator-light","rainbow","routeros","school-book","shades-of-purple","srcery","stackoverflow-dark","stackoverflow-light","sunburst","tomorrow-night-blue","tomorrow-night-bright","vs","vs2015","xcode","xt256"] diff --git a/extend.php b/extend.php index 356fe00..434a810 100644 --- a/extend.php +++ b/extend.php @@ -22,10 +22,15 @@ */ use Club1\ServerSideHighlight\Console\DownloadCommand; +use Club1\ServerSideHighlight\Controller\DownloadController; use Club1\ServerSideHighlight\Controller\PreviewController; +use Club1\ServerSideHighlight\Exception\InvalidArgumentException; +use Club1\ServerSideHighlight\Exception\IOException; +use Club1\ServerSideHighlight\Exception\KnownExceptionHandler; use Club1\ServerSideHighlight\Formatter\Configurator; use Club1\ServerSideHighlight\Formatter\Renderer; -use Club1\ServerSideHighlight\Frontend\Content; +use Club1\ServerSideHighlight\Frontend\Admin; +use Club1\ServerSideHighlight\Frontend\Forum; use Club1\ServerSideHighlight\Serializer\HighlightThemeSerializer; use Flarum\Extend; @@ -35,17 +40,23 @@ ->render(Renderer::class), (new Extend\Frontend('forum')) - ->content(Content::class) + ->content(Forum::class) ->js(__DIR__.'/js/forum.js') ->css(__DIR__.'/css/forum.css'), (new Extend\Frontend('admin')) + ->content(Admin::class) ->js(__DIR__.'/js/admin.js') ->css(__DIR__.'/css/admin.css'), (new Extend\Routes('api')) + ->get('/highlight-download', 'club-1-server-side-highlight.download', DownloadController::class) ->get('/highlight-preview', 'club-1-server-side-highlight.preview', PreviewController::class), + (new Extend\ErrorHandling) + ->handler(InvalidArgumentException::class, KnownExceptionHandler::class) + ->handler(IOException::class, KnownExceptionHandler::class), + (new Extend\Settings()) ->default('club-1-server-side-highlight.light_theme_bg_color', '#f7f7f7') ->default('club-1-server-side-highlight.light_theme_text_color', '#000000') diff --git a/js/admin.js b/js/admin.js index b963c4f..3bf16e6 100644 --- a/js/admin.js +++ b/js/admin.js @@ -1,7 +1,32 @@ +let hljsStyle; +let themes; + app.initializers.add('server-side-highlight', function(app) { - const themes = JSON.parse(app.data.settings['club-1-server-side-highlight.available_themes']); + themes = JSON.parse(app.data.settings['club-1-server-side-highlight.available_themes']); app.extensionData .for('club-1-server-side-highlight') + .registerSetting(function () { + return [ + m('div.Form-group', [ + m('label', app.translator.trans('club-1-server-side-highlight.admin.download_theme')), + m('span.Select', { + onchange: e => hljsStyle = e.target.value, + value: hljsStyle, + }, [ + m('select.Select-input.FormControl', [ + m('option', {disabled: true, selected: !hljsStyle}, app.translator.trans('club-1-server-side-highlight.admin.choose_theme')), + hljsStyles.map(x => m('option', x)), + ]), + m('i.icon.fas.fa-sort.Select-caret'), + ]), + ]), + m('button.Button.Button--primary', { + onclick: () => download.bind(this)(hljsStyle), + disabled: !hljsStyle, + }, app.translator.trans('club-1-server-side-highlight.admin.download')), + m('hr') + ]; + }) .registerSetting({ setting: 'club-1-server-side-highlight.light_theme_bg_color', label: app.translator.trans('club-1-server-side-highlight.admin.light_theme_bg_color'), @@ -46,10 +71,36 @@ function preview(mode) { let bg = encodeURIComponent(this.setting(`club-1-server-side-highlight.${mode}_theme_bg_color`)()); let text = encodeURIComponent(this.setting(`club-1-server-side-highlight.${mode}_theme_text_color`)()); let theme = encodeURIComponent(this.setting(`club-1-server-side-highlight.${mode}_theme_highlight_theme`)()); - return m('div.Form-group', m('iframe', { + return m('div.Form-group', m('iframe.highlight-preview', { src: app.forum.attribute('baseUrl') + `/api/highlight-preview?bg=${bg}&text=${text}&theme=${theme}`, - class: 'highlight-preview', })); } +function download(style) { + hljsStyle = null; + app.request({ + method: 'GET', + url: '/api/highlight-download', + params: {name: style}, + errorHandler, + }) + .then(res => { + for (const [key, value] of Object.entries(res)) { + themes[key] = value; + } + app.alerts.show({type: 'success'}, app.translator.trans('club-1-server-side-highlight.admin.download_success')); + m.redraw(); + }) +} + +function errorHandler(err) { + let msgs = err.response.errors.filter(e => e.message).map(e => e.message); + if (msgs.length == 0) { + app.alerts.show({type: err.alert.type}, err.alert.content); + } else { + msgs.forEach(m => app.alerts.show({type: 'error'}, m)); + } +} + + module.exports = {} diff --git a/locale/en.yml b/locale/en.yml index 39ceba4..a37b8d7 100644 --- a/locale/en.yml +++ b/locale/en.yml @@ -1,5 +1,9 @@ club-1-server-side-highlight: admin: + download_theme: 'Download a theme' + choose_theme: 'Choose a theme' + download: 'Download' + download_success: 'Theme downloaded successfully' light_theme_bg_color: 'Light theme background color' light_theme_text_color: 'Light theme text color' light_theme_highlight_theme: 'Light theme highlight theme' diff --git a/locale/fr.yml b/locale/fr.yml index 39b0908..f5298b4 100644 --- a/locale/fr.yml +++ b/locale/fr.yml @@ -1,5 +1,9 @@ club-1-server-side-highlight: admin: + download_theme: 'Télécharger un thème' + choose_theme: 'Selectionner un thème' + download: 'Télécharger' + download_success: 'Thème téléchargé avec succes' light_theme_bg_color: 'Couleur de fond pour le thème clair' light_theme_text_color: 'Couleur du texte pour le thème clair' light_theme_highlight_theme: 'Thème de couleur pour le thème clair' diff --git a/src/Console/DownloadCommand.php b/src/Console/DownloadCommand.php index 7f6603a..b2a85df 100644 --- a/src/Console/DownloadCommand.php +++ b/src/Console/DownloadCommand.php @@ -24,6 +24,8 @@ namespace Club1\ServerSideHighlight\Console; use Club1\ServerSideHighlight\Consts; +use Club1\ServerSideHighlight\Exception\InvalidArgumentException; +use Club1\ServerSideHighlight\Exception\IOException; use ErrorException; use Flarum\Console\AbstractCommand; use Flarum\Settings\SettingsRepositoryInterface; @@ -69,10 +71,14 @@ protected function fire(): void try { $remote = fopen($remoteFile, 'r'); assert(is_resource($remote)); + } catch (\Throwable $t) { + throw new InvalidArgumentException("Could not download theme $name", 1, $t); + } + try { $this->assetsDisk->put($localFile, $remote); } catch (\Throwable $t) { $this->assetsDisk->delete($localFile); - throw $t; + throw new IOException("Could not write theme to assets", 2, $t); } $this->info("Updating available highlight themes..."); diff --git a/src/Controller/DownloadController.php b/src/Controller/DownloadController.php new file mode 100644 index 0000000..0fb69f1 --- /dev/null +++ b/src/Controller/DownloadController.php @@ -0,0 +1,62 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Controller; + +use Club1\ServerSideHighlight\Console\DownloadCommand; +use Flarum\Http\RequestUtil; +use Flarum\Settings\SettingsRepositoryInterface; +use Laminas\Diactoros\Response\TextResponse; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\RequestHandlerInterface; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\NullOutput; + +class DownloadController implements RequestHandlerInterface +{ + /** @var SettingsRepositoryInterface */ + protected $settings; + + /** @var DownloadCommand */ + protected $command; + + public function __construct(SettingsRepositoryInterface $settings, DownloadCommand $command) + { + $this->settings = $settings; + $this->command = $command; + } + + public function handle(Request $request): Response + { + $actor = RequestUtil::getActor($request); + $actor->assertAdmin(); + $name = $request->getQueryParams()['name']; + $this->command->run(new ArrayInput(['name' => $name]), new NullOutput); + return new TextResponse( + $this->settings->get('club-1-server-side-highlight.available_themes'), + 200, + ['Content-Type' => ['application/json']] + ); + } +} diff --git a/src/Exception/IOException.php b/src/Exception/IOException.php new file mode 100644 index 0000000..e655665 --- /dev/null +++ b/src/Exception/IOException.php @@ -0,0 +1,39 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Exception; + +use RuntimeException; + +class IOException extends RuntimeException implements KnownException +{ + public function getType(): string + { + return 'io_error'; + } + + public function getStatusCode(): int + { + return 409; + } +} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..839bf81 --- /dev/null +++ b/src/Exception/InvalidArgumentException.php @@ -0,0 +1,37 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements KnownException +{ + public function getType(): string + { + return 'invalid_parameter'; + } + + public function getStatusCode(): int + { + return 400; + } +} diff --git a/src/Exception/KnownException.php b/src/Exception/KnownException.php new file mode 100644 index 0000000..6994b07 --- /dev/null +++ b/src/Exception/KnownException.php @@ -0,0 +1,52 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Exception; + +use Throwable; + +interface KnownException extends Throwable +{ + /** + * Determine the exception's type. + * + * This should be a short, precise identifier for the error that can be + * exposed to users as an error code. Furthermore, it can be used to find + * appropriate error messages in translations or views to render pretty + * error pages. + * + * Different exception classes are allowed to return the same status code, + * e.g. when they have similar semantic meaning to the end user, but are + * thrown by different subsystems. + * + * @return string + */ + public function getType(): string; + + /** + * Determine the HTTP status code to use with this exception. + * + * @return int The HTTP status code to use. + */ + public function getStatusCode(): int; +} diff --git a/src/Exception/KnownExceptionHandler.php b/src/Exception/KnownExceptionHandler.php new file mode 100644 index 0000000..846fe44 --- /dev/null +++ b/src/Exception/KnownExceptionHandler.php @@ -0,0 +1,45 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Exception; + +use Flarum\Foundation\ErrorHandling\HandledError; + +class KnownExceptionHandler +{ + public function handle(KnownException $e): HandledError + { + $prev = $e->getPrevious(); + $message = $e->getMessage(); + if (!is_null($prev)) { + $message .= ': ' . $prev->getMessage(); + } + return (new HandledError( + $e, + $e->getType(), + $e->getStatusCode() + ))->withDetails([ + ['message' => $message] + ]); + } +} diff --git a/src/Frontend/Admin.php b/src/Frontend/Admin.php new file mode 100644 index 0000000..402b38c --- /dev/null +++ b/src/Frontend/Admin.php @@ -0,0 +1,49 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Frontend; + +use Club1\ServerSideHighlight\Consts; +use Flarum\Frontend\Document; +use Illuminate\Contracts\Filesystem\Cloud; +use Illuminate\Contracts\Filesystem\Factory; + +class Admin +{ + /** @var Cloud */ + protected $assetsDisk; + + public function __construct(Factory $filesystemFactory) + { + $this->assetsDisk = $filesystemFactory->disk('flarum-assets'); + } + + public function getAssetUrl(string $assetPath): string + { + return $this->assetsDisk->url(Consts::ASSETS_PATH . $assetPath); + } + + public function __invoke(Document $document): void { + $document->js[] = $this->getAssetUrl('styles.min.js'); + } +} diff --git a/src/Frontend/Content.php b/src/Frontend/Forum.php similarity index 99% rename from src/Frontend/Content.php rename to src/Frontend/Forum.php index 1c907e3..72d0de5 100644 --- a/src/Frontend/Content.php +++ b/src/Frontend/Forum.php @@ -29,7 +29,7 @@ use Illuminate\Contracts\Filesystem\Cloud; use Illuminate\Contracts\Filesystem\Factory; -class Content +class Forum { /** @var SettingsRepositoryInterface */ protected $settings; diff --git a/tests/integration/DownloadCommandTest.php b/tests/integration/DownloadCommandTest.php index 6b6c964..32c8a82 100644 --- a/tests/integration/DownloadCommandTest.php +++ b/tests/integration/DownloadCommandTest.php @@ -21,6 +21,8 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +use Club1\ServerSideHighlight\Exception\InvalidArgumentException; +use Club1\ServerSideHighlight\Exception\IOException; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Testing\integration\ConsoleTestCase; @@ -95,8 +97,8 @@ public function testExceptions(array $input, string $class, string $regex, int $ public function exceptionsProvider(): array { return [ - "remote not exists" => [['name' => 'artaz'], ErrorException::class, '/404/'], - "dir not writable" => [['name' => 'agate'], ErrorException::class, '/permission denied/i', 0544], + "remote not exists" => [['name' => 'artaz'], InvalidArgumentException::class, '/^Could not download theme artaz\.min\.css$/'], + "dir not writable" => [['name' => 'agate'], IOException::class, '/^Could not write theme to assets$/', 0544], ]; } } diff --git a/tests/integration/DownloadControllerTest.php b/tests/integration/DownloadControllerTest.php new file mode 100644 index 0000000..dca15b8 --- /dev/null +++ b/tests/integration/DownloadControllerTest.php @@ -0,0 +1,72 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +use Flarum\Testing\integration\RetrievesAuthorizedUsers; +use Flarum\Testing\integration\TestCase; +use Mockery as m; +use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; +use Mockery\MockInterface; + +class DownloadControllerTest extends TestCase +{ + use RetrievesAuthorizedUsers; + use MockeryPHPUnitIntegration; + + /** @var \Club1\ServerSideHighlight\Console\DownloadCommand&MockInterface */ + protected $command; + + public function setUp(): void + { + parent::setUp(); + $this->extension('club-1-server-side-highlight'); + $this->command = m::mock('overload:Club1\ServerSideHighlight\Console\DownloadCommand'); + + $this->prepareDatabase([ + 'users' => [ + $this->normalUser(), + ], + ]); + } + + /** + * @dataProvider responseCodeProvider + */ + public function testResponseCode(int $user, int $commandCalls, int $code): void + { + $this->command->shouldReceive('run')->withAnyArgs()->times($commandCalls); + $response = $this->send( + $this->request('GET', '/api/highlight-download', ['authenticatedAs' => $user]) + ->withQueryParams(['name' => 'arta']) + ); + + $this->assertEquals($code, $response->getStatusCode()); + } + + public function responseCodeProvider(): array + { + return [ + 'admin user' => [1, 1, 200], + 'normal user' => [2, 0, 403], + ]; + } +} diff --git a/tests/unit/AdminTest.php b/tests/unit/AdminTest.php new file mode 100644 index 0000000..a983dfa --- /dev/null +++ b/tests/unit/AdminTest.php @@ -0,0 +1,66 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Tests\unit; + +use Club1\ServerSideHighlight\Consts; +use Club1\ServerSideHighlight\Frontend\Admin; +use Club1\ServerSideHighlight\Frontend\Forum; +use Flarum\Frontend\Document; +use Flarum\Testing\unit\TestCase; +use Illuminate\Contracts\Filesystem\Cloud; +use Illuminate\Contracts\Filesystem\Factory; +use Mockery as m; +use Mockery\MockInterface; + +class AdminTest extends TestCase +{ + /** @var Cloud&MockInterface */ + protected $disk; + + /** @var Factory&MockInterface */ + protected $factory; + + /** @var Document&MockInterface */ + protected $doc; + + public function setUp(): void + { + parent::setUp(); + $this->disk = m::mock(Cloud::class); + $this->factory = m::mock(Factory::class); + $this->factory->shouldReceive('disk')->andReturn($this->disk); + $this->doc = m::mock(Document::class); + } + + public function testBasic(): void + { + $jsPath = Consts::ASSETS_PATH . 'styles.min.js'; + $this->disk->shouldReceive('url')->once()->with($jsPath)->andReturn($jsPath); + + $content = new Admin($this->factory); + $content($this->doc); + $this->assertCount(1, $this->doc->js); + $this->assertEquals($jsPath, $this->doc->js[0]); + } +} diff --git a/tests/unit/ContentTest.php b/tests/unit/ForumTest.php similarity index 95% rename from tests/unit/ContentTest.php rename to tests/unit/ForumTest.php index 7b2fbae..24c0624 100644 --- a/tests/unit/ContentTest.php +++ b/tests/unit/ForumTest.php @@ -24,7 +24,7 @@ namespace Club1\ServerSideHighlight\Tests\unit; use Club1\ServerSideHighlight\Consts; -use Club1\ServerSideHighlight\Frontend\Content; +use Club1\ServerSideHighlight\Frontend\Forum; use Flarum\Frontend\Document; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Testing\unit\TestCase; @@ -33,7 +33,7 @@ use Mockery as m; use Mockery\MockInterface; -class ContentTest extends TestCase +class ForumTest extends TestCase { /** @var SettingsRepositoryInterface&MockInterface */ protected $settings; @@ -72,7 +72,7 @@ public function testBasic(bool $dark, string $css): void $this->settings->shouldReceive('get')->withSomeOfArgs("club-1-server-side-highlight.{$theme}_theme_bg_color")->andReturn('#000'); $this->settings->shouldReceive('get')->withSomeOfArgs("club-1-server-side-highlight.{$theme}_theme_text_color")->andReturn('#fff'); - $content = new Content($this->settings, $this->factory); + $content = new Forum($this->settings, $this->factory); $content($this->doc); $this->assertCount(1, $this->doc->js); $this->assertEquals($jsPath, $this->doc->js[0]); diff --git a/tests/unit/KnownExceptionHandlerTest.php b/tests/unit/KnownExceptionHandlerTest.php new file mode 100644 index 0000000..7c74f4d --- /dev/null +++ b/tests/unit/KnownExceptionHandlerTest.php @@ -0,0 +1,54 @@ +. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Club1\ServerSideHighlight\Tests\unit; + +use Club1\ServerSideHighlight\Exception\InvalidArgumentException; +use Club1\ServerSideHighlight\Exception\IOException; +use Club1\ServerSideHighlight\Exception\KnownException; +use Club1\ServerSideHighlight\Exception\KnownExceptionHandler; +use Flarum\Testing\unit\TestCase; + +class KnownExceptionHandlerTest extends TestCase +{ + /** + * @dataProvider basicProvider + */ + public function testBasic(KnownException $e, string $type, int $statusCode, array $details): void + { + $handler = new KnownExceptionHandler(); + $error = $handler->handle($e); + $this->assertEquals($type, $error->getType()); + $this->assertEquals($statusCode, $error->getStatusCode()); + $this->assertEquals($details, $error->getDetails()); + } + + public function basicProvider(): array + { + return [ + 'invalid' => [new InvalidArgumentException('msg'), 'invalid_parameter', 400, [['message' => 'msg']]], + 'IO' => [new IOException('msg'), 'io_error', 409, [['message' => 'msg']]], + 'prev message' => [new IOException('msg', 1, new \Exception('another')), 'io_error', 409, [['message' => 'msg: another']]], + ]; + } +}