diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index 82d738b16b35..b53626595059 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -14,9 +14,11 @@ namespace App\Console\Commands; use App\DataMapper\CompanySettings; use App\DataMapper\FeesAndLimits; use App\Events\Invoice\InvoiceWasCreated; +use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; use App\Factory\GroupSettingFactory; use App\Factory\InvoiceFactory; use App\Factory\InvoiceItemFactory; +use App\Factory\RecurringInvoiceFactory; use App\Factory\SubscriptionFactory; use App\Helpers\Invoice\InvoiceSum; use App\Jobs\Company\CreateCompanyTaskStatuses; @@ -48,6 +50,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; +use stdClass; class CreateSingleAccount extends Command { @@ -117,7 +120,7 @@ class CreateSingleAccount extends Command $company->settings = $settings; $company->save(); - + $account->default_company_id = $company->id; $account->save(); @@ -165,7 +168,7 @@ class CreateSingleAccount extends Command TaxRate::factory()->create([ 'user_id' => $user->id, - 'company_id' => $company->id, + 'company_id' => $company->id, 'name' => 'VAT', 'rate' => 17.5 ]); @@ -176,7 +179,7 @@ class CreateSingleAccount extends Command 'name' => 'CA Sales Tax', 'rate' => 5 ]); - + $this->info('Creating '.$this->count.' clients'); @@ -225,16 +228,19 @@ class CreateSingleAccount extends Command $client = $company->clients->random(); - $this->info('creating task for client #'.$client->id); + $this->info('creating task for client #' . $client->id); $this->createTask($client); $client = $company->clients->random(); - $this->info('creating project for client #'.$client->id); + $this->info('creating project for client #' . $client->id); $this->createProject($client); - $this->info('creating credit for client #'.$client->id); + $this->info('creating credit for client #' . $client->id); $this->createCredit($client); + + $this->info('creating recurring invoice for client # ' . $client->id); + $this->createRecurringInvoice($client); } $this->createGateways($company, $user); @@ -249,34 +255,34 @@ class CreateSingleAccount extends Command $gs->save(); $p1 = Product::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - 'product_key' => 'pro_plan', - 'notes' => 'The Pro Plan', - 'cost' => 10, - 'price' => 10, - 'quantity' => 1, - ]); + 'user_id' => $user->id, + 'company_id' => $company->id, + 'product_key' => 'pro_plan', + 'notes' => 'The Pro Plan', + 'cost' => 10, + 'price' => 10, + 'quantity' => 1, + ]); $p2 = Product::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - 'product_key' => 'enterprise_plan', - 'notes' => 'The Enterprise Plan', - 'cost' => 14, - 'price' => 14, - 'quantity' => 1, - ]); + 'user_id' => $user->id, + 'company_id' => $company->id, + 'product_key' => 'enterprise_plan', + 'notes' => 'The Enterprise Plan', + 'cost' => 14, + 'price' => 14, + 'quantity' => 1, + ]); $p3 = Product::factory()->create([ - 'user_id' => $user->id, - 'company_id' => $company->id, - 'product_key' => 'free_plan', - 'notes' => 'The Free Plan', - 'cost' => 0, - 'price' => 0, - 'quantity' => 1, - ]); + 'user_id' => $user->id, + 'company_id' => $company->id, + 'product_key' => 'free_plan', + 'notes' => 'The Free Plan', + 'cost' => 0, + 'price' => 0, + 'quantity' => 1, + ]); $webhook_config = [ 'post_purchase_url' => 'http://ninja.test:8000/api/admin/plan', @@ -435,6 +441,10 @@ class CreateSingleAccount extends Command $invoice = $invoice_calc->getInvoice(); + if ($this->gateway === 'braintree') { + $invoice->amount = 100; // Braintree sandbox only allows payments under 2,000 to complete successfully. + } + $invoice->save(); $invoice->service()->createInvitations()->markSent(); @@ -619,7 +629,7 @@ class CreateSingleAccount extends Command $gateway_types = $cg->driver(new Client)->gatewayTypes(); - $fees_and_limits = new \stdClass; + $fees_and_limits = new stdClass; $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; $cg->fees_and_limits = $fees_and_limits; @@ -642,7 +652,7 @@ class CreateSingleAccount extends Command $gateway_types = $cg->driver(new Client)->gatewayTypes(); - $fees_and_limits = new \stdClass; + $fees_and_limits = new stdClass; $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; $cg->fees_and_limits = $fees_and_limits; @@ -663,7 +673,7 @@ class CreateSingleAccount extends Command $gateway_types = $cg->driver(new Client)->gatewayTypes(); - $fees_and_limits = new \stdClass; + $fees_and_limits = new stdClass; $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; $cg->fees_and_limits = $fees_and_limits; @@ -684,11 +694,96 @@ class CreateSingleAccount extends Command $gateway_types = $cg->driver(new Client)->gatewayTypes(); - $fees_and_limits = new \stdClass; + $fees_and_limits = new stdClass; + $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + } + + if (config('ninja.testvars.wepay') && ($this->gateway == 'all' || $this->gateway == 'wepay')) { + $cg = new CompanyGateway; + $cg->company_id = $company->id; + $cg->user_id = $user->id; + $cg->gateway_key = '8fdeed552015b3c7b44ed6c8ebd9e992'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt(config('ninja.testvars.wepay')); + $cg->save(); + + $gateway_types = $cg->driver(new Client)->gatewayTypes(); + + $fees_and_limits = new stdClass; + $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + } + + if (config('ninja.testvars.braintree') && ($this->gateway == 'all' || $this->gateway == 'braintree')) { + $cg = new CompanyGateway; + $cg->company_id = $company->id; + $cg->user_id = $user->id; + $cg->gateway_key = 'f7ec488676d310683fb51802d076d713'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt(config('ninja.testvars.braintree')); + $cg->save(); + + $gateway_types = $cg->driver(new Client)->gatewayTypes(); + + $fees_and_limits = new stdClass; $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits; $cg->fees_and_limits = $fees_and_limits; $cg->save(); } } + + private function createRecurringInvoice($client) + { + $faker = Factory::create(); + + $invoice = RecurringInvoiceFactory::create($client->company->id, $client->user->id); //stub the company and user_id + $invoice->client_id = $client->id; + $dateable = Carbon::now()->subDays(rand(0, 90)); + $invoice->date = $dateable; + + $invoice->line_items = $this->buildLineItems(rand(1, 10)); + $invoice->uses_inclusive_taxes = false; + + if (rand(0, 1)) { + $invoice->tax_name1 = 'GST'; + $invoice->tax_rate1 = 10.00; + } + + if (rand(0, 1)) { + $invoice->tax_name2 = 'VAT'; + $invoice->tax_rate2 = 17.50; + } + + if (rand(0, 1)) { + $invoice->tax_name3 = 'CA Sales Tax'; + $invoice->tax_rate3 = 5; + } + + $invoice->custom_value1 = $faker->date; + $invoice->custom_value2 = rand(0, 1) ? 'yes' : 'no'; + + $invoice->status_id = RecurringInvoice::STATUS_ACTIVE; + $invoice->save(); + + $invoice_calc = new InvoiceSum($invoice); + $invoice_calc->build(); + + $invoice = $invoice_calc->getInvoice(); + + $invoice->save(); + + event(new RecurringInvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars())); + } } diff --git a/app/Http/Livewire/Profile/Settings/General.php b/app/Http/Livewire/Profile/Settings/General.php index e8a809acaf95..4b333431a671 100644 --- a/app/Http/Livewire/Profile/Settings/General.php +++ b/app/Http/Livewire/Profile/Settings/General.php @@ -32,6 +32,7 @@ class General extends Component 'first_name' => ['sometimes'], 'last_name' => ['sometimes'], 'email' => ['required', 'email'], + 'phone' => ['sometimes'], ]; public function mount() diff --git a/composer.json b/composer.json index 056dee862f67..151dd8239ab6 100644 --- a/composer.json +++ b/composer.json @@ -87,6 +87,7 @@ "fakerphp/faker": "^1.14", "filp/whoops": "^2.7", "friendsofphp/php-cs-fixer": "^2.16", + "laravel/dusk": "^6.15", "mockery/mockery": "^1.3.1", "nunomaduro/collision": "^5.0", "phpunit/phpunit": "^9.0", diff --git a/composer.lock b/composer.lock index 99a3b5134fb9..15b58365b9a7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "25a0cbf18fc238305c7ea640c49ba89a", + "content-hash": "d2beb37ff5fbee59ad4bb792e944eb10", "packages": [ { "name": "asm/php-ansible", @@ -11950,6 +11950,79 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "laravel/dusk", + "version": "v6.15.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/dusk.git", + "reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/dusk/zipball/45b55fa20321086c4f8cc4e712cbe54db644e21c", + "reference": "45b55fa20321086c4f8cc4e712cbe54db644e21c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-zip": "*", + "illuminate/console": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "nesbot/carbon": "^2.0", + "php": "^7.2|^8.0", + "php-webdriver/webdriver": "^1.9.0", + "symfony/console": "^4.3|^5.0", + "symfony/finder": "^4.3|^5.0", + "symfony/process": "^4.3|^5.0", + "vlucas/phpdotenv": "^3.0|^4.0|^5.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.16|^5.17.1|^6.12.1", + "phpunit/phpunit": "^7.5.15|^8.4|^9.0" + }, + "suggest": { + "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Dusk\\DuskServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Dusk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", + "keywords": [ + "laravel", + "testing", + "webdriver" + ], + "support": { + "issues": "https://github.com/laravel/dusk/issues", + "source": "https://github.com/laravel/dusk/tree/v6.15.0" + }, + "time": "2021-04-06T14:14:57+00:00" + }, { "name": "maximebf/debugbar", "version": "v1.16.5", @@ -12503,6 +12576,72 @@ }, "time": "2020-10-14T08:39:05+00:00" }, + { + "name": "php-webdriver/webdriver", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/da16e39968f8dd5cfb7d07eef91dc2b731c69880", + "reference": "da16e39968f8dd5cfb7d07eef91dc2b731c69880", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" + }, + "replace": { + "facebook/webdriver": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^1.1 || ^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + }, + "files": [ + "lib/Exception/TimeoutException.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "support": { + "issues": "https://github.com/php-webdriver/php-webdriver/issues", + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.11.1" + }, + "time": "2021-05-21T15:12:49+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -14742,5 +14881,5 @@ "platform-dev": { "php": "^7.3|^7.4|^8.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/config/ninja.php b/config/ninja.php index 8d2e235be7e0..5c20700d01e4 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -82,6 +82,8 @@ return [ 'checkout' => env('CHECKOUT_KEYS', ''), 'travis' => env('TRAVIS', false), 'test_email' => env('TEST_EMAIL', 'test@example.com'), + 'wepay' => env('WEPAY_KEYS', ''), + 'braintree' => env('BRAINTREE_KEYS', ''), ], 'contact' => [ 'email' => env('MAIL_FROM_ADDRESS'), diff --git a/cypress.json b/cypress.json deleted file mode 100644 index fa8e22edf8d1..000000000000 --- a/cypress.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "video": false, - "baseUrl": "http://localhost:8080/", - "chromeWebSecurity": false, - "env": { - "runningEnvironment": "docker" - }, - "viewportWidth": 1280, - "viewportHeight": 800 -} diff --git a/cypress/excluded/checkout_credit_card.spec.js b/cypress/excluded/checkout_credit_card.spec.js deleted file mode 100644 index d0a6ab0325c4..000000000000 --- a/cypress/excluded/checkout_credit_card.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { second } from '../fixtures/example.json'; - -describe('Checkout Credit Card Payments', () => { - beforeEach(() => { - // cy.useGateway(second); - cy.clientLogin(); - }); - - it('should be able to complete payment using checkout credit card', () => { - cy.visit('/client/invoices'); - - cy.get('#unpaid-checkbox').click(); - - cy.get('[data-cy=pay-now') - .first() - .click(); - - cy.location('pathname').should('eq', '/client/invoices/payment'); - - cy.get('[data-cy=payment-methods-dropdown').click(); - - cy.get('[data-cy=payment-method') - .first() - .click(); - - cy.wait(8000); - - cy.get('.cko-pay-now.show') - .first() - .click(); - - cy.wait(3000); - - cy.getWithinIframe('[data-checkout="card-number"]').type( - '4242424242424242' - ); - cy.getWithinIframe('[data-checkout="expiry-month"]').type('12'); - cy.getWithinIframe('[data-checkout="expiry-year"]').type('30'); - cy.getWithinIframe('[data-checkout="cvv"]').type('100'); - - cy.getWithinIframe('.form-submit') - .first() - .click(); - - cy.wait(5000); - cy.url().should('contain', '/client/payments'); - }); -}); diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json deleted file mode 100644 index f0300d2d471c..000000000000 --- a/cypress/fixtures/example.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes", - - "first": "VolejRejNm", - "second": "Wpmbk5ezJn", - - "url": "http://localhost:8000" -} diff --git a/cypress/integration/client_portal/credits.spec.js b/cypress/integration/client_portal/credits.spec.js deleted file mode 100644 index ca51088147dd..000000000000 --- a/cypress/integration/client_portal/credits.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -describe('Credits', () => { - beforeEach(() => { - cy.clientLogin(); - }); - - it('should show credits page', () => { - cy.visit('/client/credits'); - cy.location().should(location => { - expect(location.pathname).to.eq('/client/credits'); - }); - }); - - it('should show credits text', () => { - cy.visit('/client/credits'); - - cy.get('body') - .find('[data-ref=meta-title]') - .first() - .should('contain.text', 'Credits'); - }); - - /* it('should have required table elements', () => { - cy.visit('/client/credits'); - - cy.get('body') - .find('table.credits-table > tbody > tr') - .first() - .find('a') - .first() - .should('contain.text', 'View') - .click() - .location() - .should(location => { - expect(location.pathname).to.eq('/client/credits/VolejRejNm'); - }); - });*/ -}); diff --git a/cypress/integration/client_portal/invoices.spec.js b/cypress/integration/client_portal/invoices.spec.js deleted file mode 100644 index 8f1c9d28339d..000000000000 --- a/cypress/integration/client_portal/invoices.spec.js +++ /dev/null @@ -1,73 +0,0 @@ -context('Invoices', () => { - beforeEach(() => { - cy.clientLogin(); - }); - - it('should show invoices page', () => { - cy.visit('/client/invoices'); - cy.location().should(location => { - expect(location.pathname).to.eq('/client/invoices'); - }); - }); - - it('should show invoices text', () => { - cy.visit('/client/invoices'); - - cy.get('body') - .find('[data-ref=meta-title]') - .first() - .should('contain.text', 'Invoices'); - }); - - it('should show download and pay now buttons', () => { - cy.visit('/client/invoices'); - - cy.get('body') - .find('button[value="download"]') - .first() - .should('contain.text', 'Download'); - - cy.get('body') - .find('button[value="payment"]') - .first() - .should('contain.text', 'Pay Now'); - }); - - it('should have per page options dropdown', () => { - cy.visit('/client/invoices'); - - cy.get('body') - .find('select') - .first() - .should('have.value', '10'); - }); - - it('should have required table elements', () => { - cy.visit('/client/invoices'); - - cy.get('body') - .find('table.invoices-table > tbody > tr') - .first() - .find('.button-link') - .first() - .should('contain.text', 'View') - .click() - .location() - .should(location => { - expect(location.pathname).to.eq('/client/invoices/VolejRejNm'); - }); - }); - - it('should filter table content', () => { - cy.visit('/client/invoices'); - - cy.get('body') - .find('#paid-checkbox') - .check(); - - cy.get('body') - .find('table.invoices-table > tbody > tr') - .first() - .should('not.contain', 'Overdue'); - }); -}); diff --git a/cypress/integration/client_portal/login.spec.js b/cypress/integration/client_portal/login.spec.js deleted file mode 100644 index 3bb0c4e55ec1..000000000000 --- a/cypress/integration/client_portal/login.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -context('Login', () => { - beforeEach(() => { - cy.visit('/client/login'); - }); - - it('should type into login form elements', () => { - cy.get('#test_email') - .invoke('val') - .then(emailValue => { - cy.get('#email') - .type(emailValue) - .should('have.value', emailValue); - }); - - cy.get('#test_password') - .invoke('val') - .then(passwordValue => { - cy.get('#password') - .type(passwordValue) - .should('have.value', passwordValue); - }); - }); - - it('should login into client portal', () => { - cy.get('#test_email') - .invoke('val') - .then(emailValue => { - cy.get('#test_password') - .invoke('val') - .then(passwordValue => { - cy.get('#email') - .type(emailValue) - .should('have.value', emailValue); - cy.get('#password') - .type(passwordValue) - .should('have.value', passwordValue); - cy.get('#loginBtn') - .contains('Login') - .click(); - cy.location().should(location => { - expect(location.pathname).to.eq( - '/client/invoices' - ); - }); - }); - }); - }); -}); diff --git a/cypress/integration/client_portal/payment_methods.spec.js b/cypress/integration/client_portal/payment_methods.spec.js deleted file mode 100644 index 69a682d4ac92..000000000000 --- a/cypress/integration/client_portal/payment_methods.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -context('Payment methods', () => { - beforeEach(() => { - cy.clientLogin(); - }); - - it('should show payment methods page', () => { - cy.visit('/client/payment_methods'); - cy.location().should(location => { - expect(location.pathname).to.eq('/client/payment_methods'); - }); - }); - - it('should show payment methods text', () => { - cy.visit('/client/payment_methods'); - - cy.get('body') - .find('[data-ref=meta-title]') - .first() - .should('contain.text', 'Payment Method'); - }); - - it('should have per page options dropdown', () => { - cy.visit('/client/payment_methods'); - - cy.get('body') - .find('select') - .first() - .should('have.value', '10'); - }); -}); diff --git a/cypress/integration/client_portal/payments.spec.js b/cypress/integration/client_portal/payments.spec.js deleted file mode 100644 index aa735b970999..000000000000 --- a/cypress/integration/client_portal/payments.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -context('Payments', () => { - beforeEach(() => { - cy.clientLogin(); - }); - - it('should show payments page', () => { - cy.visit('/client/payments'); - cy.location().should(location => { - expect(location.pathname).to.eq('/client/payments'); - }); - }); - - it('should show payments text', () => { - cy.visit('/client/payments'); - - cy.get('body') - .find('[data-ref=meta-title]') - .first() - .should('contain.text', 'Payments'); - }); - - it('should have per page options dropdown', () => { - cy.visit('/client/payments'); - - cy.get('body') - .find('select') - .first() - .should('have.value', '10'); - }); -}); diff --git a/cypress/integration/client_portal/quotes.spec.js b/cypress/integration/client_portal/quotes.spec.js deleted file mode 100644 index 538b2fd558eb..000000000000 --- a/cypress/integration/client_portal/quotes.spec.js +++ /dev/null @@ -1,73 +0,0 @@ -describe('Quotes', () => { - beforeEach(() => { - cy.clientLogin(); - }); - - it('should show quotes page', () => { - cy.visit('/client/quotes'); - cy.location().should(location => { - expect(location.pathname).to.eq('/client/quotes'); - }); - }); - - it('should show quotes text', () => { - cy.visit('/client/quotes'); - - cy.get('body') - .find('[data-ref=meta-title]') - .first() - .should('contain.text', 'Quotes'); - }); - - it('should show download and approve buttons', () => { - cy.visit('/client/quotes'); - - cy.get('body') - .find('button[value="download"]') - .first() - .should('contain.text', 'Download'); - - cy.get('body') - .find('button[value="approve"]') - .first() - .should('contain.text', 'Approve'); - }); - - it('should have per page options dropdown', () => { - cy.visit('/client/quotes'); - - cy.get('body') - .find('select') - .first() - .should('have.value', '10'); - }); - - it('should have required table elements', () => { - cy.visit('/client/quotes'); - - cy.get('body') - .find('table.quotes-table > tbody > tr') - .first() - .find('.button-link') - .first() - .should('contain.text', 'View') - .click() - .location() - .should(location => { - expect(location.pathname).to.eq('/client/quotes/VolejRejNm'); - }); - }); - - it('should filter table content', () => { - cy.visit('/client/quotes'); - - cy.get('body') - .find('#draft-checkbox') - .check(); - - cy.get('body') - .find('table.quotes-table > tbody > tr') - .first() - .should('not.contain', 'Sent'); - }); -}); diff --git a/cypress/integration/client_portal/recurring_invoices.spec.js b/cypress/integration/client_portal/recurring_invoices.spec.js deleted file mode 100644 index 7fae096d784a..000000000000 --- a/cypress/integration/client_portal/recurring_invoices.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -context('Recurring invoices', () => { - beforeEach(() => { - cy.clientLogin(); - }); - - it('should show recurring invoices page', () => { - cy.visit('/client/recurring_invoices'); - - cy.location().should(location => { - expect(location.pathname).to.eq('/client/recurring_invoices'); - }); - }); - - it('should show reucrring invoices text', () => { - cy.visit('/client/recurring_invoices'); - - cy.get('body') - .find('[data-ref=meta-title]') - .first() - .should('contain.text', 'Recurring Invoices'); - }); - - it('should have per page options dropdown', () => { - cy.visit('/client/recurring_invoices'); - - cy.get('body') - .find('select') - .first() - .should('have.value', '10'); - }); -}); diff --git a/cypress/integration/examples/actions.spec.js b/cypress/integration/examples/actions.spec.js deleted file mode 100644 index f26ba6343585..000000000000 --- a/cypress/integration/examples/actions.spec.js +++ /dev/null @@ -1,298 +0,0 @@ -/// - -context('Actions', () => { - beforeEach(() => { - cy.visit('https://example.cypress.io/commands/actions') - }) - - // https://on.cypress.io/interacting-with-elements - - it('.type() - type into a DOM element', () => { - // https://on.cypress.io/type - cy.get('.action-email') - .type('fake@email.com').should('have.value', 'fake@email.com') - - // .type() with special character sequences - .type('{leftarrow}{rightarrow}{uparrow}{downarrow}') - .type('{del}{selectall}{backspace}') - - // .type() with key modifiers - .type('{alt}{option}') //these are equivalent - .type('{ctrl}{control}') //these are equivalent - .type('{meta}{command}{cmd}') //these are equivalent - .type('{shift}') - - // Delay each keypress by 0.1 sec - .type('slow.typing@email.com', { delay: 100 }) - .should('have.value', 'slow.typing@email.com') - - cy.get('.action-disabled') - // Ignore error checking prior to type - // like whether the input is visible or disabled - .type('disabled error checking', { force: true }) - .should('have.value', 'disabled error checking') - }) - - it('.focus() - focus on a DOM element', () => { - // https://on.cypress.io/focus - cy.get('.action-focus').focus() - .should('have.class', 'focus') - .prev().should('have.attr', 'style', 'color: orange;') - }) - - it('.blur() - blur off a DOM element', () => { - // https://on.cypress.io/blur - cy.get('.action-blur').type('About to blur').blur() - .should('have.class', 'error') - .prev().should('have.attr', 'style', 'color: red;') - }) - - it('.clear() - clears an input or textarea element', () => { - // https://on.cypress.io/clear - cy.get('.action-clear').type('Clear this text') - .should('have.value', 'Clear this text') - .clear() - .should('have.value', '') - }) - - it('.submit() - submit a form', () => { - // https://on.cypress.io/submit - cy.get('.action-form') - .find('[type="text"]').type('HALFOFF') - cy.get('.action-form').submit() - .next().should('contain', 'Your form has been submitted!') - }) - - it('.click() - click on a DOM element', () => { - // https://on.cypress.io/click - cy.get('.action-btn').click() - - // You can click on 9 specific positions of an element: - // ----------------------------------- - // | topLeft top topRight | - // | | - // | | - // | | - // | left center right | - // | | - // | | - // | | - // | bottomLeft bottom bottomRight | - // ----------------------------------- - - // clicking in the center of the element is the default - cy.get('#action-canvas').click() - - cy.get('#action-canvas').click('topLeft') - cy.get('#action-canvas').click('top') - cy.get('#action-canvas').click('topRight') - cy.get('#action-canvas').click('left') - cy.get('#action-canvas').click('right') - cy.get('#action-canvas').click('bottomLeft') - cy.get('#action-canvas').click('bottom') - cy.get('#action-canvas').click('bottomRight') - - // .click() accepts an x and y coordinate - // that controls where the click occurs :) - - cy.get('#action-canvas') - .click(80, 75) // click 80px on x coord and 75px on y coord - .click(170, 75) - .click(80, 165) - .click(100, 185) - .click(125, 190) - .click(150, 185) - .click(170, 165) - - // click multiple elements by passing multiple: true - cy.get('.action-labels>.label').click({ multiple: true }) - - // Ignore error checking prior to clicking - cy.get('.action-opacity>.btn').click({ force: true }) - }) - - it('.dblclick() - double click on a DOM element', () => { - // https://on.cypress.io/dblclick - - // Our app has a listener on 'dblclick' event in our 'scripts.js' - // that hides the div and shows an input on double click - cy.get('.action-div').dblclick().should('not.be.visible') - cy.get('.action-input-hidden').should('be.visible') - }) - - it('.rightclick() - right click on a DOM element', () => { - // https://on.cypress.io/rightclick - - // Our app has a listener on 'contextmenu' event in our 'scripts.js' - // that hides the div and shows an input on right click - cy.get('.rightclick-action-div').rightclick().should('not.be.visible') - cy.get('.rightclick-action-input-hidden').should('be.visible') - }) - - it('.check() - check a checkbox or radio element', () => { - // https://on.cypress.io/check - - // By default, .check() will check all - // matching checkbox or radio elements in succession, one after another - cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]') - .check().should('be.checked') - - cy.get('.action-radios [type="radio"]').not('[disabled]') - .check().should('be.checked') - - // .check() accepts a value argument - cy.get('.action-radios [type="radio"]') - .check('radio1').should('be.checked') - - // .check() accepts an array of values - cy.get('.action-multiple-checkboxes [type="checkbox"]') - .check(['checkbox1', 'checkbox2']).should('be.checked') - - // Ignore error checking prior to checking - cy.get('.action-checkboxes [disabled]') - .check({ force: true }).should('be.checked') - - cy.get('.action-radios [type="radio"]') - .check('radio3', { force: true }).should('be.checked') - }) - - it('.uncheck() - uncheck a checkbox element', () => { - // https://on.cypress.io/uncheck - - // By default, .uncheck() will uncheck all matching - // checkbox elements in succession, one after another - cy.get('.action-check [type="checkbox"]') - .not('[disabled]') - .uncheck().should('not.be.checked') - - // .uncheck() accepts a value argument - cy.get('.action-check [type="checkbox"]') - .check('checkbox1') - .uncheck('checkbox1').should('not.be.checked') - - // .uncheck() accepts an array of values - cy.get('.action-check [type="checkbox"]') - .check(['checkbox1', 'checkbox3']) - .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked') - - // Ignore error checking prior to unchecking - cy.get('.action-check [disabled]') - .uncheck({ force: true }).should('not.be.checked') - }) - - it('.select() - select an option in a - diff --git a/resources/views/portal/ninja2020/components/livewire/pay-now-dropdown.blade.php b/resources/views/portal/ninja2020/components/livewire/pay-now-dropdown.blade.php index d0c53470b6fb..259f7f11e75e 100644 --- a/resources/views/portal/ninja2020/components/livewire/pay-now-dropdown.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/pay-now-dropdown.blade.php @@ -1,10 +1,10 @@
@unless(count($methods) == 0)
+ class="relative inline-block text-left" dusk="payment-methods-dropdown">
- diff --git a/resources/views/portal/ninja2020/profile/settings/general.blade.php b/resources/views/portal/ninja2020/profile/settings/general.blade.php index c891f046160e..259f0c934ce4 100644 --- a/resources/views/portal/ninja2020/profile/settings/general.blade.php +++ b/resources/views/portal/ninja2020/profile/settings/general.blade.php @@ -7,7 +7,7 @@
-
+ @csrf @method('PUT')
@@ -15,7 +15,9 @@
- + @error('first_name')
{{ $message }} @@ -25,7 +27,9 @@
- + @error('last_name')
{{ $message }} @@ -35,7 +39,9 @@
- + @error('email')
{{ $message }} @@ -44,8 +50,9 @@
- - + + @error('phone')
{{ $message }} @@ -54,8 +61,9 @@
- - + + @error('password')
{{ $message }} @@ -64,8 +72,11 @@
- - + + @error('password_confirmation')
{{ $message }} @@ -75,7 +86,8 @@
- +
diff --git a/resources/views/portal/ninja2020/profile/settings/name-website-logo.blade.php b/resources/views/portal/ninja2020/profile/settings/name-website-logo.blade.php index cd1f88afcb9d..a99547730af9 100644 --- a/resources/views/portal/ninja2020/profile/settings/name-website-logo.blade.php +++ b/resources/views/portal/ninja2020/profile/settings/name-website-logo.blade.php @@ -13,45 +13,49 @@
- - + + @error('name') -
- {{ $message }} -
+
+ {{ $message }} +
@enderror
- - + + @error('vat_number') -
- {{ $message }} -
+
+ {{ $message }} +
@enderror
- - + + @error('phone') -
- {{ $message }} -
+
+ {{ $message }} +
@enderror
- + E.g. https://example.com
- + @error('website') -
- {{ $message }} -
+
+ {{ $message }} +
@enderror
diff --git a/resources/views/portal/ninja2020/profile/settings/personal-address.blade.php b/resources/views/portal/ninja2020/profile/settings/personal-address.blade.php index 53c51709a6d3..e4b9ffc6e4fd 100644 --- a/resources/views/portal/ninja2020/profile/settings/personal-address.blade.php +++ b/resources/views/portal/ninja2020/profile/settings/personal-address.blade.php @@ -6,7 +6,7 @@
-
+ @csrf
diff --git a/resources/views/portal/ninja2020/profile/settings/shipping-address.blade.php b/resources/views/portal/ninja2020/profile/settings/shipping-address.blade.php index 9129fabddef6..82d15ebfde69 100644 --- a/resources/views/portal/ninja2020/profile/settings/shipping-address.blade.php +++ b/resources/views/portal/ninja2020/profile/settings/shipping-address.blade.php @@ -6,7 +6,7 @@
- + @csrf
diff --git a/tests/Browser/ClientPortal/CreditsTest.php b/tests/Browser/ClientPortal/CreditsTest.php new file mode 100644 index 000000000000..c5c274b44461 --- /dev/null +++ b/tests/Browser/ClientPortal/CreditsTest.php @@ -0,0 +1,45 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.credits.index') + ->assertSeeIn('span[data-ref="meta-title"]', 'Credits') + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/ClientPortal/DocumentsTest.php b/tests/Browser/ClientPortal/DocumentsTest.php new file mode 100644 index 000000000000..454acd856b59 --- /dev/null +++ b/tests/Browser/ClientPortal/DocumentsTest.php @@ -0,0 +1,45 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.documents.index') + ->assertSee('Documents') + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/AuthorizeNet/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/AuthorizeNet/CreditCardTest.php new file mode 100644 index 000000000000..d8030f5701ea --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/AuthorizeNet/CreditCardTest.php @@ -0,0 +1,127 @@ +markTestSkipped('Skipping Authorize.net (GitHub Actions)'); + } + + foreach (static::$browsers as $browser) { + $browser->driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', '3b6621f970ab18887c4f6dca78d3f8bb')->restore(); + } + + public function testPayWithNewCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->type('card-number', '4007000000027') + ->type('card-holders-name', 'John Doe') + ->type('.expiry', '12/28') + ->type('cvc', '100') + ->press('Pay Now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testPayWithNewCardAndSaveForFutureUse() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->radio('#proxy_is_default', true) + ->type('card-number', '4007000000027') + ->type('card-holders-name', 'John Doe') + ->type('.expiry', '12/28') + ->type('cvc', '100') + ->press('Pay Now') + ->waitForText('Details of the payment', 60) + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->assertSee('0027'); + }); + } + + public function testPayWithSavedCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->click('.toggle-payment-with-token') + ->press('Pay Now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testRemoveCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } + + public function testAddingCreditCardStandalone() + { + $this->markTestIncomplete("E00117 OTS Service Error 'Field validation error.'"); + + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Credit Card') + ->type('card-number', '4012888818888') + ->type('card-holders-name', 'John Doe') + ->type('.expiry', '12/28') + ->type('cvc', '900') + ->press('Add Payment Method') + ->waitForText('0027', 60); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/Braintree/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Braintree/CreditCardTest.php new file mode 100644 index 000000000000..ef125ab77981 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Braintree/CreditCardTest.php @@ -0,0 +1,146 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', 'f7ec488676d310683fb51802d076d713')->restore(); + + $cg = CompanyGateway::where('gateway_key', 'f7ec488676d310683fb51802d076d713')->firstOrFail(); + $fees_and_limits = $cg->fees_and_limits; + $fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits(); + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + + $company = Company::first(); + $settings = $company->settings; + + $settings->client_portal_allow_under_payment = true; + $settings->client_portal_allow_over_payment = true; + + $company->settings = $settings; + $company->save(); + } + + public function testPayWithNewCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->type('@underpayment-input', '100') + ->press('Pay Now') + ->clickLink('Credit Card') + ->waitFor('#braintree-hosted-field-number', 60) + ->withinFrame('#braintree-hosted-field-number', function (Browser $browser) { + $browser->type('credit-card-number', '4111111111111111'); + }) + ->withinFrame('#braintree-hosted-field-expirationDate', function (Browser $browser) { + $browser->type('expiration', '04/25'); + }) + ->press('Pay Now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testPayWithNewCardAndSaveForFuture() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->type('@underpayment-input', '100') + ->press('Pay Now') + ->clickLink('Credit Card') + ->waitFor('#braintree-hosted-field-number', 60) + ->withinFrame('#braintree-hosted-field-number', function (Browser $browser) { + $browser->typeSlowly('credit-card-number', '4005519200000004'); + }) + ->withinFrame('#braintree-hosted-field-expirationDate', function (Browser $browser) { + $browser->typeSlowly('expiration', '04/25'); + }) + ->radio('#proxy_is_default', true) + ->press('Pay Now') + ->waitForText('Details of the payment', 60) + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->assertSee('0004'); + }); + } + + public function testPayWithSavedCard() + { + $this->markTestSkipped('Works in "real" browser, otherwise giving error code 0.'); + + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->type('@underpayment-input', '100') + ->press('Pay Now') + ->clickLink('Credit Card') + ->click('.toggle-payment-with-token') + ->click('#pay-now-with-token') + ->waitForText('Details of the payment', 60); + }); + } + + public function testRemoveCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } + + public function testAddingPaymentMethodShouldntBePossible() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Credit Card') + ->assertSee('This payment method can be can saved for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.'); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/Braintree/PayPalTest.php b/tests/Browser/ClientPortal/Gateways/Braintree/PayPalTest.php new file mode 100644 index 000000000000..031d0c4a2e21 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Braintree/PayPalTest.php @@ -0,0 +1,45 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', 'f7ec488676d310683fb51802d076d713')->restore(); + } + + public function testOffsitePayment() + { + $this->markTestSkipped('Sometimes after redirect PayPal shows the register-like page with credit card, sometimes is login page.'); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/CheckoutCom/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/CheckoutCom/CreditCardTest.php new file mode 100644 index 000000000000..691779f30241 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/CheckoutCom/CreditCardTest.php @@ -0,0 +1,120 @@ +driver->manage()->deleteAllCookies(); + } + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', '3758e7f7c6f4cecf0f4f348b9a00f456')->restore(); + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testAddingPaymentMethodShouldntBePossible() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Credit Card') + ->assertSee('Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.'); + }); + } + + public function testPayWithNewCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('cardnumber', '4242424242424242') + ->type('exp-date', '04/22') + ->type('cvc', '100'); + }) + ->press('#pay-button') + ->waitForText('Details of the payment', 60); + }); + } + + public function testPayWithNewCardAndSaveForFutureUse() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('cardnumber', '4242424242424242') + ->type('exp-date', '04/22') + ->type('cvc', '100'); + }) + ->radio('#proxy_is_default', true) + ->press('#pay-button') + ->waitForText('Details of the payment', 60) + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->assertSee('4242'); + }); + } + + public function testPayWithSavedCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->click('.toggle-payment-with-token') + ->click('#pay-now-with-token') + ->waitForText('Details of the payment', 60); + }); + } + + public function testRemoveCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/PayPal/PayPalTest.php b/tests/Browser/ClientPortal/Gateways/PayPal/PayPalTest.php new file mode 100644 index 000000000000..0525406c80de --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/PayPal/PayPalTest.php @@ -0,0 +1,40 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testOffsitePayment() + { + $this->markTestSkipped('Sometimes after redirect PayPal shows the register-like page with credit card, sometimes is login page.'); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/Stripe/ACHTest.php b/tests/Browser/ClientPortal/Gateways/Stripe/ACHTest.php new file mode 100644 index 000000000000..53947540b35b --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Stripe/ACHTest.php @@ -0,0 +1,106 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + + $this->disableCompanyGateways(); + + // Enable Stripe. + CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->restore(); + + // Enable ACH. + $cg = CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->firstOrFail(); + $fees_and_limits = $cg->fees_and_limits; + $fees_and_limits->{GatewayType::BANK_TRANSFER} = new FeesAndLimits(); + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + + // ACH required US to be billing country. + $client = Client::first(); + $client->country_id = 840; + $client->save(); + } + + public function testAddingACHAccountAndVerifyingIt() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Bank Account') + ->type('#account-holder-name', 'John Doe') + ->select('#country', 'US') + ->select('#currency', 'USD') + ->type('#routing-number', '110000000') + ->type('#account-number', '000123456789') + ->check('#accept-terms') + ->press('Add Payment Method') + ->waitForText('ACH (Verification)', 60) + ->type('@verification-1st', '32') + ->type('@verification-2nd', '45') + ->press('Complete Verification') + ->assertSee('Verification completed successfully') + ->assertSee('Bank Transfer'); + }); + } + + public function testPayingWithExistingACH() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Bank Transfer') + ->click('.toggle-payment-with-token') + ->press('Pay Now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testRemoveACHAccount() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/Stripe/AlipayTest.php b/tests/Browser/ClientPortal/Gateways/Stripe/AlipayTest.php new file mode 100644 index 000000000000..7a6a4630b8e2 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Stripe/AlipayTest.php @@ -0,0 +1,72 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + + $this->disableCompanyGateways(); + + // Enable Stripe. + CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->restore(); + + // Enable Alipay. + $cg = CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->firstOrFail(); + $fees_and_limits = $cg->fees_and_limits; + $fees_and_limits->{GatewayType::ALIPAY} = new FeesAndLimits(); + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + + // Setting country to DEU (276). + $client = Client::first(); + $client->country_id = 276; + $client->save(); + } + + public function testPayingWithAlipay() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Alipay') + ->press('Pay Now') + ->waitForText('Alipay test payment page', 120) + ->press('.common-Button.common-Button--default') + ->waitForText('Details of the payment', 60); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/Stripe/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/Stripe/CreditCardTest.php new file mode 100644 index 000000000000..e15ea3d678e8 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Stripe/CreditCardTest.php @@ -0,0 +1,139 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + + $this->disableCompanyGateways(); + + // Enable Stripe. + CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->restore(); + + $cg = CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->firstOrFail(); + $fees_and_limits = $cg->fees_and_limits; + $fees_and_limits->{GatewayType::CREDIT_CARD} = new FeesAndLimits(); + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + } + + public function testPaymentWithNewCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->type('#cardholder-name', 'John Doe') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('cardnumber', '4242 4242 4242 4242') + ->type('exp-date', '04/22') + ->type('cvc', '242'); + }) + ->click('#pay-now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testPayWithNewCardAndSaveForFutureUse() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->type('#cardholder-name', 'John Doe') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('cardnumber', '4242 4242 4242 4242') + ->type('exp-date', '04/22') + ->type('cvc', '242'); + }) + ->radio('#proxy_is_default', true) + ->click('#pay-now') + ->waitForText('Details of the payment', 60) + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->assertSee('4242'); + }); + } + + public function testPayWithSavedCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->click('@pay-now-dropdown') + ->clickLink('Credit Card') + ->click('.toggle-payment-with-token') + ->click('#pay-now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testRemoveCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } + + public function testAddingCreditCardStandalone() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Credit Card') + ->type('#cardholder-name', 'John Doe') + ->withinFrame('iframe', function (Browser $browser) { + $browser + ->type('cardnumber', '4242 4242 4242 4242') + ->type('exp-date', '04/22') + ->type('cvc', '242'); + }) + ->press('Add Payment Method') + ->waitForText('**** 4242'); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/Stripe/SofortTest.php b/tests/Browser/ClientPortal/Gateways/Stripe/SofortTest.php new file mode 100644 index 000000000000..a8a0fe88d548 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/Stripe/SofortTest.php @@ -0,0 +1,72 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + + $this->disableCompanyGateways(); + + // Enable Stripe. + CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->restore(); + + // Enable SOFORT. + $cg = CompanyGateway::where('gateway_key', 'd14dd26a37cecc30fdd65700bfb55b23')->firstOrFail(); + $fees_and_limits = $cg->fees_and_limits; + $fees_and_limits->{GatewayType::SOFORT} = new FeesAndLimits(); + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + + // SOFORT required ['AUT', 'BEL', 'DEU', 'ITA', 'NLD', 'ESP'] to be billing country. + // Setting country to DEU (276). + $client = Client::first(); + $client->country_id = 276; + $client->save(); + } + + public function testPayingWithSofort() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Sofort') + ->press('Pay Now') + ->waitForText('Sofort test payment page', 120) + ->press('.common-Button.common-Button--default') + ->waitForText('Details of the payment', 60); + }); + } +} diff --git a/tests/Browser/ClientPortal/Gateways/WePay/ACHTest.php b/tests/Browser/ClientPortal/Gateways/WePay/ACHTest.php new file mode 100644 index 000000000000..25e405429747 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/WePay/ACHTest.php @@ -0,0 +1,62 @@ +driver->manage()->deleteAllCookies(); + } + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', '8fdeed552015b3c7b44ed6c8ebd9e992')->restore(); + + // Enable ACH. + $cg = CompanyGateway::where('gateway_key', '8fdeed552015b3c7b44ed6c8ebd9e992')->firstOrFail(); + $fees_and_limits = $cg->fees_and_limits; + $fees_and_limits->{GatewayType::BANK_TRANSFER} = new FeesAndLimits(); + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPayingWithNoPreauthorizedIsntPossible() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Bank Transfer') + ->assertSee('To pay with a bank account, first you have to add it as payment method.'); + }); + } +} \ No newline at end of file diff --git a/tests/Browser/ClientPortal/Gateways/WePay/CreditCardTest.php b/tests/Browser/ClientPortal/Gateways/WePay/CreditCardTest.php new file mode 100644 index 000000000000..062c1b411549 --- /dev/null +++ b/tests/Browser/ClientPortal/Gateways/WePay/CreditCardTest.php @@ -0,0 +1,123 @@ +driver->manage()->deleteAllCookies(); + } + + $this->disableCompanyGateways(); + + CompanyGateway::where('gateway_key', '8fdeed552015b3c7b44ed6c8ebd9e992')->restore(); + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPayWithNewCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->type('card-number', '4003830171874018') + ->type('card-holders-name', 'John Doe') + ->type('.expiry', '12/28') + ->type('cvc', '100') + ->press('Pay Now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testPayWithNewCardAndSaveForFutureUse() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->type('card-number', '4003830171874018') + ->type('card-holders-name', 'John Doe') + ->type('.expiry', '12/28') + ->type('cvc', '100') + ->radio('#proxy_is_default', true) + ->press('Pay Now') + ->waitForText('Details of the payment', 60) + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->assertSee('4018'); + }); + } + + public function testPayWithSavedCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('@pay-now') + ->press('Pay Now') + ->clickLink('Credit Card') + ->click('.toggle-payment-with-token') + ->press('Pay Now') + ->waitForText('Details of the payment', 60); + }); + } + + public function testRemoveCreditCard() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->clickLink('View') + ->press('Remove Payment Method') + ->waitForText('Confirmation') + ->click('@confirm-payment-removal') + ->assertSee('Payment method has been successfully removed.'); + }); + } + + public function testAddingCreditCardStandalone() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->press('Add Payment Method') + ->clickLink('Credit Card') + ->waitForText('Credit Card') + ->type('#cardholder_name', 'John Doe') + ->type('card-number', '4003830171874018') + ->type('card-holders-name', 'John Doe') + ->type('.expiry', '12/28') + ->type('cvc', '100') + ->press('Add Payment Method') + ->waitForText(4018, 60); + }); + } +} diff --git a/tests/Browser/ClientPortal/InvoicesTest.php b/tests/Browser/ClientPortal/InvoicesTest.php new file mode 100644 index 000000000000..838ae1bc6671 --- /dev/null +++ b/tests/Browser/ClientPortal/InvoicesTest.php @@ -0,0 +1,79 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->assertSeeIn('span[data-ref="meta-title"]', 'Invoices') + ->visitRoute('client.logout'); + }); + } + + public function testClickingPayNowWithoutInvoices() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->press('Pay Now') + ->assertSee('No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.') + ->visitRoute('client.logout'); + }); + } + + public function testClickingDownloadWithoutInvoices() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->press('Download') + ->assertSee('No items selected.') + ->visitRoute('client.logout'); + }); + } + + public function testCheckingInvoiceAndClickingPayNow() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->check('.form-check.form-check-child') + ->press('Pay Now') + ->assertPathIs('/client/invoices/payment') + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/ClientPortal/LoginTest.php b/tests/Browser/ClientPortal/LoginTest.php new file mode 100644 index 000000000000..407e2f3c2999 --- /dev/null +++ b/tests/Browser/ClientPortal/LoginTest.php @@ -0,0 +1,65 @@ +driver->manage()->deleteAllCookies(); + } + } + + public function testLoginPage() + { + $this->browse(function (Browser $browser) { + $browser + ->visit(route('client.login')) + ->assertSee('Client Portal') + ->type('email', 'user@example.com') + ->type('password', 'password') + ->press('Login'); + + $browser->assertPathIs('/client/invoices'); + }); + } + + public function testLoginFormValidation() + { + $this->browse(function (Browser $browser) { + $browser + ->visit(route('client.login')) + ->press('Login') + ->assertSee('The email field is required.') + ->assertSee('The password field is required.'); + }); + } + + public function testForgotPasswordLink() + { + $this->browse(function (Browser $browser) { + $browser + ->visit(route('client.login')) + ->assertSeeLink('Forgot your password?') + ->clickLink('Forgot your password?') + ->assertPathIs('/client/password/reset'); + }); + } +} diff --git a/tests/Browser/ClientPortal/PaymentMethodsTest.php b/tests/Browser/ClientPortal/PaymentMethodsTest.php new file mode 100644 index 000000000000..56feb97f80c2 --- /dev/null +++ b/tests/Browser/ClientPortal/PaymentMethodsTest.php @@ -0,0 +1,45 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payment_methods.index') + ->assertSeeIn('span[data-ref="meta-title"]', 'Payment Methods') + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/ClientPortal/PaymentsTest.php b/tests/Browser/ClientPortal/PaymentsTest.php new file mode 100644 index 000000000000..835389d16506 --- /dev/null +++ b/tests/Browser/ClientPortal/PaymentsTest.php @@ -0,0 +1,45 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.payments.index') + ->assertSeeIn('span[data-ref="meta-title"]', 'Payments') + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/ClientPortal/ProfileSettingsTest.php b/tests/Browser/ClientPortal/ProfileSettingsTest.php new file mode 100644 index 000000000000..ff567c60dfbb --- /dev/null +++ b/tests/Browser/ClientPortal/ProfileSettingsTest.php @@ -0,0 +1,210 @@ +faker = Factory::create(); + + foreach (static::$browsers as $browser) { + $browser->driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.invoices.index') + ->click('button[data-ref="client-profile-dropdown"]') + ->click('a[data-ref="client-profile-dropdown-settings"]') + ->waitForText('Client Information') + ->assertSeeIn('span[data-ref="meta-title"]', 'Client Information') + ->visitRoute('client.logout'); + }); + } + + public function testClientDetailsUpdate() + { + $original = [ + 'name' => $this->faker->name, + 'vat_number' => (string)$this->faker->randomNumber(6), + 'phone' => $this->faker->phoneNumber, + 'website' => $this->faker->url, + ]; + + $this->browse(function (Browser $browser) use ($original) { + $browser + ->visitRoute('client.invoices.index') + ->click('button[data-ref="client-profile-dropdown"]') + ->click('a[data-ref="client-profile-dropdown-settings"]') + ->waitForText('Client Information'); + + $browser + ->with('#update_contact', function (Browser $form) use ($original) { + $form + ->type('#client_name', $original['name']) + ->type('#client_vat_number', $original['vat_number']) + ->type('#client_phone', $original['phone']) + ->type('#client_website', $original['website']) + ->press('Save'); + }) + ->pause(2000) + ->refresh(); + + $updated = [ + 'name' => $browser->value('#client_name'), + 'vat_number' => $browser->value('#client_vat_number'), + 'phone' => $browser->value('#client_phone'), + 'website' => $browser->value('#client_website') + ]; + + $this->assertSame($original, $updated); + }); + } + + public function testContactDetailsUpdate() + { + $original = [ + 'first_name' => $this->faker->firstName, + 'last_name' => $this->faker->lastName, + 'email_address' => 'user@example.com', + 'phone' => $this->faker->phoneNumber, + ]; + + $this->browse(function (Browser $browser) use ($original) { + $browser + ->visitRoute('client.invoices.index') + ->click('button[data-ref="client-profile-dropdown"]') + ->click('a[data-ref="client-profile-dropdown-settings"]') + ->waitForText('Client Information'); + + $browser + ->with('#update_client', function (Browser $form) use ($original) { + $form + ->type('#contact_first_name', $original['first_name']) + ->type('#contact_last_name', $original['last_name']) + ->scrollIntoView('#contact_email_address') + ->type('#contact_email_address', $original['email_address']) + ->type('#contact_phone', $original['phone']) + ->click('button[data-ref="update-contact-details"]'); + }) + ->pause(2000) + ->refresh(); + + $updated = [ + 'first_name' => $browser->value('#contact_first_name'), + 'last_name' => $browser->value('#contact_last_name'), + 'email_address' => $browser->value('#contact_email_address'), + 'phone' => $browser->value('#contact_phone'), + ]; + + $this->assertSame($original, $updated); + }); + } + + public function testBillingAddressUpdate() + { + $original = [ + 'street' => $this->faker->streetName, + 'apt' => $this->faker->streetAddress, + 'city' => $this->faker->city, + 'state' => $this->faker->state, + 'postal_code' => $this->faker->postcode, + ]; + + $this->browse(function (Browser $browser) use ($original) { + $browser + ->visitRoute('client.invoices.index') + ->click('button[data-ref="client-profile-dropdown"]') + ->click('a[data-ref="client-profile-dropdown-settings"]') + ->waitForText('Client Information'); + + $browser + ->with('#update_billing_address', function (Browser $form) use ($original) { + $form + ->type('#address1', $original['street']) + ->type('#address2', $original['apt']) + ->type('#city', $original['city']) + ->type('#state', $original['state']) + ->type('#postal_code', $original['postal_code']) + ->select('#country') + ->press('Save'); + }) + ->pause(1000) + ->refresh(); + + $updated = [ + 'street' => $browser->value('#address1'), + 'apt' => $browser->value('#address2'), + 'city' => $browser->value('#city'), + 'state' => $browser->value('#state'), + 'postal_code' => $browser->value('#postal_code'), + ]; + + $this->assertSame($original, $updated); + }); + } + + public function testShippingAddressUpdate() + { + $original = [ + 'street' => $this->faker->streetName, + 'apt' => $this->faker->streetAddress, + 'city' => $this->faker->city, + 'state' => $this->faker->state, + 'postal_code' => $this->faker->postcode, + ]; + + $this->browse(function (Browser $browser) use ($original) { + $browser + ->visitRoute('client.invoices.index') + ->click('button[data-ref="client-profile-dropdown"]') + ->click('a[data-ref="client-profile-dropdown-settings"]') + ->waitForText('Client Information'); + + $browser + ->with('#update_shipping_address', function (Browser $form) use ($original) { + $form + ->type('#shipping_address1', $original['street']) + ->type('#shipping_address2', $original['apt']) + ->type('#shipping_city', $original['city']) + ->type('#shipping_state', $original['state']) + ->type('#shipping_postal_code', $original['postal_code']) + ->select('#shipping_country') + ->press('Save'); + }) + ->pause(1000) + ->refresh(); + + $updated = [ + 'street' => $browser->value('#shipping_address1'), + 'apt' => $browser->value('#shipping_address2'), + 'city' => $browser->value('#shipping_city'), + 'state' => $browser->value('#shipping_state'), + 'postal_code' => $browser->value('#shipping_postal_code'), + ]; + + $this->assertSame($original, $updated); + }); + } +} diff --git a/tests/Browser/ClientPortal/QuotesTest.php b/tests/Browser/ClientPortal/QuotesTest.php new file mode 100644 index 000000000000..2a294eca2ed2 --- /dev/null +++ b/tests/Browser/ClientPortal/QuotesTest.php @@ -0,0 +1,84 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.quotes.index') + ->assertSeeIn('span[data-ref="meta-title"]', 'Quotes') + ->visitRoute('client.logout'); + }); + } + + public function testClickingApproveWithoutQuotesDoesntWork() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.quotes.index') + ->press('Approve') + ->assertPathIs('/client/quotes'); + }); + } + + public function testApprovingQuotes() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.quotes.index') + ->check('.form-check.form-check-child') + ->press('Approve') + ->assertPathIs('/client/quotes/approve') + ->press('Approve') + ->assertPathIs('/client/quotes') + ->assertSee('Quote(s) approved successfully.') + ->visitRoute('client.logout'); + }); + } + + public function testQuotesWithSentStatusCanOnlyBeApproved() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.quotes.index') + ->check('.form-check.form-check-child') + ->press('Approve') + ->assertPathIs('/client/quotes') + ->assertDontSee('Quote(s) approved successfully.') + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/ClientPortal/RecurringInvoicesTest.php b/tests/Browser/ClientPortal/RecurringInvoicesTest.php new file mode 100644 index 000000000000..d5d9b711ef6b --- /dev/null +++ b/tests/Browser/ClientPortal/RecurringInvoicesTest.php @@ -0,0 +1,65 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.recurring_invoices.index') + ->assertSeeIn('span[data-ref="meta-title"]', 'Recurring Invoices') + ->visitRoute('client.logout'); + }); + } + + public function testRequestingCancellation() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.recurring_invoices.index') + ->clickLink('View') + ->assertSee('Cancellation') + ->press('Request Cancellation') + ->pause(1000) + ->waitForText('Request cancellation') + ->press('Confirm') + ->pause(5000) + ->assertPathIs( + route('client.recurring_invoices.request_cancellation', RecurringInvoice::first()->hashed_id, false) + ) + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/ClientPortal/SubscriptionsTest.php b/tests/Browser/ClientPortal/SubscriptionsTest.php new file mode 100644 index 000000000000..b0962cc3e333 --- /dev/null +++ b/tests/Browser/ClientPortal/SubscriptionsTest.php @@ -0,0 +1,45 @@ +driver->manage()->deleteAllCookies(); + } + + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login()) + ->auth(); + }); + } + + public function testPageLoads() + { + $this->browse(function (Browser $browser) { + $browser + ->visitRoute('client.subscriptions.index') + ->assertSeeIn('span[data-ref="meta-title"]', 'Subscriptions') + ->visitRoute('client.logout'); + }); + } +} diff --git a/tests/Browser/Pages/ClientPortal/Login.php b/tests/Browser/Pages/ClientPortal/Login.php new file mode 100644 index 000000000000..11125eba444a --- /dev/null +++ b/tests/Browser/Pages/ClientPortal/Login.php @@ -0,0 +1,51 @@ +assertPathIs($this->url()); + } + + /** + * Get the element shortcuts for the page. + * + * @return array + */ + public function elements() + { + return [ + '@element' => '#selector', + ]; + } + + public function auth(Browser $browser) + { + $browser + ->visitRoute('client.login') + ->type('email', 'user@example.com') + ->type('password', 'password') + ->press('Login'); + } +} diff --git a/tests/Browser/console/.gitignore b/tests/Browser/console/.gitignore new file mode 100644 index 000000000000..d6b7ef32c847 --- /dev/null +++ b/tests/Browser/console/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Browser/screenshots/.gitignore b/tests/Browser/screenshots/.gitignore new file mode 100644 index 000000000000..d6b7ef32c847 --- /dev/null +++ b/tests/Browser/screenshots/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php new file mode 100644 index 000000000000..a7cdfe734de8 --- /dev/null +++ b/tests/DuskTestCase.php @@ -0,0 +1,72 @@ +addArguments(collect([ + '--window-size=1920,1080', + ])->unless($this->hasHeadlessDisabled(), function ($items) { + return $items->merge([ + '--disable-gpu', + '--headless', + ]); + })->all()); + + return RemoteWebDriver::create( + $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515', + DesiredCapabilities::chrome()->setCapability( + ChromeOptions::CAPABILITY, $options + ) + ); + } + + /** + * Determine whether the Dusk command has disabled headless mode. + * + * @return bool + */ + protected function hasHeadlessDisabled() + { + return isset($_SERVER['DUSK_HEADLESS_DISABLED']) || + isset($_ENV['DUSK_HEADLESS_DISABLED']); + } + + /** + * Disable all company gateways, test classes should enable them per need. + * + * @return void + */ + public function disableCompanyGateways() + { + CompanyGateway::where('company_id', 1)->delete(); + } +} diff --git a/tests/ClientPortal/CreditsTest.php b/tests/Feature/ClientPortal/CreditsTest.php similarity index 98% rename from tests/ClientPortal/CreditsTest.php rename to tests/Feature/ClientPortal/CreditsTest.php index 129c811bc34c..dc61ad687b6f 100644 --- a/tests/ClientPortal/CreditsTest.php +++ b/tests/Feature/ClientPortal/CreditsTest.php @@ -10,7 +10,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace Tests\ClientPortal; +namespace Tests\Feature\ClientPortal; use App\Http\Livewire\CreditsTable; use App\Models\Account; @@ -23,6 +23,7 @@ use Faker\Factory; use Illuminate\Foundation\Testing\DatabaseTransactions; use Livewire\Livewire; use Tests\TestCase; +use function now; class CreditsTest extends TestCase { diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php new file mode 100644 index 000000000000..2ec29de8ca01 --- /dev/null +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -0,0 +1,90 @@ +faker = Factory::create(); + } + + public function testInvoiceTableFilters() + { + $account = Account::factory()->create(); + + $user = User::factory()->create( + ['account_id' => $account->id, 'email' => $this->faker->safeEmail] + ); + + $company = Company::factory()->create(['account_id' => $account->id]); + + $client = Client::factory()->create(['company_id' => $company->id, 'user_id' => $user->id]); + + ClientContact::factory()->count(2)->create([ + 'user_id' => $user->id, + 'client_id' => $client->id, + 'company_id' => $company->id, + ]); + + $sent = Invoice::factory()->create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'status_id' => Invoice::STATUS_SENT, + ]); + + $paid = Invoice::factory()->create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'status_id' => Invoice::STATUS_PAID, + ]); + + $unpaid = Invoice::factory()->create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'client_id' => $client->id, + 'status_id' => Invoice::STATUS_UNPAID, + ]); + + $this->actingAs($client->contacts->first(), 'contact'); + + Livewire::test(InvoicesTable::class, ['company' => $company]) + ->assertSee($sent->number) + ->assertSee($paid->number) + ->assertSee($unpaid->number); + + Livewire::test(InvoicesTable::class, ['company' => $company]) + ->set('status', ['paid']) + ->assertSee($paid->number) + ->assertDontSee($unpaid->number); + } +}