diff --git a/src/resources/vue/Admin/Distlist.vue b/src/resources/vue/Admin/Distlist.vue index 021c9bd7..acfe289c 100644 --- a/src/resources/vue/Admin/Distlist.vue +++ b/src/resources/vue/Admin/Distlist.vue @@ -1,116 +1,116 @@ diff --git a/src/resources/vue/Admin/Domain.vue b/src/resources/vue/Admin/Domain.vue index 74e9b749..5bdf8803 100644 --- a/src/resources/vue/Admin/Domain.vue +++ b/src/resources/vue/Admin/Domain.vue @@ -1,118 +1,118 @@ diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue index 0349d9fc..b4f99676 100644 --- a/src/resources/vue/Admin/User.vue +++ b/src/resources/vue/Admin/User.vue @@ -1,832 +1,832 @@ diff --git a/src/resources/vue/Domain/Info.vue b/src/resources/vue/Domain/Info.vue index 58741c0d..3c831088 100644 --- a/src/resources/vue/Domain/Info.vue +++ b/src/resources/vue/Domain/Info.vue @@ -1,222 +1,221 @@ diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue index aca09338..c448c1c2 100644 --- a/src/resources/vue/Meet/Room.vue +++ b/src/resources/vue/Meet/Room.vue @@ -1,667 +1,667 @@ diff --git a/src/resources/vue/Meet/RoomOptions.vue b/src/resources/vue/Meet/RoomOptions.vue index dc6d3671..bbf5ff9c 100644 --- a/src/resources/vue/Meet/RoomOptions.vue +++ b/src/resources/vue/Meet/RoomOptions.vue @@ -1,113 +1,112 @@ diff --git a/src/resources/vue/Meet/RoomStats.vue b/src/resources/vue/Meet/RoomStats.vue index 9df41dae..330951c1 100644 --- a/src/resources/vue/Meet/RoomStats.vue +++ b/src/resources/vue/Meet/RoomStats.vue @@ -1,91 +1,64 @@ - diff --git a/src/resources/vue/PasswordReset.vue b/src/resources/vue/PasswordReset.vue index a71d58bd..d3a45f78 100644 --- a/src/resources/vue/PasswordReset.vue +++ b/src/resources/vue/PasswordReset.vue @@ -1,173 +1,173 @@ diff --git a/src/resources/vue/Reseller/Dashboard.vue b/src/resources/vue/Reseller/Dashboard.vue index 83ad2142..8c5f197e 100644 --- a/src/resources/vue/Reseller/Dashboard.vue +++ b/src/resources/vue/Reseller/Dashboard.vue @@ -1,51 +1,51 @@ diff --git a/src/resources/vue/Resource/Info.vue b/src/resources/vue/Resource/Info.vue index eb420ccb..00cf6fc8 100644 --- a/src/resources/vue/Resource/Info.vue +++ b/src/resources/vue/Resource/Info.vue @@ -1,187 +1,188 @@ diff --git a/src/resources/vue/SharedFolder/Info.vue b/src/resources/vue/SharedFolder/Info.vue index 737f5ff3..52e1a749 100644 --- a/src/resources/vue/SharedFolder/Info.vue +++ b/src/resources/vue/SharedFolder/Info.vue @@ -1,181 +1,182 @@ diff --git a/src/resources/vue/Signup.vue b/src/resources/vue/Signup.vue index 6bff675b..987a921c 100644 --- a/src/resources/vue/Signup.vue +++ b/src/resources/vue/Signup.vue @@ -1,302 +1,297 @@ diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue index a9a36b6a..9c7ddb61 100644 --- a/src/resources/vue/User/Info.vue +++ b/src/resources/vue/User/Info.vue @@ -1,326 +1,326 @@ diff --git a/src/resources/vue/User/Profile.vue b/src/resources/vue/User/Profile.vue index 1f5171d7..36232f28 100644 --- a/src/resources/vue/User/Profile.vue +++ b/src/resources/vue/User/Profile.vue @@ -1,114 +1,114 @@ diff --git a/src/resources/vue/Wallet.vue b/src/resources/vue/Wallet.vue index 601bc83c..773e1b97 100644 --- a/src/resources/vue/Wallet.vue +++ b/src/resources/vue/Wallet.vue @@ -1,430 +1,438 @@ diff --git a/src/tests/Browser/Reseller/WalletTest.php b/src/tests/Browser/Reseller/WalletTest.php index 2df1d124..e2f8ab85 100644 --- a/src/tests/Browser/Reseller/WalletTest.php +++ b/src/tests/Browser/Reseller/WalletTest.php @@ -1,248 +1,248 @@ getTestUser('reseller@' . \config('app.domain')); $wallet = $reseller->wallets()->first(); $wallet->balance = 0; $wallet->save(); $wallet->payments()->delete(); $wallet->transactions()->delete(); parent::tearDown(); } /** * Test wallet page (unauthenticated) */ public function testWalletUnauth(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $browser->visit('/wallet')->on(new Home()); }); } /** * Test wallet "box" on Dashboard */ public function testDashboard(): void { $reseller = $this->getTestUser('reseller@' . \config('app.domain')); Wallet::where('user_id', $reseller->id)->update(['balance' => 125]); // Positive balance $this->browse(function (Browser $browser) { $browser->visit(new Home()) ->submitLogon('reseller@' . \config('app.domain'), \App\Utils::generatePassphrase(), true) ->on(new Dashboard()) - ->assertSeeIn('@links .link-wallet .name', 'Wallet') + ->assertSeeIn('@links .link-wallet svg + span', 'Wallet') ->assertSeeIn('@links .link-wallet .badge.bg-success', '1,25 CHF'); }); Wallet::where('user_id', $reseller->id)->update(['balance' => -1234]); // Negative balance $this->browse(function (Browser $browser) { $browser->visit(new Dashboard()) - ->assertSeeIn('@links .link-wallet .name', 'Wallet') + ->assertSeeIn('@links .link-wallet svg + span', 'Wallet') ->assertSeeIn('@links .link-wallet .badge.bg-danger', '-12,34 CHF'); }); } /** * Test wallet page * * @depends testDashboard */ public function testWallet(): void { $reseller = $this->getTestUser('reseller@' . \config('app.domain')); Wallet::where('user_id', $reseller->id)->update(['balance' => -1234]); $this->browse(function (Browser $browser) { $browser->click('@links .link-wallet') ->on(new WalletPage()) ->assertSeeIn('#wallet .card-title', 'Account balance -12,34 CHF') ->assertSeeIn('#wallet .card-title .text-danger', '-12,34 CHF') ->assertSeeIn('#wallet .card-text', 'You are out of credit'); }); } /** * Test Receipts tab * * @depends testWallet */ public function testReceipts(): void { $user = $this->getTestUser('reseller@' . \config('app.domain')); $wallet = $user->wallets()->first(); $wallet->payments()->delete(); // Assert Receipts tab content when there's no receipts available $this->browse(function (Browser $browser) { $browser->visit(new WalletPage()) ->assertSeeIn('#wallet .card-title', 'Account balance 0,00 CHF') ->assertSeeIn('#wallet .card-title .text-success', '0,00 CHF') ->assertSeeIn('#wallet .card-text', 'You are in your free trial period.') // TODO ->assertSeeIn('@nav #tab-receipts', 'Receipts') ->with('@receipts-tab', function (Browser $browser) { $browser->waitUntilMissing('.app-loader') ->assertSeeIn('p', 'There are no receipts for payments') ->assertDontSeeIn('p', 'Here you can download') ->assertMissing('select') ->assertMissing('button'); }); }); // Create some sample payments $receipts = []; $date = Carbon::create(intval(date('Y')) - 1, 3, 30); $payment = Payment::create([ 'id' => 'AAA1', 'status' => PaymentProvider::STATUS_PAID, 'type' => PaymentProvider::TYPE_ONEOFF, 'description' => 'Paid in March', 'wallet_id' => $wallet->id, 'provider' => 'stripe', 'amount' => 1111, 'currency_amount' => 1111, 'currency' => 'CHF', ]); $payment->updated_at = $date; $payment->save(); $receipts[] = $date->format('Y-m'); $date = Carbon::create(intval(date('Y')) - 1, 4, 30); $payment = Payment::create([ 'id' => 'AAA2', 'status' => PaymentProvider::STATUS_PAID, 'type' => PaymentProvider::TYPE_ONEOFF, 'description' => 'Paid in April', 'wallet_id' => $wallet->id, 'provider' => 'stripe', 'amount' => 1111, 'currency_amount' => 1111, 'currency' => 'CHF', ]); $payment->updated_at = $date; $payment->save(); $receipts[] = $date->format('Y-m'); // Assert Receipts tab with receipts available $this->browse(function (Browser $browser) use ($receipts) { $browser->refresh() ->on(new WalletPage()) ->assertSeeIn('@nav #tab-receipts', 'Receipts') ->with('@receipts-tab', function (Browser $browser) use ($receipts) { $browser->waitUntilMissing('.app-loader') ->assertDontSeeIn('p', 'There are no receipts for payments') ->assertSeeIn('p', 'Here you can download') ->assertSeeIn('button', 'Download') ->assertElementsCount('select > option', 2) ->assertSeeIn('select > option:nth-child(1)', $receipts[1]) ->assertSeeIn('select > option:nth-child(2)', $receipts[0]); // Download a receipt file $browser->select('select', $receipts[0]) ->click('button') ->pause(2000); $files = glob(__DIR__ . '/../downloads/*.pdf'); $filename = pathinfo($files[0], PATHINFO_BASENAME); $this->assertTrue(strpos($filename, $receipts[0]) !== false); $content = $browser->readDownloadedFile($filename, 0); $this->assertStringStartsWith("%PDF-1.", $content); $browser->removeDownloadedFile($filename); }); }); } /** * Test History tab * * @depends testWallet */ public function testHistory(): void { $user = $this->getTestUser('reseller@' . \config('app.domain')); $wallet = $user->wallets()->first(); $wallet->transactions()->delete(); // Create some sample transactions $transactions = $this->createTestTransactions($wallet); $transactions = array_reverse($transactions); $pages = array_chunk($transactions, 10 /* page size*/); $this->browse(function (Browser $browser) use ($pages) { $browser->on(new WalletPage()) ->assertSeeIn('@nav #tab-history', 'History') ->click('@nav #tab-history') ->with('@history-tab', function (Browser $browser) use ($pages) { $browser->waitUntilMissing('.app-loader') ->assertElementsCount('table tbody tr', 10) ->assertMissing('table td.email') ->assertSeeIn('.more-loader button', 'Load more'); foreach ($pages[0] as $idx => $transaction) { $selector = 'table tbody tr:nth-child(' . ($idx + 1) . ')'; $priceStyle = $transaction->type == Transaction::WALLET_AWARD ? 'text-success' : 'text-danger'; $browser->assertSeeIn("$selector td.description", $transaction->shortDescription()) ->assertMissing("$selector td.selection button") ->assertVisible("$selector td.price.{$priceStyle}"); // TODO: Test more transaction details } // Load the next page $browser->click('.more-loader button') ->waitUntilMissing('.app-loader') ->assertElementsCount('table tbody tr', 12) ->assertMissing('.more-loader button'); $debitEntry = null; foreach ($pages[1] as $idx => $transaction) { $selector = 'table tbody tr:nth-child(' . ($idx + 1 + 10) . ')'; $priceStyle = $transaction->type == Transaction::WALLET_CREDIT ? 'text-success' : 'text-danger'; $browser->assertSeeIn("$selector td.description", $transaction->shortDescription()); if ($transaction->type == Transaction::WALLET_DEBIT) { $debitEntry = $selector; } else { $browser->assertMissing("$selector td.selection button"); } } }); }); } } diff --git a/src/tests/Browser/SettingsTest.php b/src/tests/Browser/SettingsTest.php index 98a1ddaf..25505bc4 100644 --- a/src/tests/Browser/SettingsTest.php +++ b/src/tests/Browser/SettingsTest.php @@ -1,120 +1,120 @@ browse(function (Browser $browser) { $browser->visit('/settings')->on(new Home()); }); } /** * Test settings "box" on Dashboard */ public function testDashboard(): void { $this->browse(function (Browser $browser) { // Test a user that is not an account owner $browser->visit(new Home()) ->submitLogon('jack@kolab.org', 'simple123', true) ->on(new Dashboard()) - ->assertMissing('@links .link-settings .name') + ->assertMissing('@links .link-settings') ->visit('/settings') ->assertErrorPage(403) ->within(new Menu(), function (Browser $browser) { $browser->clickMenuItem('logout'); }); // Test the account owner $browser->waitForLocation('/login') ->on(new Home()) ->submitLogon('john@kolab.org', 'simple123', true) ->on(new Dashboard()) - ->assertSeeIn('@links .link-settings .name', 'Settings'); + ->assertSeeIn('@links .link-settings svg + span', 'Settings'); }); } /** * Test Settings page * * @depends testDashboard */ public function testSettings(): void { $john = $this->getTestUser('john@kolab.org'); $john->setSetting('password_policy', 'min:5,max:100,lower'); $john->setSetting('max_password_age', null); $this->browse(function (Browser $browser) { $browser->click('@links .link-settings') ->on(new Settings()) ->assertSeeIn('#settings .card-title', 'Settings') // Password policy ->assertSeeIn('@form .row:nth-child(1) > label', 'Password Policy') ->with('@form #password_policy', function (Browser $browser) { $browser->assertElementsCount('li', 7) ->assertSeeIn('li:nth-child(1) label', 'Minimum password length') ->assertChecked('li:nth-child(1) input[type=checkbox]') ->assertDisabled('li:nth-child(1) input[type=checkbox]') ->assertValue('li:nth-child(1) input[type=text]', '5') ->assertSeeIn('li:nth-child(2) label', 'Maximum password length') ->assertChecked('li:nth-child(2) input[type=checkbox]') ->assertDisabled('li:nth-child(2) input[type=checkbox]') ->assertValue('li:nth-child(2) input[type=text]', '100') ->assertSeeIn('li:nth-child(3) label', 'Password contains a lower-case character') ->assertChecked('li:nth-child(3) input[type=checkbox]') ->assertMissing('li:nth-child(3) input[type=text]') ->assertSeeIn('li:nth-child(4) label', 'Password contains an upper-case character') ->assertNotChecked('li:nth-child(4) input[type=checkbox]') ->assertMissing('li:nth-child(4) input[type=text]') ->assertSeeIn('li:nth-child(5) label', 'Password contains a digit') ->assertNotChecked('li:nth-child(5) input[type=checkbox]') ->assertMissing('li:nth-child(5) input[type=text]') ->assertSeeIn('li:nth-child(6) label', 'Password contains a special character') ->assertNotChecked('li:nth-child(6) input[type=checkbox]') ->assertMissing('li:nth-child(6) input[type=text]') ->assertSeeIn('li:nth-child(7) label', 'Password cannot be the same as the last') ->assertNotChecked('li:nth-child(7) input[type=checkbox]') ->assertMissing('li:nth-child(7) input[type=text]') ->assertSelected('li:nth-child(7) select', 3) ->assertSelectHasOptions('li:nth-child(7) select', [1,2,3,4,5,6]) // Change the policy ->type('li:nth-child(1) input[type=text]', '11') ->type('li:nth-child(2) input[type=text]', '120') ->click('li:nth-child(3) input[type=checkbox]') ->click('li:nth-child(4) input[type=checkbox]'); }) ->assertSeeIn('@form .row:nth-child(2) > label', 'Password Retention') ->with('@form #password_retention', function (Browser $browser) { $browser->assertElementsCount('li', 1) ->assertSeeIn('li:nth-child(1) label', 'Require a password change every') ->assertNotChecked('li:nth-child(1) input[type=checkbox]') ->assertSelected('li:nth-child(1) select', 3) ->assertSelectHasOptions('li:nth-child(1) select', [3, 6, 9, 12]) // change the policy ->check('li:nth-child(1) input[type=checkbox]') ->select('li:nth-child(1) select', 6); }) ->click('button[type=submit]') ->assertToast(Toast::TYPE_SUCCESS, 'User settings updated successfully.'); }); $this->assertSame('min:11,max:120,upper', $john->getSetting('password_policy')); $this->assertSame('6', $john->getSetting('max_password_age')); } } diff --git a/src/tests/Browser/WalletTest.php b/src/tests/Browser/WalletTest.php index df2149ee..bcf89dc0 100644 --- a/src/tests/Browser/WalletTest.php +++ b/src/tests/Browser/WalletTest.php @@ -1,275 +1,275 @@ deleteTestUser('wallets-controller@kolabnow.com'); $john = $this->getTestUser('john@kolab.org'); Wallet::where('user_id', $john->id)->update(['balance' => -1234]); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestUser('wallets-controller@kolabnow.com'); $john = $this->getTestUser('john@kolab.org'); Wallet::where('user_id', $john->id)->update(['balance' => 0]); parent::tearDown(); } /** * Test wallet page (unauthenticated) */ public function testWalletUnauth(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $browser->visit('/wallet')->on(new Home()); }); } /** * Test wallet "box" on Dashboard */ public function testDashboard(): void { // Test that the page requires authentication $this->browse(function (Browser $browser) { $browser->visit(new Home()) ->submitLogon('john@kolab.org', 'simple123', true) ->on(new Dashboard()) - ->assertSeeIn('@links .link-wallet .name', 'Wallet') + ->assertSeeIn('@links .link-wallet svg + span', 'Wallet') ->assertSeeIn('@links .link-wallet .badge', '-12,34 CHF'); }); } /** * Test wallet page * * @depends testDashboard */ public function testWallet(): void { $this->browse(function (Browser $browser) { $browser->click('@links .link-wallet') ->on(new WalletPage()) ->assertSeeIn('#wallet .card-title', 'Account balance -12,34 CHF') ->assertSeeIn('#wallet .card-title .text-danger', '-12,34 CHF') ->assertSeeIn('#wallet .card-text', 'You are out of credit'); }); } /** * Test Receipts tab */ public function testReceipts(): void { $user = $this->getTestUser('wallets-controller@kolabnow.com', ['password' => 'simple123']); $wallet = $user->wallets()->first(); $wallet->payments()->delete(); // Log out John and log in the test user $this->browse(function (Browser $browser) { $browser->visit('/logout') ->waitForLocation('/login') ->on(new Home()) ->submitLogon('wallets-controller@kolabnow.com', 'simple123', true); }); // Assert Receipts tab content when there's no receipts available $this->browse(function (Browser $browser) { $browser->on(new Dashboard()) ->click('@links .link-wallet') ->on(new WalletPage()) ->assertSeeIn('#wallet .card-title', 'Account balance 0,00 CHF') ->assertSeeIn('#wallet .card-title .text-success', '0,00 CHF') ->assertSeeIn('#wallet .card-text', 'You are in your free trial period.') ->assertSeeIn('@nav #tab-receipts', 'Receipts') ->with('@receipts-tab', function (Browser $browser) { $browser->waitUntilMissing('.app-loader') ->assertSeeIn('p', 'There are no receipts for payments') ->assertDontSeeIn('p', 'Here you can download') ->assertMissing('select') ->assertMissing('button'); }); }); // Create some sample payments $receipts = []; $date = Carbon::create(intval(date('Y')) - 1, 3, 30); $payment = Payment::create([ 'id' => 'AAA1', 'status' => PaymentProvider::STATUS_PAID, 'type' => PaymentProvider::TYPE_ONEOFF, 'description' => 'Paid in March', 'wallet_id' => $wallet->id, 'provider' => 'stripe', 'amount' => 1111, 'currency_amount' => 1111, 'currency' => 'CHF', ]); $payment->updated_at = $date; $payment->save(); $receipts[] = $date->format('Y-m'); $date = Carbon::create(intval(date('Y')) - 1, 4, 30); $payment = Payment::create([ 'id' => 'AAA2', 'status' => PaymentProvider::STATUS_PAID, 'type' => PaymentProvider::TYPE_ONEOFF, 'description' => 'Paid in April', 'wallet_id' => $wallet->id, 'provider' => 'stripe', 'amount' => 1111, 'currency_amount' => 1111, 'currency' => 'CHF', ]); $payment->updated_at = $date; $payment->save(); $receipts[] = $date->format('Y-m'); // Assert Receipts tab with receipts available $this->browse(function (Browser $browser) use ($receipts) { $browser->refresh() ->on(new WalletPage()) ->assertSeeIn('@nav #tab-receipts', 'Receipts') ->with('@receipts-tab', function (Browser $browser) use ($receipts) { $browser->waitUntilMissing('.app-loader') ->assertDontSeeIn('p', 'There are no receipts for payments') ->assertSeeIn('p', 'Here you can download') ->assertSeeIn('button', 'Download') ->assertElementsCount('select > option', 2) ->assertSeeIn('select > option:nth-child(1)', $receipts[1]) ->assertSeeIn('select > option:nth-child(2)', $receipts[0]); // Download a receipt file $browser->select('select', $receipts[0]) ->click('button') ->pause(2000); $files = glob(__DIR__ . '/downloads/*.pdf'); $filename = pathinfo($files[0], PATHINFO_BASENAME); $this->assertTrue(strpos($filename, $receipts[0]) !== false); $content = $browser->readDownloadedFile($filename, 0); $this->assertStringStartsWith("%PDF-1.", $content); $browser->removeDownloadedFile($filename); }); }); } /** * Test History tab */ public function testHistory(): void { $user = $this->getTestUser('wallets-controller@kolabnow.com', ['password' => 'simple123']); // Log out John and log in the test user $this->browse(function (Browser $browser) { $browser->visit('/logout') ->waitForLocation('/login') ->on(new Home()) ->submitLogon('wallets-controller@kolabnow.com', 'simple123', true); }); $package_kolab = \App\Package::where('title', 'kolab')->first(); $user->assignPackage($package_kolab); $wallet = $user->wallets()->first(); // Create some sample transactions $transactions = $this->createTestTransactions($wallet); $transactions = array_reverse($transactions); $pages = array_chunk($transactions, 10 /* page size*/); $this->browse(function (Browser $browser) use ($pages) { $browser->on(new Dashboard()) ->click('@links .link-wallet') ->on(new WalletPage()) ->assertSeeIn('@nav #tab-history', 'History') ->click('@nav #tab-history') ->with('@history-tab', function (Browser $browser) use ($pages) { $browser->waitUntilMissing('.app-loader') ->assertElementsCount('table tbody tr', 10) ->assertMissing('table td.email') ->assertSeeIn('.more-loader button', 'Load more'); foreach ($pages[0] as $idx => $transaction) { $selector = 'table tbody tr:nth-child(' . ($idx + 1) . ')'; $priceStyle = $transaction->type == Transaction::WALLET_AWARD ? 'text-success' : 'text-danger'; $browser->assertSeeIn("$selector td.description", $transaction->shortDescription()) ->assertMissing("$selector td.selection button") ->assertVisible("$selector td.price.{$priceStyle}"); // TODO: Test more transaction details } // Load the next page $browser->click('.more-loader button') ->waitUntilMissing('.app-loader') ->assertElementsCount('table tbody tr', 12) ->assertMissing('.more-loader button'); $debitEntry = null; foreach ($pages[1] as $idx => $transaction) { $selector = 'table tbody tr:nth-child(' . ($idx + 1 + 10) . ')'; $priceStyle = $transaction->type == Transaction::WALLET_CREDIT ? 'text-success' : 'text-danger'; $browser->assertSeeIn("$selector td.description", $transaction->shortDescription()); if ($transaction->type == Transaction::WALLET_DEBIT) { $debitEntry = $selector; } else { $browser->assertMissing("$selector td.selection button"); } } // Load sub-transactions $browser->click("$debitEntry td.selection button") ->waitUntilMissing('.app-loader') ->assertElementsCount("$debitEntry td.description ul li", 2) ->assertMissing("$debitEntry td.selection button"); }); }); } /** * Test that non-controller user has no access to wallet */ public function testAccessDenied(): void { $this->browse(function (Browser $browser) { $browser->visit('/logout') ->on(new Home()) ->submitLogon('jack@kolab.org', 'simple123', true) ->on(new Dashboard()) ->assertMissing('@links .link-wallet') ->visit('/wallet') ->assertErrorPage(403, "Only account owners can access a wallet."); }); } }