diff --git a/.env.dusk b/.env.dusk new file mode 100644 index 000000000..4ba3c8e1b --- /dev/null +++ b/.env.dusk @@ -0,0 +1,38 @@ +APP_ENV=dusk +APP_DEBUG=true +APP_URL=http://127.0.0.1:8000 +APP_KEY=base64:R2w4QUhGcWxobnpjcHhlSkd0MHpMTjVxZTVuZ1BkaUM= + +DB_CONNECTION=sqlite +DB_HOST= +DB_PORT= +DB_DATABASE=storage/dusk.sqlite +DB_USERNAME= +DB_PASSWORD= +REDIS_HOST= +REDIS_PASSWORD= +REDIS_PORT= +DB_USE_CONFIG_FOR_TESTING=false + +CACHE_DRIVER=file + +SESSION_DRIVER=file + +QUEUE_CONNECTION=sync + +MAIL_DRIVER=array +MAIL_HOST= +MAIL_PORT= +MAIL_ENCRYPTION=tls +MAIL_USERNAME= +MAIL_PASSWORD= + +ROUTES_CACHE=false +ASSET_CACHE=false +DATABASE_TEMPLATES=false +LINK_POLICY=detect +ENABLE_CSRF=false + +# Uncomment the following to use custom authentication credentials for tests +#DUSK_ADMIN_USER= +#DUSK_ADMIN_PASS= diff --git a/.github/workflows/code-quality-pr.yaml b/.github/workflows/code-quality-pr.yaml index 65a0f4646..af05451d1 100644 --- a/.github/workflows/code-quality-pr.yaml +++ b/.github/workflows/code-quality-pr.yaml @@ -6,25 +6,18 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks run: | git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch - ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) + phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) diff --git a/.github/workflows/code-quality-push.yaml b/.github/workflows/code-quality-push.yaml index 3de3f6a97..ff209d0b5 100644 --- a/.github/workflows/code-quality-push.yaml +++ b/.github/workflows/code-quality-push.yaml @@ -9,23 +9,16 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks - run: ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) + run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) diff --git a/.github/workflows/frontend-tests.yaml b/.github/workflows/frontend-tests.yaml deleted file mode 100644 index 9d0068a0a..000000000 --- a/.github/workflows/frontend-tests.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Tests - -on: - push: - branches: - - master - - develop - pull_request: - -jobs: - frontendTests: - runs-on: ubuntu-latest - name: JavaScript - steps: - - name: Checkout changes - uses: actions/checkout@v1 - - name: Install Node - uses: actions/setup-node@v1 - with: - node-version: 8 - - name: Install Node dependencies - run: npm install - - name: Run tests - run: npm run test diff --git a/.github/workflows/matchers/phpcs-matcher.json b/.github/workflows/matchers/phpcs-matcher.json new file mode 100644 index 000000000..5c80b26d2 --- /dev/null +++ b/.github/workflows/matchers/phpcs-matcher.json @@ -0,0 +1,23 @@ +{ + "problemMatcher": [ + { + "owner": "phpcs", + "severity": "error", + "pattern": [ + { + "regexp": "^$", + "file": 1 + }, + { + "regexp": "+)$", + "line": 1, + "column": 2, + "severity": 3, + "message": 4, + "code": 5, + "loop": true + } + ] + } + ] + } diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29713edeb..3c5e7b922 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,6 +8,20 @@ on: pull_request: jobs: + frontendTests: + runs-on: ubuntu-latest + name: JavaScript + steps: + - name: Checkout changes + uses: actions/checkout@v1 + - name: Install Node + uses: actions/setup-node@v1 + with: + node-version: 8 + - name: Install Node dependencies + run: npm install + - name: Run tests + run: npm run test phpUnitTests: runs-on: ubuntu-latest strategy: @@ -15,22 +29,79 @@ jobs: matrix: phpVersions: ['7.2', '7.3', '7.4'] fail-fast: false - name: PHP ${{ matrix.phpVersions }} + name: Unit Tests / PHP ${{ matrix.phpVersions }} steps: - name: Checkout changes uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@master + uses: shivammathur/setup-php@v1 with: php-version: ${{ matrix.phpVersions }} - extension-csv: mbstring, intl, gd, xml, sqlite + extensions: mbstring, intl, gd, xml, sqlite + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Set Composer cache + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache Composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - name: Run post-update Composer scripts + run: php artisan package:discover + - name: Reset October modules run: | - php artisan october:util set build - php artisan package:discover + git reset --hard HEAD + composer dumpautoload - name: Run Linting and Tests run: | ./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php . ./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php + duskTests: + runs-on: ubuntu-latest + name: Browser Tests + steps: + - name: Checkout changes + uses: actions/checkout@v1 + - name: Install PHP + uses: shivammathur/setup-php@v1 + with: + php-version: '7.3' + extensions: mbstring, intl, gd, xml, sqlite + - name: Setup problem matcher for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Set Composer cache + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache Composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install Composer dependencies + run: composer install --no-interaction --no-progress --no-suggest --no-scripts + - name: Run post-update Composer scripts + run: php artisan package:discover + - name: Reset October modules + run: | + git reset --hard HEAD + composer dumpautoload + - name: Install Chrome driver + run: php artisan dusk:chrome-driver + - name: Start Chrome driver + run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 & + - name: Run Laravel Server + run: php artisan serve > /dev/null 2>&1 & + - name: Run Dusk Tests + run: php artisan dusk + - name: Upload Dusk Screenshots + uses: actions/upload-artifact@v1 + if: failure() + with: + name: dusk-screenshots + path: ./tests/Browser/screenshots diff --git a/composer.json b/composer.json index 35fa6f05a..22a0fac82 100644 --- a/composer.json +++ b/composer.json @@ -49,16 +49,13 @@ "dms/phpunit-arraysubset-asserts": "^0.1.0", "meyfa/phpunit-assert-gd": "^2.0", "squizlabs/php_codesniffer": "3.*", - "jakub-onderka/php-parallel-lint": "^1.0" + "jakub-onderka/php-parallel-lint": "^1.0", + "laravel/dusk": "^5.8" }, "autoload-dev": { - "classmap": [ - "tests/concerns/InteractsWithAuthentication.php", - "tests/fixtures/backend/models/UserFixture.php", - "tests/TestCase.php", - "tests/UiTestCase.php", - "tests/PluginTestCase.php" - ] + "psr-4": { + "October\\Core\\Tests\\": "tests/" + } }, "scripts": { "post-create-project-cmd": [ @@ -92,6 +89,11 @@ "recurse": true, "replace": false, "merge-dev": false + }, + "laravel": { + "dont-discover": [ + "laravel/dusk" + ] } } } diff --git a/config/dusk/app.php b/config/dusk/app.php new file mode 100644 index 000000000..79438c741 --- /dev/null +++ b/config/dusk/app.php @@ -0,0 +1,30 @@ + env('APP_URL', 'http://127.0.0.1:8000'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => env('APP_KEY', 'Gl8AHFqlhnzcpxeJGt0zLN5qe5ngPdiC'), +]; diff --git a/config/dusk/cms.php b/config/dusk/cms.php new file mode 100644 index 000000000..1b0ad7107 --- /dev/null +++ b/config/dusk/cms.php @@ -0,0 +1,27 @@ + 'demo', + + /* + |-------------------------------------------------------------------------- + | Cross Site Request Forgery (CSRF) Protection + |-------------------------------------------------------------------------- + | + | If the CSRF protection is enabled, all "postback" & AJAX requests are + | checked for a valid security token. + | + */ + + 'enableCsrfProtection' => false, +]; diff --git a/config/dusk/database.php b/config/dusk/database.php new file mode 100644 index 000000000..072d9b35b --- /dev/null +++ b/config/dusk/database.php @@ -0,0 +1,146 @@ + PDO::FETCH_CLASS, + + /* + |-------------------------------------------------------------------------- + | Default Database Connection Name + |-------------------------------------------------------------------------- + | + | Here you may specify which of the database connections below you wish + | to use as your default connection for all database work. Of course + | you may use many connections at once using the Database library. + | + */ + + 'default' => env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => env('DB_DATABASE', 'storage/dusk.sqlite'), + 'prefix' => '', + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'engine' => 'InnoDB', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'varcharmax' => 191, + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 5432), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 5432), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), + 'prefix' => '', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk have not actually be run in the databases. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'cluster' => false, + + 'default' => [ + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', ''), + 'port' => env('REDIS_PORT', 6379), + 'database' => 0, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Use DB configuration for testing + |-------------------------------------------------------------------------- + | + | When running plugin tests OctoberCMS by default uses SQLite in memory. + | You can override this behavior by setting `useConfigForTesting` to true. + | + | After that OctoberCMS will take DB parameters from the config. + | If file `/config/testing/database.php` exists, config will be read from it, + | but remember that when not specified it will use parameters specified in + | `/config/database.php`. + | + */ + + 'useConfigForTesting' => env('DB_USE_CONFIG_FOR_TESTING', false), +]; diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 1695adc54..40ea3d649 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -261,6 +261,15 @@ class ServiceProvider extends ModuleServiceProvider $this->registerConsoleCommand('theme.list', 'System\Console\ThemeList'); $this->registerConsoleCommand('theme.use', 'System\Console\ThemeUse'); $this->registerConsoleCommand('theme.sync', 'System\Console\ThemeSync'); + + if (!App::isProduction() && class_exists('Laravel\Dusk\Dusk')) { + $this->registerConsoleCommand('dusk', 'System\Console\Dusk'); + $this->registerConsoleCommand('dusk.fails', 'System\Console\DuskFails'); + + $this->commands([ + \Laravel\Dusk\Console\ChromeDriverCommand::class, + ]); + } } /* diff --git a/modules/system/console/Dusk.php b/modules/system/console/Dusk.php new file mode 100644 index 000000000..5891037b5 --- /dev/null +++ b/modules/system/console/Dusk.php @@ -0,0 +1,89 @@ +duskFile()))) { + if (!file_exists(base_path('.env'))) { + $this->stubEnvironment(); + } elseif (file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) { + $this->backupEnvironment(); + } + $this->refreshEnvironment(); + } + + $this->writeConfiguration(); + + $this->setupSignalHandler(); + } + + /** + * Restore the original environment. + * + * @return void + */ + protected function teardownDuskEnviroment() + { + $this->removeConfiguration(); + + if ( + file_exists(base_path($this->duskFile())) + && ( + file_exists(base_path('.env.backup')) + || file_exists(base_path('.env.blank')) + ) + ) { + $this->restoreEnvironment(); + } + } + + + /** + * Stub a current environment file. + * + * @return void + */ + protected function stubEnvironment() + { + touch(base_path('.env.blank')); + + copy(base_path($this->duskFile()), base_path('.env')); + } + + /** + * Backup the current environment file. + * + * @return void + */ + protected function backupEnvironment() + { + copy(base_path('.env'), base_path('.env.backup')); + + copy(base_path($this->duskFile()), base_path('.env')); + } + + /** + * Restore the backed-up environment file. + * + * @return void + */ + protected function restoreEnvironment() + { + if (file_exists(base_path('.env.blank'))) { + unlink(base_path('.env')); + unlink(base_path('.env.blank')); + } else { + copy(base_path('.env.backup'), base_path('.env')); + + unlink(base_path('.env.backup')); + } + } +} diff --git a/modules/system/console/DuskFails.php b/modules/system/console/DuskFails.php new file mode 100644 index 000000000..7cbbd3b71 --- /dev/null +++ b/modules/system/console/DuskFails.php @@ -0,0 +1,31 @@ + + + + + ./tests/Browser/Backend + + + + + + ./modules/ + + + ./modules/backend/routes.php + ./modules/cms/routes.php + ./modules/system/routes.php + + ./modules/backend/database + ./modules/cms/database + ./modules/system/database + + + + diff --git a/tests/Browser/Backend/AuthTest.php b/tests/Browser/Backend/AuthTest.php new file mode 100644 index 000000000..6d1b0514a --- /dev/null +++ b/tests/Browser/Backend/AuthTest.php @@ -0,0 +1,41 @@ +browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->click('@accountMenu') + ->clickLink('Sign out'); + + $browser + ->on(new Login); + }); + } + + public function testPasswordReset() + { + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login) + ->pause(500) + ->click('@forgotPasswordLink'); + + $browser + ->on(new ForgotPassword) + ->type('@loginField', 'admin') + ->click('@submitButton'); + + $browser + ->on(new Login) + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Message sent to your email address'); + }); + } +} diff --git a/tests/Browser/Backend/Cms/TemplateTest.php b/tests/Browser/Backend/Cms/TemplateTest.php new file mode 100644 index 000000000..43fcb5039 --- /dev/null +++ b/tests/Browser/Backend/Cms/TemplateTest.php @@ -0,0 +1,300 @@ +browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + // Add a new page + $browser + ->click('form[data-template-type="page"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertEquals('New page', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="settings[title]"]', 'Functional Test Page') + ->pause(100) + + // Check that slug values are working + ->assertInputValue('input[name="settings[url]"]', '/functional-test-page') + ->assertInputValue('input[name="fileName"]', 'functional-test-page') + + ->clear('input[name="settings[url]"]') + ->type('input[name="settings[url]"]', '/xxx/functional/test/page') + ->clear('input[name="fileName"]') + ->type('input[name="fileName"]', 'xxx_functional_test_page.htm') + + // Check that slug values have not been re-added after manual entry + ->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page') + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm'); + + // Save the new page + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'Functional Test Page', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="page-"][data-tab-id$="-xxx_functional_test_page.htm"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the page + $browser + ->click('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="settings[title]"]', 'Functional Test Page') + ->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page') + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm'); + + // Delete the page + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"]'); + }); + } + + public function testPartialTemplates() + { + $this->browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + $browser + ->click('@sideNav > li[data-menu-item="partials"] a'); + + // Add a new partial + $browser + ->click('form[data-template-type="partial"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertEquals('New partial', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="fileName"]', 'xxx_functional_test_partial') + ->type('input[name="settings[description]"]', 'Test Partial'); + + // Save the new partial + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'xxx_functional_test_partial', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="partial-"][data-tab-id$="-xxx_functional_test_partial.htm"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the partial + $browser + ->click('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_partial.htm') + ->assertInputValue('input[name="settings[description]"]', 'Test Partial'); + + // Delete the partial + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"]'); + }); + } + + public function testLayoutTemplates() + { + $this->browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + $browser + ->click('@sideNav > li[data-menu-item="layouts"] a'); + + // Add a new layout + $browser + ->click('form[data-template-type="layout"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertEquals('New layout', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="fileName"]', 'xxx_functional_test_layout') + ->type('input[name="settings[description]"]', 'Test Layout'); + + // Save the new layout + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'xxx_functional_test_layout', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="layout-"][data-tab-id$="-xxx_functional_test_layout.htm"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the partial + $browser + ->click('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_layout.htm') + ->assertInputValue('input[name="settings[description]"]', 'Test Layout'); + + // Delete the partial + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"]'); + }); + } + + public function testContentTemplates() + { + $this->browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + $browser + ->click('@sideNav > li[data-menu-item="content"] a'); + + // Add a new content file + $browser + ->click('form[data-template-type="content"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertStringContainsString('content', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="fileName"]', 'xxx_functional_test_content.txt'); + + // Save the new content file + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'xxx_functional_test_content.txt', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="content-"][data-tab-id$="-xxx_functional_test_content.txt"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the partial + $browser + ->click('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_content.txt'); + + // Delete the partial + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"]'); + }); + } +} diff --git a/tests/Browser/Pages/Backend/Cms.php b/tests/Browser/Pages/Backend/Cms.php new file mode 100644 index 000000000..e088330c1 --- /dev/null +++ b/tests/Browser/Pages/Backend/Cms.php @@ -0,0 +1,32 @@ +assertTitleContains('CMS |') + ->assertPresent('@mainMenu') + ->assertPresent('@sideNav') + ->assertPresent('@accountMenu'); + } +} diff --git a/tests/Browser/Pages/Backend/Dashboard.php b/tests/Browser/Pages/Backend/Dashboard.php new file mode 100644 index 000000000..1fb910d20 --- /dev/null +++ b/tests/Browser/Pages/Backend/Dashboard.php @@ -0,0 +1,33 @@ +assertTitleContains('Dashboard |') + ->assertPresent('@mainMenu') + ->assertPresent('@accountMenu') + ->waitFor('.report-widget') + ->assertSee('Welcome'); + } +} diff --git a/tests/Browser/Pages/Backend/ForgotPassword.php b/tests/Browser/Pages/Backend/ForgotPassword.php new file mode 100644 index 000000000..97f02bb1e --- /dev/null +++ b/tests/Browser/Pages/Backend/ForgotPassword.php @@ -0,0 +1,48 @@ +assertTitle('Administration Area') + ->assertPresent('@loginField') + ->assertMissing('input[name="password"]') + ->assertPresent('@submitButton') + ->assertPresent('@cancelLink') + ->assertSeeIn('@submitButton', 'Restore'); + } + + /** + * Get the global element shortcuts for the site. + * + * @return array + */ + public function elements() + { + return [ + '@loginField' => 'input[name="login"]', + '@submitButton' => 'button[type="submit"]', + '@cancelLink' => 'p.forgot-password > a', + ]; + } +} diff --git a/tests/Browser/Pages/Backend/Login.php b/tests/Browser/Pages/Backend/Login.php new file mode 100644 index 000000000..d62672aa0 --- /dev/null +++ b/tests/Browser/Pages/Backend/Login.php @@ -0,0 +1,49 @@ +assertTitle('Administration Area') + ->assertPresent('@loginField') + ->assertPresent('@passwordField') + ->assertPresent('@submitButton') + ->assertPresent('@forgotPasswordLink') + ->assertSeeIn('@submitButton', 'Login'); + } + + /** + * Get the global element shortcuts for the site. + * + * @return array + */ + public function elements() + { + return [ + '@loginField' => 'input[name="login"]', + '@passwordField' => 'input[name="password"]', + '@submitButton' => 'button[type="submit"]', + '@forgotPasswordLink' => 'p.forgot-password > a', + ]; + } +} diff --git a/tests/Browser/Pages/BackendPage.php b/tests/Browser/Pages/BackendPage.php new file mode 100644 index 000000000..b57d6cee5 --- /dev/null +++ b/tests/Browser/Pages/BackendPage.php @@ -0,0 +1,21 @@ + '#layout-mainmenu', + '@accountMenu' => '#layout-mainmenu .mainmenu-account > a', + + '@sideNav' => '#layout-sidenav > ul', + '@sidePanel' => '#layout-side-panel', + '@sidePanelFixButton' => '#layout-side-panel a.fix-button', + ]; + } +} diff --git a/tests/Browser/Pages/Page.php b/tests/Browser/Pages/Page.php new file mode 100644 index 000000000..30bfd0632 --- /dev/null +++ b/tests/Browser/Pages/Page.php @@ -0,0 +1,16 @@ +addArguments([ + '--disable-gpu', + '--headless', + '--window-size=1920,1080', + ]); + + return RemoteWebDriver::create( + 'http://localhost:9515', + DesiredCapabilities::chrome()->setCapability( + ChromeOptions::CAPABILITY, + $options + ) + ); + } + + public function setUp(): void + { + $this->resetManagers(); + + parent::setUp(); + + // Ensure system is up to date + if ($this->usingTestDatabase) { + $this->runOctoberUpCommand(); + } + + // Detect a plugin and autoload it, if necessary + $this->detectPlugin(); + + // Disable mailer + \Mail::pretend(); + + Browser::$baseUrl = $this->baseUrl(); + Browser::$storeScreenshotsAt = base_path('tests/Browser/screenshots'); + Browser::$storeConsoleLogAt = base_path('tests/Browser/console'); + Browser::$userResolver = function () { + return $this->user(); + }; + + $this->setupMacros(); + } + + public function tearDown(): void + { + if ($this->usingTestDatabase && isset($this->testDatabasePath)) { + unlink($this->testDatabasePath); + } + + parent::tearDown(); + } + + /** + * Defines October macros for use in browser tests + * + * @return void + */ + protected function setupMacros() + { + /** + * Signs the user into the backend + */ + Browser::macro('signInToBackend', function (string $username = null, string $password = null) { + $username = $username ?? env('DUSK_ADMIN_USER', 'admin'); + $password = $password ?? env('DUSK_ADMIN_PASS', 'admin1234'); + + $this + ->visit(new Login) + ->pause(500) + ->type('@loginField', $username) + ->type('@passwordField', $password) + ->click('@submitButton'); + + $this-> + on(new Dashboard); + + return $this; + }); + + + Browser::macro('hasClass', function (string $selector, string $class) { + $classes = preg_split('/\s+/', $this->attribute($selector, 'class'), -1, PREG_SPLIT_NO_EMPTY); + + if (empty($classes)) { + return false; + } + + return in_array($class, $classes); + }); + } + + /** + * Similar to the native getConfirmation() function + */ + protected function getSweetConfirmation($expectedText = null, $clickOk = true) + { + $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); + + if ($expectedText) { + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); + } + + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); + + if ($clickOk) { + $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); + } + } +} diff --git a/tests/Concerns/CreatesApplication.php b/tests/Concerns/CreatesApplication.php new file mode 100644 index 000000000..e9a35fc75 --- /dev/null +++ b/tests/Concerns/CreatesApplication.php @@ -0,0 +1,98 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); + $app['cache']->setDefaultDriver('array'); + $app->setLocale('en'); + + $app->singleton('auth', function ($app) { + $app['auth.loaded'] = true; + + return AuthManager::instance(); + }); + + // Use test database configuration, unless overriden + $dbConnection = Config::get('database.default', 'sqlite'); + $dbConnections = [ + $dbConnection => Config::get('database.connections.' . $dbConnection, [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]) + ]; + + if (env('APP_ENV') === 'testing' && !Config::get('database.useConfigForTesting', false)) { + $this->usingTestDatabase = true; + + $dbConnection = 'sqlite'; + $dbConnections = [ + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ], + ]; + } elseif (env('APP_ENV') === 'dusk' && !Config::get('database.useConfigForTesting', false)) { + $this->usingTestDatabase = true; + + $dbConnection = 'sqlite'; + $dbConnections = [ + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => 'storage/dusk.sqlite', + 'prefix' => '', + ], + ]; + + // Ensure a fresh copy of the SQLite database is made + $this->testDatabasePath = base_path('storage/dusk.sqlite'); + + if (file_exists($this->testDatabasePath)) { + unlink($this->testDatabasePath); + } + + touch($this->testDatabasePath); + } + + $app['config']->set('database.default', $dbConnection); + $app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]); + + /** + * Prevent mail from being sent out + */ + $app['config']->set('mail.driver', 'array'); + + /** + * Modify the plugin path away from the test context + */ + $app->setPluginsPath(realpath(base_path() . Config::get('cms.pluginsPath'))); + + return $app; + } +} diff --git a/tests/concerns/InteractsWithAuthentication.php b/tests/Concerns/InteractsWithAuthentication.php similarity index 98% rename from tests/concerns/InteractsWithAuthentication.php rename to tests/Concerns/InteractsWithAuthentication.php index a7950f230..bde15cdef 100644 --- a/tests/concerns/InteractsWithAuthentication.php +++ b/tests/Concerns/InteractsWithAuthentication.php @@ -1,6 +1,4 @@ -pluginTestCaseLoadedPlugins = []; + $pluginCode = $this->guessPluginCodeFromTest(); + + if ($pluginCode !== false) { + $this->runPluginRefreshCommand($pluginCode, false); + } + } + + /** + * Locates the plugin code based on the test file location. + * + * @return string|bool + */ + protected function guessPluginCodeFromTest() + { + $reflect = new \ReflectionClass($this); + $path = $reflect->getFilename(); + $basePath = $this->app->pluginsPath(); + + $result = false; + + if (strpos($path, $basePath) === 0) { + $result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/'); + $result = implode('.', array_slice(explode('/', $result), 0, 2)); + } + + return $result; + } + + /** + * Runs a refresh command on a plugin. + * + * Since the test environment has loaded all the test plugins + * natively, this method will ensure the desired plugin is + * loaded in the system before proceeding to migrate it. + * + * @return void + */ + protected function runPluginRefreshCommand($code, $throwException = true): void + { + if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) { + if (!$throwException) { + return; + } + throw new \Exception(sprintf('Invalid plugin code: "%s"', $code)); + } + + $manager = PluginManager::instance(); + $plugin = $manager->findByIdentifier($code); + + // First time seeing this plugin, load it up + if (!$plugin) { + $namespace = '\\'.str_replace('.', '\\', strtolower($code)); + $path = array_get($manager->getPluginNamespaces(), $namespace); + + if (!$path) { + if (!$throwException) { + return; + } + throw new \Exception(sprintf('Unable to find plugin with code: "%s"', $code)); + } + + $plugin = $manager->loadPlugin($namespace, $path); + } + + // Spin over dependencies and refresh them too + $this->pluginTestCaseLoadedPlugins[$code] = $plugin; + + if (!empty($plugin->require)) { + foreach ((array) $plugin->require as $dependency) { + if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) { + continue; + } + + $this->runPluginRefreshCommand($dependency); + } + } + + // Execute the command + \Artisan::call('plugin:refresh', ['name' => $code]); + } +} diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 728d5d430..82f09618a 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -1,67 +1,17 @@ -make('Illuminate\Contracts\Console\Kernel')->bootstrap(); - - $app['cache']->setDefaultDriver('array'); - $app->setLocale('en'); - - $app->singleton('auth', function ($app) { - $app['auth.loaded'] = true; - - return AuthManager::instance(); - }); - - /* - * Store database in memory by default, if not specified otherwise - */ - $dbConnection = 'sqlite'; - - $dbConnections = []; - $dbConnections['sqlite'] = [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '' - ]; - - if (env('APP_ENV') === 'testing' && Config::get('database.useConfigForTesting', false)) { - $dbConnection = Config::get('database.default', 'sqlite'); - - $dbConnections[$dbConnection] = Config::get('database.connections' . $dbConnection, $dbConnections['sqlite']); - } - - $app['config']->set('database.default', $dbConnection); - $app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]); - - /* - * Modify the plugin path away from the test context - */ - $app->setPluginsPath(realpath(base_path().Config::get('cms.pluginsPath'))); - - return $app; - } + use RunsMigrations; + use TestsPlugins; /** * Perform test case set up. @@ -69,36 +19,21 @@ abstract class PluginTestCase extends TestCase */ public function setUp() : void { - /* - * Force reload of October singletons - */ - PluginManager::forgetInstance(); - UpdateManager::forgetInstance(); + $this->resetManagers(); - /* - * Create application instance - */ + // Create application parent::setUp(); - /* - * Ensure system is up to date - */ - $this->runOctoberUpCommand(); - - /* - * Detect plugin from test and autoload it - */ - $this->pluginTestCaseLoadedPlugins = []; - $pluginCode = $this->guessPluginCodeFromTest(); - - if ($pluginCode !== false) { - $this->runPluginRefreshCommand($pluginCode, false); + // Ensure system is up to date + if ($this->usingTestDatabase) { + $this->runOctoberUpCommand(); } - /* - * Disable mailer - */ - Mail::pretend(); + // Detect a plugin and autoload it, if necessary + $this->detectPlugin(); + + // Disable mailer + \Mail::pretend(); } /** @@ -108,88 +43,8 @@ abstract class PluginTestCase extends TestCase public function tearDown() : void { $this->flushModelEventListeners(); + parent::tearDown(); - unset($this->app); - } - - /** - * Migrate database using october:up command. - * @return void - */ - protected function runOctoberUpCommand() - { - Artisan::call('october:up'); - } - - /** - * Since the test environment has loaded all the test plugins - * natively, this method will ensure the desired plugin is - * loaded in the system before proceeding to migrate it. - * @return void - */ - protected function runPluginRefreshCommand($code, $throwException = true) - { - if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) { - if (!$throwException) { - return; - } - throw new Exception(sprintf('Invalid plugin code: "%s"', $code)); - } - - $manager = PluginManager::instance(); - $plugin = $manager->findByIdentifier($code); - - /* - * First time seeing this plugin, load it up - */ - if (!$plugin) { - $namespace = '\\'.str_replace('.', '\\', strtolower($code)); - $path = array_get($manager->getPluginNamespaces(), $namespace); - - if (!$path) { - if (!$throwException) { - return; - } - throw new Exception(sprintf('Unable to find plugin with code: "%s"', $code)); - } - - $plugin = $manager->loadPlugin($namespace, $path); - } - - /* - * Spin over dependencies and refresh them too - */ - $this->pluginTestCaseLoadedPlugins[$code] = $plugin; - - if (!empty($plugin->require)) { - foreach ((array) $plugin->require as $dependency) { - if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) { - continue; - } - - $this->runPluginRefreshCommand($dependency); - } - } - - /* - * Execute the command - */ - Artisan::call('plugin:refresh', ['name' => $code]); - } - - /** - * Returns a plugin object from its code, useful for registering events, etc. - * @return PluginBase - */ - protected function getPluginObject($code = null) - { - if ($code === null) { - $code = $this->guessPluginCodeFromTest(); - } - - if (isset($this->pluginTestCaseLoadedPlugins[$code])) { - return $this->pluginTestCaseLoadedPlugins[$code]; - } } /** @@ -205,7 +60,7 @@ abstract class PluginTestCase extends TestCase continue; } - $reflectClass = new ReflectionClass($class); + $reflectClass = new \ReflectionClass($class); if ( !$reflectClass->isInstantiable() || !$reflectClass->isSubclassOf('October\Rain\Database\Model') || @@ -219,24 +74,4 @@ abstract class PluginTestCase extends TestCase ActiveRecord::flushEventListeners(); } - - /** - * Locates the plugin code based on the test file location. - * @return string|bool - */ - protected function guessPluginCodeFromTest() - { - $reflect = new ReflectionClass($this); - $path = $reflect->getFilename(); - $basePath = $this->app->pluginsPath(); - - $result = false; - - if (strpos($path, $basePath) === 0) { - $result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/'); - $result = implode('.', array_slice(explode('/', $result), 0, 2)); - } - - return $result; - } } diff --git a/tests/README.md b/tests/README.md index a9e7b78b5..9960ba0c6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,26 +1,143 @@ -# Plugin testing +# Testing -Plugin unit tests can be performed by running `phpunit` in the base plugin directory. +October CMS has a suite of tools available for running automated tests on your October instance and plugins. To run tests, you must ensure that you have PHPUnit installed and can run the `phpunit` command from a command-line interface. -### Creating plugin tests +--- -Plugins can be tested by creating a file called `phpunit.xml` in the base directory with the following content, for example, in a file **/plugins/acme/blog/phpunit.xml**: +- [System Tests](#system-tests) + - [Unit Tests](#unit-tests) + - [Using a Custom Database Engine](#custom-database-engine) + - [Browser Tests](#browser-tests) + - [Testing Environment for Browser Tests](#testing-environment) + - [JavaScript Tests](#javascript-tests) +- [Creating Tests for Plugins](#creating-plugin-tests) + - [Unit Tests](#plugin-unit-tests) + - [Browser Tests](#plugin-browser-tests) +--- + + +## System Tests + +The system tests cover the tests that analyse the core functionality of October CMS. To run these tests, we recommend that you use Git to checkout a copy of the development version of October CMS and use Composer to install all necessary dependencies. + +You can do this on command-line by simply running the following: + +```bash +git checkout git@github.com:octobercms/october.git +cd october +composer install +``` + + +### Unit Tests + +The unit tests in October CMS can be found in the **tests/unit** folder and are executed through PHPUnit. You can run the tests by running the following command in the root folder of the October CMS installation: + +```bash +./vendor/bin/phpunit +``` + +This will run tests for both October CMS and the Rain library. The Rain library tests can be found in the **vendor/october/rain/tests** folder. + +Note that unit tests run in a special environment called `testing`. You may configure this environment by adding or modifying the configuration files in the **config/testing/** directory. + + +#### Using a Custom Database Engine + +By default, OctoberCMS uses SQLite stored in memory for the `testing` environment. If you wish to override this with your own database configuration, set the `useConfigForTesting` config to `true` in your `/config/database.php` file. + +When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`. + +You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken. + + +### Browser Tests + +Browsers tests are a more flexible type of automated test that run directly in a web browser. October CMS leverages the [Laravel Dusk](https://laravel.com/docs/6.x/dusk) framework to run these tests, which in turn uses Google Chrome and a ChromeDriver install to run the tests. + +Running the browser tests will require Google Chrome to be installed on your machine. Once this is done, you may prepare your installation for running Browser tests by running the following in your project root folder: + +```bash +php artisan dusk:chrome-driver +``` + +> **Note:** It is possible to use other browsers, or a standalone Selenium server, if you wish. Please see the [Laravel Dusk documentation](https://laravel.com/docs/6.x/dusk#using-other-browsers) for more information. + +Once installed, you may run the browsers test by simply running the following command in the root folder of the October CMS installation: + +```bash +php artisan dusk +``` + +If you have previously run the browser tests and want to re-run only the tests that failed, you may use this shortcut to run just the failed tests: + +```bash +php artisan dusk:fails +``` + +Note that your October CMS installation must be web-accessible in order to run the browser tests. Please review the next section on setting up the testing environment. + + +#### Testing Environment for Browser Tests + +The Browser tests in October CMS are, by default, set up to run within a special testing environment called `dusk`. This is configured to run October CMS via the inbuilt PHP web server, using an SQLite database to store the database temporarily. + +You may start this web server before running the browser tests simply by running the following: + +```bash +php artisan serve +``` + +This environment is configured in two places: the **.env.dusk** file available in the project root, and within the **config/dusk/** folder. You may modify either of these configuration files in order to configure the testing environment to your requirements. + +When the browser tests are started, the **.env** file in your project (if any) is subtituted with **.env.dusk** for the duration of the tests. Once the tests end, the **.env** file is restored to its original content. + +> **Note:** The system browser tests will need to be authenticated with a superuser-level user to run correctly. If you are using the default environment, this will happen automatically.

If you are custom settings however, you may need to provide authentication information to Dusk in order for it to run. You can specify the environment variables `DUSK_ADMIN_USER` and `DUSK_ADMIN_PASS` to use specific authentication credentials during testing. These are available in the `.env.dusk` file. + + +### JavaScript Tests + +In addition to the PHP-based tests above, we also have a suite of unit tests for our JavaScript libraries and functions. These run on an NodeJS-based environment, so you will need to [download and install](https://nodejs.org/en/download/) NodeJS and NPM in order to install the tools required for these tests. + +Once installed, you may install the tools by running the following in the browser root: + +```bash +npm install +``` + +Then, you may run the following command to run the JavaScript tests: + +```bash +npm run test +``` + + +## Creating Tests for Plugins + +October CMS has made it easy for plugin developers to create unit and browser tests for their plugins. + +Please read the sections below in order to configure your plugin for your required types of testing. + + +### Unit Tests + +To allow unit testing in your plugin, you must first create a file called `phpunit.xml` in the plugin base directory with the following content - for example, in a file **/plugins/acme/blog/phpunit.xml**: - - ./tests + ./tests/unit @@ -30,29 +147,53 @@ Plugins can be tested by creating a file called `phpunit.xml` in the base direct -Then a **tests/** directory can be created to contain the test classes. The file structure should mimic the base directory with classes having a `Test` suffix. Using a namespace for the class is also recommended. +Then you may create a **tests/unit/** directory to contain the unit test classes. - 'Hi!']); - $this->assertEquals(1, $post->id); - } + $post = Post::create(['title' => 'Hi!']); + $this->assertEquals(1, $post->id); } +} +``` -The test class should extend the base class `PluginTestCase` and this is a special class that will set up the October database stored in memory, as part of the `setUp` method. It will also refresh the plugin being tested, along with any of the defined dependencies in the plugin registration file. This is the equivalent of running the following before each test: +The `October\Core\Tests\PluginTestCase` class takes care of ensuring that you have a clean database for each test, in order to run each test in isolation. If you need to run some code before, or after each test, you may overwrite the `setUp` and `tearDown` methods in your class. It is important, however, that you allow the parent methods to run too. - php artisan october:up - php artisan plugin:refresh Acme.Blog - [php artisan plugin:refresh , ...] +```php +public function setUp(): void +{ + parent::setUp(); -> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests. Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation. + // Load necessary model fixture + $this->postFixture = PostFixture::create(['title' => 'Test Post']); +} + +public function tearDown(): void +{ + // Remove model fixtur + unset($this->postFixture); + + parent::tearDown(); +} +``` + +> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests.

Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation. use System\Classes\PluginManager; @@ -84,51 +225,51 @@ The test class should extend the base class `PluginTestCase` and this is a speci } } -#### Changing database engine for plugins tests +To run the unit tests for your plugin, simply go to the base folder for your plugin, and run the following command: -By default OctoberCMS uses SQLite stored in memory for the plugin testing environment. If you want to override the default behavior set the `useConfigForTesting` config to `true` in your `/config/database.php` file. When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`. +```bash +../../../vendor/bin/phpunit +``` -You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken. +This will execute PHPUnit in the context of your plugin. -## System testing + +### Browsers Tests -To perform unit testing on the core October files, you should download a development copy using composer or cloning the git repo. This will ensure you have the `tests/` directory. +Browser tests for plugins can be set up in much the same way as unit tests, with a small number of differences. -### Unit tests +Browsers tests require their own PHPUnit XML configuration file. You should create a `phpunit.dusk.xml` file in your project base directory with the following content: -Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. + + + + + ./tests/browser + + + + + + + + -### Functional tests +Browser tests should be separate from unit tests - you can instead store browser tests within the **tests/browser/** directory. -Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: +Browser test files follow the same rules as unit test files, however, instead of extending the `October\Core\Tests\PluginTestCase` class, they should instead extend the `October\Core\Tests\BrowserTestCase` class. -- Active theme is `demo` -- Language preference is `en` +To run the plugin browser tests, you must run the following command within the root folder of your *October CMS install*, not the plugin: -#### Selenium set up - -1. Download latest Java SE from http://java.sun.com/ and install -1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/). -1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance. -1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`. - -#### Selenium configuration - -Create a new file `selenium.php` in the root directory, add the following content: - - getMethod($name); $method->setAccessible(true); return $method->invokeArgs($object, $params); @@ -34,7 +34,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase public static function getProtectedProperty($object, $name) { $className = get_class($object); - $class = new ReflectionClass($className); + $class = new \ReflectionClass($className); $property = $class->getProperty($name); $property->setAccessible(true); return $property->getValue($object); @@ -43,7 +43,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase public static function setProtectedProperty($object, $name, $value) { $className = get_class($object); - $class = new ReflectionClass($className); + $class = new \ReflectionClass($className); $property = $class->getProperty($name); $property->setAccessible(true); return $property->setValue($object, $value); diff --git a/tests/UiTestCase.php b/tests/UiTestCase.php deleted file mode 100644 index e95c3e9bf..000000000 --- a/tests/UiTestCase.php +++ /dev/null @@ -1,111 +0,0 @@ -markTestSkipped('Selenium skipped'); - } - - if (defined('TEST_SELENIUM_HOST')) { - $this->setHost(TEST_SELENIUM_HOST); - } - if (defined('TEST_SELENIUM_PORT')) { - $this->setPort(TEST_SELENIUM_PORT); - } - if (defined('TEST_SELENIUM_BROWSER')) { - $this->setBrowser(TEST_SELENIUM_BROWSER); - } - $this->setBrowserUrl(TEST_SELENIUM_URL); - } - - // - // OctoberCMS Helpers - // - - protected function signInToBackend() - { - $this->open('backend'); - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - } - - /** - * Similar to the native getConfirmation() function - */ - protected function getSweetConfirmation($expectedText = null, $clickOk = true) - { - $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); - - if ($expectedText) { - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); - } - - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); - - if ($clickOk) { - $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); - } - } - - // - // Selenium helpers - // - - protected function waitForElementPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if ($this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } - - protected function waitForElementNotPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if (!$this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3de4e0dc9..6c9464ba3 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -11,5 +11,5 @@ $loader = new October\Rain\Support\ClassLoader( $loader->register(); $loader->addDirectories([ 'modules', - 'plugins' + 'plugins', ]); diff --git a/tests/fixtures/backend/models/UserFixture.php b/tests/fixtures/backend/models/UserFixture.php index a444cd597..acad32ce1 100644 --- a/tests/fixtures/backend/models/UserFixture.php +++ b/tests/fixtures/backend/models/UserFixture.php @@ -1,6 +1,6 @@ open('backend'); - - $cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a'; - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("name=login")); - $this->assertTrue($this->isElementPresent("name=password")); - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Login"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - /* - * Sign in - */ - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Dashboard | October CMS'); - $this->assertTrue($this->isElementPresent('css='.$cssLogoutLink)); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->verifyText('css='.$cssLogoutLink, "Sign out"); - - /* - * Log out - */ - $this->click('css='.$cssLogoutLink); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } - - public function testPasswordReset() - { - $this->open('backend'); - - try { - $this->assertTrue($this->isElementPresent("link=exact:Forgot your password?")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->click('link=exact:Forgot your password?'); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Restore"); - $this->assertTrue($this->isElementPresent("link=Cancel")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->type("name=login", TEST_SELENIUM_USER); - sleep(1); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("css=p.flash-message.success")); - $this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } -} diff --git a/tests/functional/cms/TemplateTest.php b/tests/functional/cms/TemplateTest.php deleted file mode 100644 index 5ff2a0a9e..000000000 --- a/tests/functional/cms/TemplateTest.php +++ /dev/null @@ -1,143 +0,0 @@ -signInToBackend(); - $this->open('cms'); - $this->waitForPageToLoad("30000"); - - // Fix the sidebar - $this->click("xpath=(//a[@class='fix-button'])[1]"); - - /* - * Page - */ - - // Create a new page - $this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=settings[title]"); - - // Populate page details - $this->type('name=settings[title]', 'Functional Test Page'); - $this->type('name=settings[url]', '/xxx/functional/test/page'); - $this->type('name=fileName', 'xxx_functional_test_page'); - - // Save the new page - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->waitForElementPresent("name=settings[title]"); - sleep(1); - - // Delete the page - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this page?'); - // $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation())); - $this->waitForElementNotPresent("name=settings[title]"); - - /* - * Partial - */ - - // Click partials menu item - $this->click("xpath=(//li[@data-menu-item='partials']/a)[1]"); - - // Create a new partial - $this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate partial details - $this->type('name=fileName', 'xxx_functional_test_partial'); - $this->type('name=settings[description]', 'Test partial'); - - // Save the new partial - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the partial - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this partial?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Layout - */ - - // Click layouts menu item - $this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]"); - - // Create a new layout - $this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate layout details - $this->type('name=fileName', 'xxx_functional_test_layout'); - $this->type('name=settings[description]', 'Test layout'); - - // Save the new layout - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the layout - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this layout?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Content - */ - - // Click contents menu item - $this->click("xpath=(//li[@data-menu-item='content']/a)[1]"); - - // Create a new content - $this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate content details - $this->type('name=fileName', 'xxx_functional_test_content.txt'); - - // Save the new content - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the content - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this content file?'); - $this->waitForElementNotPresent("name=fileName"); - } -} diff --git a/tests/functional/phpunit.xml b/tests/functional/phpunit.xml deleted file mode 100644 index cb4586424..000000000 --- a/tests/functional/phpunit.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - ./ - - - diff --git a/tests/unit/backend/classes/AuthManagerTest.php b/tests/unit/backend/classes/AuthManagerTest.php index 65dd5e735..d06ed53a6 100644 --- a/tests/unit/backend/classes/AuthManagerTest.php +++ b/tests/unit/backend/classes/AuthManagerTest.php @@ -2,7 +2,7 @@ use Backend\Classes\AuthManager; use October\Rain\Exception\SystemException; -class AuthManagerTest extends TestCase +class AuthManagerTest extends \October\Core\Tests\TestCase { public function setUp(): void { diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index f63645198..9c4a99a7c 100644 --- a/tests/unit/backend/classes/NavigationManagerTest.php +++ b/tests/unit/backend/classes/NavigationManagerTest.php @@ -3,7 +3,7 @@ use Backend\Classes\Controller; use Backend\Classes\NavigationManager; -class NavigationManagerTest extends TestCase +class NavigationManagerTest extends \October\Core\Tests\TestCase { public function testRegisterMenuItems() { diff --git a/tests/unit/backend/classes/WidgetManagerTest.php b/tests/unit/backend/classes/WidgetManagerTest.php index 819997385..c46a75731 100644 --- a/tests/unit/backend/classes/WidgetManagerTest.php +++ b/tests/unit/backend/classes/WidgetManagerTest.php @@ -3,7 +3,7 @@ use Backend\Classes\Controller; use Backend\Classes\WidgetManager; -class WidgetManagerTest extends TestCase +class WidgetManagerTest extends \October\Core\Tests\TestCase { public function testListFormWidgets() { diff --git a/tests/unit/backend/helpers/BackendHelperTest.php b/tests/unit/backend/helpers/BackendHelperTest.php index 3766f5c8e..e33127eea 100644 --- a/tests/unit/backend/helpers/BackendHelperTest.php +++ b/tests/unit/backend/helpers/BackendHelperTest.php @@ -3,7 +3,7 @@ use Backend\Helpers\Backend; use Backend\Helpers\Exception\DecompileException; -class BackendHelperTest extends TestCase +class BackendHelperTest extends \October\Core\Tests\TestCase { public function testDecompileAssets() { diff --git a/tests/unit/backend/models/ExportModelTest.php b/tests/unit/backend/models/ExportModelTest.php index 4fd55b6f0..75fc8dba6 100644 --- a/tests/unit/backend/models/ExportModelTest.php +++ b/tests/unit/backend/models/ExportModelTest.php @@ -25,7 +25,7 @@ class ExampleExportModel extends ExportModel } } -class ExportModelTest extends TestCase +class ExportModelTest extends \October\Core\Tests\TestCase { // diff --git a/tests/unit/backend/models/ImportModelTest.php b/tests/unit/backend/models/ImportModelTest.php index 1f803bd06..c390c40f2 100644 --- a/tests/unit/backend/models/ImportModelTest.php +++ b/tests/unit/backend/models/ImportModelTest.php @@ -16,7 +16,7 @@ class ExampleImportModel extends ImportModel } } -class ImportModelTest extends TestCase +class ImportModelTest extends \October\Core\Tests\TestCase { // diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 9a290b509..0aa7ff8da 100644 --- a/tests/unit/backend/traits/WidgetMakerTest.php +++ b/tests/unit/backend/traits/WidgetMakerTest.php @@ -12,7 +12,7 @@ class ExampleTraitClass } } -class WidgetMakerTest extends TestCase +class WidgetMakerTest extends \October\Core\Tests\TestCase { /** * The object under test. diff --git a/tests/unit/backend/widgets/FilterTest.php b/tests/unit/backend/widgets/FilterTest.php index 05d0f0d78..66f04e62e 100644 --- a/tests/unit/backend/widgets/FilterTest.php +++ b/tests/unit/backend/widgets/FilterTest.php @@ -2,10 +2,17 @@ use Backend\Widgets\Filter; use Backend\Models\User; -use October\Tests\Fixtures\Backend\Models\UserFixture; +use October\Core\Tests\Fixtures\Backend\Models\UserFixture; -class FilterTest extends PluginTestCase +class FilterTest extends \October\Core\Tests\PluginTestCase { + public function setUp() : void + { + parent::setUp(); + + include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; + } + public function testRestrictedScopeWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/backend/widgets/FormTest.php b/tests/unit/backend/widgets/FormTest.php index d373a1d86..81f49917a 100644 --- a/tests/unit/backend/widgets/FormTest.php +++ b/tests/unit/backend/widgets/FormTest.php @@ -2,15 +2,23 @@ use Backend\Widgets\Form; use Illuminate\Database\Eloquent\Model; -use October\Tests\Fixtures\Backend\Models\UserFixture; +use October\Core\Tests\Fixtures\Backend\Models\UserFixture; class FormTestModel extends Model { } -class FormTest extends PluginTestCase +class FormTest extends \October\Core\Tests\PluginTestCase { + public function setUp() : void + { + parent::setUp(); + + include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; + } + + public function testRestrictedFieldWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/backend/widgets/ListsTest.php b/tests/unit/backend/widgets/ListsTest.php index e74aa877d..e8ad8cf03 100644 --- a/tests/unit/backend/widgets/ListsTest.php +++ b/tests/unit/backend/widgets/ListsTest.php @@ -3,10 +3,17 @@ use Backend\Models\User; use Backend\Widgets\Lists; use October\Rain\Exception\ApplicationException; -use October\Tests\Fixtures\Backend\Models\UserFixture; +use October\Core\Tests\Fixtures\Backend\Models\UserFixture; -class ListsTest extends PluginTestCase +class ListsTest extends \October\Core\Tests\PluginTestCase { + public function setUp() : void + { + parent::setUp(); + + include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; + } + public function testRestrictedColumnWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index ec82153f9..66bc7d849 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -28,7 +28,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject } } -class CmsCompoundObjectTest extends TestCase +class CmsCompoundObjectTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/CmsExceptionTest.php b/tests/unit/cms/classes/CmsExceptionTest.php index 6e270fb7a..1a100e1fe 100644 --- a/tests/unit/cms/classes/CmsExceptionTest.php +++ b/tests/unit/cms/classes/CmsExceptionTest.php @@ -9,7 +9,7 @@ use Cms\Classes\CmsException; use Cms\Classes\CodeParser; use October\Rain\Exception\SystemException; -class CmsExceptionTest extends TestCase +class CmsExceptionTest extends \October\Core\Tests\TestCase { // // Tests diff --git a/tests/unit/cms/classes/CmsObjectQueryTest.php b/tests/unit/cms/classes/CmsObjectQueryTest.php index 4e19b55da..fa7cd2110 100644 --- a/tests/unit/cms/classes/CmsObjectQueryTest.php +++ b/tests/unit/cms/classes/CmsObjectQueryTest.php @@ -5,7 +5,7 @@ use Cms\Classes\Theme; use Cms\Classes\Layout; use October\Rain\Halcyon\Model; -class CmsObjectQueryTest extends TestCase +class CmsObjectQueryTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 2c65761b4..9a008ac71 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -13,7 +13,7 @@ class TestTemporaryCmsObject extends CmsObject protected $dirName = 'temporary'; } -class CmsObjectTest extends TestCase +class CmsObjectTest extends \October\Core\Tests\TestCase { public function testLoad() { diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 10d1b7d23..13e8024ee 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -8,7 +8,7 @@ use Cms\Classes\Layout; use Cms\Classes\CodeParser; use Cms\Classes\Controller; -class CodeParserTest extends TestCase +class CodeParserTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/ComponentManagerTest.php b/tests/unit/cms/classes/ComponentManagerTest.php index 152367b8d..6a067e5f3 100644 --- a/tests/unit/cms/classes/ComponentManagerTest.php +++ b/tests/unit/cms/classes/ComponentManagerTest.php @@ -7,7 +7,7 @@ use Cms\Classes\Controller; use Cms\Classes\CodeParser; use Cms\Classes\ComponentManager; -class ComponentManagerTest extends TestCase +class ComponentManagerTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/ContentTest.php b/tests/unit/cms/classes/ContentTest.php index 790501c6f..0178d41ae 100644 --- a/tests/unit/cms/classes/ContentTest.php +++ b/tests/unit/cms/classes/ContentTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Theme; use Cms\Classes\Content; -class ContentTest extends TestCase +class ContentTest extends \October\Core\Tests\TestCase { public function testMarkdownContent() diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index da16e317d..d7f1f40a4 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -4,7 +4,7 @@ use Cms\Classes\Theme; use Cms\Classes\Controller; use October\Rain\Halcyon\Model; -class ControllerTest extends TestCase +class ControllerTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/PartialStackTest.php b/tests/unit/cms/classes/PartialStackTest.php index f9371a3b6..14404ffde 100644 --- a/tests/unit/cms/classes/PartialStackTest.php +++ b/tests/unit/cms/classes/PartialStackTest.php @@ -2,7 +2,7 @@ use Cms\Classes\PartialStack; -class PartialStackTest extends TestCase +class PartialStackTest extends \October\Core\Tests\TestCase { public function testStackPartials() diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index f4eeda27a..c5887e929 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Router; use Cms\Classes\Theme; -class RouterTest extends TestCase +class RouterTest extends \October\Core\Tests\TestCase { protected static $theme = null; diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index 07a15f2db..69d461862 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -2,7 +2,7 @@ use Cms\Classes\Theme; -class ThemeTest extends TestCase +class ThemeTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/helpers/FileTest.php b/tests/unit/cms/helpers/FileTest.php index dde19f0c3..23c253d6b 100644 --- a/tests/unit/cms/helpers/FileTest.php +++ b/tests/unit/cms/helpers/FileTest.php @@ -2,7 +2,7 @@ use Cms\Helpers\File as FileHelper; -class FileTest extends TestCase +class FileTest extends \October\Core\Tests\TestCase { public function testValidateName() { diff --git a/tests/unit/plugins/backend/ImportModelDbTest.php b/tests/unit/plugins/backend/ImportModelDbTest.php index 61a1d8ea9..7ca7c6cba 100644 --- a/tests/unit/plugins/backend/ImportModelDbTest.php +++ b/tests/unit/plugins/backend/ImportModelDbTest.php @@ -13,7 +13,7 @@ class ExampleDbImportModel extends ImportModel } } -class ImportModelDbTest extends PluginTestCase +class ImportModelDbTest extends \October\Core\Tests\PluginTestCase { public function testGetImportFilePath() { diff --git a/tests/unit/plugins/database/AttachManyModelTest.php b/tests/unit/plugins/database/AttachManyModelTest.php index 9c316c032..5aed54acd 100644 --- a/tests/unit/plugins/database/AttachManyModelTest.php +++ b/tests/unit/plugins/database/AttachManyModelTest.php @@ -3,7 +3,7 @@ use System\Models\File as FileModel; use Database\Tester\Models\User; -class AttachManyModelTest extends PluginTestCase +class AttachManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index d50debdb6..da7af5f33 100644 --- a/tests/unit/plugins/database/AttachOneModelTest.php +++ b/tests/unit/plugins/database/AttachOneModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\User; use Database\Tester\Models\SoftDeleteUser; use Symfony\Component\HttpFoundation\File\UploadedFile; -class AttachOneModelTest extends PluginTestCase +class AttachOneModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index e850b7752..8d7a3314d 100644 --- a/tests/unit/plugins/database/BelongsToManyModelTest.php +++ b/tests/unit/plugins/database/BelongsToManyModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Role; use Database\Tester\Models\Author; -class BelongsToManyModelTest extends PluginTestCase +class BelongsToManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 8fca6ff06..12ee089f4 100644 --- a/tests/unit/plugins/database/BelongsToModelTest.php +++ b/tests/unit/plugins/database/BelongsToModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; -class BelongsToModelTest extends PluginTestCase +class BelongsToModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index e36460255..b84898a30 100644 --- a/tests/unit/plugins/database/DeferredBindingTest.php +++ b/tests/unit/plugins/database/DeferredBindingTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; use October\Rain\Database\Models\DeferredBinding; -class DeferredBindingTest extends PluginTestCase +class DeferredBindingTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 9a1def095..93f81914d 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Post; use October\Rain\Database\Collection; -class HasManyModelTest extends PluginTestCase +class HasManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasManyThroughModelTest.php b/tests/unit/plugins/database/HasManyThroughModelTest.php index 85ac947f2..8fde8f1fa 100644 --- a/tests/unit/plugins/database/HasManyThroughModelTest.php +++ b/tests/unit/plugins/database/HasManyThroughModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Country; use Database\Tester\Models\Post; use October\Rain\Database\Collection; -class HasManyThroughModelTest extends PluginTestCase +class HasManyThroughModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasOneModelTest.php b/tests/unit/plugins/database/HasOneModelTest.php index e6bd0f45e..5e0920630 100644 --- a/tests/unit/plugins/database/HasOneModelTest.php +++ b/tests/unit/plugins/database/HasOneModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Phone; -class HasOneModelTest extends PluginTestCase +class HasOneModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php index 91be83b60..220dc5ec6 100644 --- a/tests/unit/plugins/database/HasOneThroughModelTest.php +++ b/tests/unit/plugins/database/HasOneThroughModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Phone; use Database\Tester\Models\User; -class HasOneThroughModelTest extends PluginTestCase +class HasOneThroughModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index 16bf07c1d..ed2a4286f 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\Post; -class ModelTest extends PluginTestCase +class ModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphManyModelTest.php b/tests/unit/plugins/database/MorphManyModelTest.php index a7ba3041c..8ffb67ecb 100644 --- a/tests/unit/plugins/database/MorphManyModelTest.php +++ b/tests/unit/plugins/database/MorphManyModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\EventLog; use October\Rain\Database\Collection; -class MorphManyModelTest extends PluginTestCase +class MorphManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index e8c6f60fd..130ad1a90 100644 --- a/tests/unit/plugins/database/MorphOneModelTest.php +++ b/tests/unit/plugins/database/MorphOneModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Post; use Database\Tester\Models\Meta; -class MorphOneModelTest extends PluginTestCase +class MorphOneModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index b0eb37ff7..cedf23017 100644 --- a/tests/unit/plugins/database/MorphToModelTest.php +++ b/tests/unit/plugins/database/MorphToModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; use Database\Tester\Models\EventLog; -class MorphToModelTest extends PluginTestCase +class MorphToModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index 49d46764c..2a05621a3 100644 --- a/tests/unit/plugins/database/NestedTreeModelTest.php +++ b/tests/unit/plugins/database/NestedTreeModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\CategoryNested; -class NestedTreeModelTest extends PluginTestCase +class NestedTreeModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 9d6d61114..568a54c0d 100644 --- a/tests/unit/plugins/database/NullableModelTest.php +++ b/tests/unit/plugins/database/NullableModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\NullablePost; -class NullableModelTest extends PluginTestCase +class NullableModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index c8fa19a54..3d9af946f 100644 --- a/tests/unit/plugins/database/RevisionableModelTest.php +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\RevisionablePost; -class RevisionableModelTest extends PluginTestCase +class RevisionableModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index c22b11d16..c1b0e3ad7 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\CategorySimple; -class SimpleTreeModelTest extends PluginTestCase +class SimpleTreeModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index 22cad1837..5f8a0de0e 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\SluggablePost; -class SluggableModelTest extends PluginTestCase +class SluggableModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index fc5212433..399f21dda 100644 --- a/tests/unit/plugins/database/SoftDeleteModelTest.php +++ b/tests/unit/plugins/database/SoftDeleteModelTest.php @@ -8,7 +8,7 @@ use Database\Tester\Models\UserWithSoftAuthor; use Database\Tester\Models\UserWithAuthorAndSoftDelete; use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete; -class SoftDeleteModelTest extends PluginTestCase +class SoftDeleteModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index f5ddb55b7..94aa60926 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\ValidationPost; -class ValidationModelTest extends PluginTestCase +class ValidationModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index 14dc6f54b..f75dd70fb 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -14,7 +14,7 @@ class CmsThemeTemplateFixture extends Model public $table = 'cms_theme_templates'; } -class AutoDatasourceTest extends PluginTestCase +class AutoDatasourceTest extends \October\Core\Tests\PluginTestCase { /** * Array of model fixtures. diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index 5c5d313bd..77173cf3a 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Theme; use System\Classes\CombineAssets; -class CombineAssetsTest extends TestCase +class CombineAssetsTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/system/classes/CoreLangTest.php b/tests/unit/system/classes/CoreLangTest.php index 54899a812..5a5c5fdf3 100644 --- a/tests/unit/system/classes/CoreLangTest.php +++ b/tests/unit/system/classes/CoreLangTest.php @@ -2,7 +2,7 @@ use System\Classes\PluginManager; -class CoreLangTest extends TestCase +class CoreLangTest extends \October\Core\Tests\TestCase { public function testValidationTranslator() { diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index f4ee00a32..4c8eaa3e2 100644 --- a/tests/unit/system/classes/MarkupManagerTest.php +++ b/tests/unit/system/classes/MarkupManagerTest.php @@ -2,7 +2,7 @@ use System\Classes\MarkupManager; -class MarkupManagerTest extends TestCase +class MarkupManagerTest extends \October\Core\Tests\TestCase { public function setUp() : void diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 5d8082ba5..a326bbdf0 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -2,7 +2,7 @@ use System\Classes\MediaLibrary; -class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine +class MediaLibraryTest extends \October\Core\Tests\TestCase // @codingStandardsIgnoreLine { public function invalidPathsProvider() { diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index e41d860f5..c5c5102f8 100644 --- a/tests/unit/system/classes/PluginManagerTest.php +++ b/tests/unit/system/classes/PluginManagerTest.php @@ -1,7 +1,7 @@