Add membership fee system with disability discount and fix document permissions
Features: - Implement two fee types: entrance fee and annual fee (both NT$1,000) - Add 50% discount for disability certificate holders - Add disability certificate upload in member profile - Integrate disability verification into cashier approval workflow - Add membership fee settings in system admin Document permissions: - Fix hard-coded role logic in Document model - Use permission-based authorization instead of role checks Additional features: - Add announcements, general ledger, and trial balance modules - Add income management and accounting entries - Add comprehensive test suite with factories - Update UI translations to Traditional Chinese 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
201
tests/Browser/AdminDashboardBrowserTest.php
Normal file
201
tests/Browser/AdminDashboardBrowserTest.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\FinanceDocument;
|
||||
use App\Models\Issue;
|
||||
use App\Models\Member;
|
||||
use App\Models\MembershipPayment;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* Admin Dashboard Browser Tests
|
||||
*
|
||||
* Tests the admin dashboard user interface and user experience.
|
||||
*/
|
||||
class AdminDashboardBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an admin user for testing
|
||||
*/
|
||||
protected function createAdminUser(): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email' => 'admin@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
|
||||
$user->assignRole('admin');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin can view dashboard
|
||||
*/
|
||||
public function test_admin_can_view_dashboard(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->assertSee('管理儀表板');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test dashboard shows statistics
|
||||
*/
|
||||
public function test_dashboard_shows_statistics(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
// Create some data
|
||||
Member::factory()->count(5)->create();
|
||||
Issue::factory()->count(3)->create(['created_by_user_id' => $admin->id]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->assertPresent('.stats-card');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin can navigate to members page
|
||||
*/
|
||||
public function test_admin_can_navigate_to_members_page(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->clickLink('會員管理')
|
||||
->assertPathIs('/admin/members');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin can navigate to finance page
|
||||
*/
|
||||
public function test_admin_can_navigate_to_finance_page(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->clickLink('財務管理')
|
||||
->assertPathIs('/admin/finance*');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin can navigate to issues page
|
||||
*/
|
||||
public function test_admin_can_navigate_to_issues_page(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->clickLink('議題追蹤')
|
||||
->assertPathIs('/admin/issues');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sidebar navigation works
|
||||
*/
|
||||
public function test_sidebar_navigation_works(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->assertPresent('.sidebar')
|
||||
->assertPresent('.nav-link');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test pending approvals widget
|
||||
*/
|
||||
public function test_pending_approvals_widget(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
MembershipPayment::factory()->count(3)->create([
|
||||
'status' => MembershipPayment::STATUS_PENDING,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->assertSee('待審核');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test recent activity section
|
||||
*/
|
||||
public function test_recent_activity_section(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.dashboard'))
|
||||
->assertPresent('.activity-feed');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin can search members
|
||||
*/
|
||||
public function test_admin_can_search_members(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
Member::factory()->create(['full_name' => '測試搜尋會員']);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.members.index'))
|
||||
->type('search', '測試搜尋')
|
||||
->press('搜尋')
|
||||
->assertSee('測試搜尋會員');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test responsive sidebar collapse
|
||||
*/
|
||||
public function test_responsive_sidebar_collapse(): void
|
||||
{
|
||||
$admin = $this->createAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
// Mobile view
|
||||
$browser->loginAs($admin)
|
||||
->resize(375, 667)
|
||||
->visit(route('admin.dashboard'))
|
||||
->assertPresent('.sidebar-toggle');
|
||||
});
|
||||
}
|
||||
}
|
||||
254
tests/Browser/DocumentManagementBrowserTest.php
Normal file
254
tests/Browser/DocumentManagementBrowserTest.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\DocumentCategory;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* Document Management Browser Tests
|
||||
*
|
||||
* Tests the document management user interface and user experience.
|
||||
*/
|
||||
class DocumentManagementBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Storage::fake('local');
|
||||
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an admin user
|
||||
*/
|
||||
protected function createAdmin(): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email' => 'admin@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
$user->assignRole('admin');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test document list page loads
|
||||
*/
|
||||
public function test_document_list_page_loads(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.index'))
|
||||
->assertSee('文件管理');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can upload document
|
||||
*/
|
||||
public function test_can_upload_document(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.create'))
|
||||
->assertSee('上傳文件')
|
||||
->assertPresent('input[name="title"]')
|
||||
->assertPresent('input[type="file"]');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test document categories are displayed
|
||||
*/
|
||||
public function test_document_categories_are_displayed(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
DocumentCategory::factory()->create(['name' => '會議紀錄']);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.index'))
|
||||
->assertSee('會議紀錄');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can filter by category
|
||||
*/
|
||||
public function test_can_filter_by_category(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$category1 = DocumentCategory::factory()->create(['name' => '類別A']);
|
||||
$category2 = DocumentCategory::factory()->create(['name' => '類別B']);
|
||||
|
||||
Document::factory()->create([
|
||||
'title' => '文件A',
|
||||
'category_id' => $category1->id,
|
||||
'uploaded_by' => $admin->id,
|
||||
]);
|
||||
|
||||
Document::factory()->create([
|
||||
'title' => '文件B',
|
||||
'category_id' => $category2->id,
|
||||
'uploaded_by' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $category1) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.index'))
|
||||
->select('category_id', $category1->id)
|
||||
->press('篩選')
|
||||
->assertSee('文件A')
|
||||
->assertDontSee('文件B');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can search documents
|
||||
*/
|
||||
public function test_can_search_documents(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
Document::factory()->create([
|
||||
'title' => '特殊搜尋文件',
|
||||
'uploaded_by' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.index'))
|
||||
->type('search', '特殊搜尋')
|
||||
->press('搜尋')
|
||||
->assertSee('特殊搜尋文件');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can view document details
|
||||
*/
|
||||
public function test_can_view_document_details(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$document = Document::factory()->create([
|
||||
'title' => '詳細資料文件',
|
||||
'description' => '這是文件描述',
|
||||
'uploaded_by' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $document) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.show', $document))
|
||||
->assertSee('詳細資料文件')
|
||||
->assertSee('這是文件描述');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download button is present
|
||||
*/
|
||||
public function test_download_button_is_present(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$document = Document::factory()->create([
|
||||
'uploaded_by' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $document) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.show', $document))
|
||||
->assertSee('下載');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can delete document
|
||||
*/
|
||||
public function test_can_delete_document(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$document = Document::factory()->create([
|
||||
'title' => '待刪除文件',
|
||||
'uploaded_by' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $document) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.show', $document))
|
||||
->press('刪除')
|
||||
->acceptDialog()
|
||||
->waitForLocation('/documents')
|
||||
->assertDontSee('待刪除文件');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test file size is displayed
|
||||
*/
|
||||
public function test_file_size_is_displayed(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$document = Document::factory()->create([
|
||||
'uploaded_by' => $admin->id,
|
||||
'file_size' => 1024 * 1024, // 1MB
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $document) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.show', $document))
|
||||
->assertSee('MB');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test upload date is displayed
|
||||
*/
|
||||
public function test_upload_date_is_displayed(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$document = Document::factory()->create([
|
||||
'uploaded_by' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $document) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.show', $document))
|
||||
->assertPresent('.upload-date');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test grid and list view toggle
|
||||
*/
|
||||
public function test_grid_and_list_view_toggle(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('documents.index'))
|
||||
->assertPresent('.view-toggle');
|
||||
});
|
||||
}
|
||||
}
|
||||
21
tests/Browser/ExampleTest.php
Normal file
21
tests/Browser/ExampleTest.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class ExampleTest extends DuskTestCase
|
||||
{
|
||||
/**
|
||||
* A basic browser test example.
|
||||
*/
|
||||
public function testBasicExample(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit('/')
|
||||
->assertSee('Laravel');
|
||||
});
|
||||
}
|
||||
}
|
||||
243
tests/Browser/FinanceWorkflowBrowserTest.php
Normal file
243
tests/Browser/FinanceWorkflowBrowserTest.php
Normal file
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\FinanceDocument;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* Finance Workflow Browser Tests
|
||||
*
|
||||
* Tests the finance workflow user interface and user experience.
|
||||
*/
|
||||
class FinanceWorkflowBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a cashier user
|
||||
*/
|
||||
protected function createCashier(): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email' => 'cashier@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
$user->assignRole('finance_cashier');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an accountant user
|
||||
*/
|
||||
protected function createAccountant(): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email' => 'accountant@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
$user->assignRole('finance_accountant');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test finance dashboard loads
|
||||
*/
|
||||
public function test_finance_dashboard_loads(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance.index'))
|
||||
->assertSee('財務管理');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can create finance document
|
||||
*/
|
||||
public function test_can_create_finance_document(): void
|
||||
{
|
||||
$accountant = $this->createAccountant();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($accountant) {
|
||||
$browser->loginAs($accountant)
|
||||
->visit(route('admin.finance-documents.create'))
|
||||
->assertSee('新增財務單據')
|
||||
->assertPresent('input[name="title"]')
|
||||
->assertPresent('input[name="amount"]');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test finance document list shows status
|
||||
*/
|
||||
public function test_finance_document_list_shows_status(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
'title' => '測試單據',
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.index'))
|
||||
->assertSee('測試單據')
|
||||
->assertSee('待審核');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cashier can see approve button
|
||||
*/
|
||||
public function test_cashier_can_see_approve_button(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$document = FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.show', $document))
|
||||
->assertSee('核准');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cashier can see reject button
|
||||
*/
|
||||
public function test_cashier_can_see_reject_button(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$document = FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.show', $document))
|
||||
->assertSee('退回');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test approval requires confirmation
|
||||
*/
|
||||
public function test_approval_requires_confirmation(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$document = FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.show', $document))
|
||||
->press('核准')
|
||||
->assertDialogOpened();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test document amount is formatted
|
||||
*/
|
||||
public function test_document_amount_is_formatted(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
'amount' => 15000,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.index'))
|
||||
->assertSee('15,000');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filter by status works
|
||||
*/
|
||||
public function test_filter_by_status_works(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
'title' => '待審核單據',
|
||||
]);
|
||||
|
||||
FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
||||
'title' => '已核准單據',
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.index'))
|
||||
->select('status', FinanceDocument::STATUS_PENDING)
|
||||
->press('篩選')
|
||||
->assertSee('待審核單據')
|
||||
->assertDontSee('已核准單據');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test rejection requires reason
|
||||
*/
|
||||
public function test_rejection_requires_reason(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$document = FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_PENDING,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.show', $document))
|
||||
->press('退回')
|
||||
->waitFor('.modal')
|
||||
->assertSee('退回原因');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test document history is visible
|
||||
*/
|
||||
public function test_document_history_is_visible(): void
|
||||
{
|
||||
$cashier = $this->createCashier();
|
||||
|
||||
$document = FinanceDocument::factory()->create([
|
||||
'status' => FinanceDocument::STATUS_APPROVED_CASHIER,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($cashier, $document) {
|
||||
$browser->loginAs($cashier)
|
||||
->visit(route('admin.finance-documents.show', $document))
|
||||
->assertSee('審核歷程');
|
||||
});
|
||||
}
|
||||
}
|
||||
254
tests/Browser/IssueTrackerBrowserTest.php
Normal file
254
tests/Browser/IssueTrackerBrowserTest.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\Issue;
|
||||
use App\Models\IssueLabel;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* Issue Tracker Browser Tests
|
||||
*
|
||||
* Tests the issue tracking user interface and user experience.
|
||||
*/
|
||||
class IssueTrackerBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an admin user
|
||||
*/
|
||||
protected function createAdmin(): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email' => 'admin@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
$user->assignRole('admin');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test issue list page loads
|
||||
*/
|
||||
public function test_issue_list_page_loads(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.index'))
|
||||
->assertSee('議題列表');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can create new issue
|
||||
*/
|
||||
public function test_can_create_new_issue(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.create'))
|
||||
->assertSee('新增議題')
|
||||
->type('title', '測試議題標題')
|
||||
->type('description', '這是測試議題描述')
|
||||
->select('priority', Issue::PRIORITY_HIGH)
|
||||
->press('建立')
|
||||
->waitForLocation('/admin/issues/*')
|
||||
->assertSee('測試議題標題');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test issue shows status badge
|
||||
*/
|
||||
public function test_issue_shows_status_badge(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
'title' => '狀態測試議題',
|
||||
'status' => Issue::STATUS_IN_PROGRESS,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.index'))
|
||||
->assertSee('狀態測試議題')
|
||||
->assertSee('進行中');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can assign issue
|
||||
*/
|
||||
public function test_can_assign_issue(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
$assignee = User::factory()->create(['name' => '被指派者']);
|
||||
|
||||
$issue = Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
'assignee_id' => null,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $issue) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.show', $issue))
|
||||
->assertSee('指派')
|
||||
->select('assignee_id', '被指派者');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can add comment
|
||||
*/
|
||||
public function test_can_add_comment(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$issue = Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $issue) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.show', $issue))
|
||||
->type('content', '這是一則測試留言')
|
||||
->press('新增留言')
|
||||
->waitForText('這是一則測試留言')
|
||||
->assertSee('這是一則測試留言');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can change issue status
|
||||
*/
|
||||
public function test_can_change_issue_status(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$issue = Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
'status' => Issue::STATUS_NEW,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $issue) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.show', $issue))
|
||||
->select('status', Issue::STATUS_IN_PROGRESS)
|
||||
->press('更新狀態')
|
||||
->waitForText('進行中');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can filter by status
|
||||
*/
|
||||
public function test_can_filter_by_status(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
'title' => '新議題',
|
||||
'status' => Issue::STATUS_NEW,
|
||||
]);
|
||||
|
||||
Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
'title' => '已關閉議題',
|
||||
'status' => Issue::STATUS_CLOSED,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.index'))
|
||||
->select('status', Issue::STATUS_NEW)
|
||||
->press('篩選')
|
||||
->assertSee('新議題')
|
||||
->assertDontSee('已關閉議題');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can filter by priority
|
||||
*/
|
||||
public function test_can_filter_by_priority(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
'title' => '高優先議題',
|
||||
'priority' => Issue::PRIORITY_HIGH,
|
||||
]);
|
||||
|
||||
Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
'title' => '低優先議題',
|
||||
'priority' => Issue::PRIORITY_LOW,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.index'))
|
||||
->select('priority', Issue::PRIORITY_HIGH)
|
||||
->press('篩選')
|
||||
->assertSee('高優先議題')
|
||||
->assertDontSee('低優先議題');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test issue number is displayed
|
||||
*/
|
||||
public function test_issue_number_is_displayed(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
|
||||
$issue = Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $issue) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.show', $issue))
|
||||
->assertSee($issue->issue_number);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can add labels to issue
|
||||
*/
|
||||
public function test_can_add_labels_to_issue(): void
|
||||
{
|
||||
$admin = $this->createAdmin();
|
||||
$label = IssueLabel::factory()->create(['name' => '緊急']);
|
||||
|
||||
$issue = Issue::factory()->create([
|
||||
'created_by_user_id' => $admin->id,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $issue) {
|
||||
$browser->loginAs($admin)
|
||||
->visit(route('admin.issues.show', $issue))
|
||||
->assertPresent('.labels-section');
|
||||
});
|
||||
}
|
||||
}
|
||||
211
tests/Browser/MemberDashboardBrowserTest.php
Normal file
211
tests/Browser/MemberDashboardBrowserTest.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\MembershipPayment;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* Member Dashboard Browser Tests
|
||||
*
|
||||
* Tests the member dashboard user interface and user experience.
|
||||
*/
|
||||
class MemberDashboardBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
|
||||
protected User $memberUser;
|
||||
|
||||
protected Member $member;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a member user for testing
|
||||
*/
|
||||
protected function createMemberUser(): User
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'email' => 'member@test.com',
|
||||
'password' => Hash::make('password'),
|
||||
]);
|
||||
|
||||
Member::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'membership_status' => Member::STATUS_ACTIVE,
|
||||
]);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member can view dashboard
|
||||
*/
|
||||
public function test_member_can_view_dashboard(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.dashboard'))
|
||||
->assertSee('會員')
|
||||
->assertSee('儀表板');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member sees membership status
|
||||
*/
|
||||
public function test_member_sees_membership_status(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.dashboard'))
|
||||
->assertSee('會員狀態');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member can navigate to payment page
|
||||
*/
|
||||
public function test_member_can_navigate_to_payment_page(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.dashboard'))
|
||||
->clickLink('繳費')
|
||||
->assertPathIs('/member/payments*');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member can view payment history
|
||||
*/
|
||||
public function test_member_can_view_payment_history(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
$member = $user->member;
|
||||
|
||||
MembershipPayment::factory()->count(3)->create([
|
||||
'member_id' => $member->id,
|
||||
'status' => MembershipPayment::STATUS_APPROVED_CHAIR,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.payments.index'))
|
||||
->assertSee('繳費紀錄');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member can submit new payment
|
||||
*/
|
||||
public function test_member_can_submit_new_payment(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.payments.create'))
|
||||
->assertSee('提交繳費')
|
||||
->assertPresent('input[name="amount"]')
|
||||
->assertPresent('input[type="file"]');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member sees pending payment status
|
||||
*/
|
||||
public function test_member_sees_pending_payment_status(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
$member = $user->member;
|
||||
|
||||
MembershipPayment::factory()->create([
|
||||
'member_id' => $member->id,
|
||||
'status' => MembershipPayment::STATUS_PENDING,
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.payments.index'))
|
||||
->assertSee('待審核');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member can update profile
|
||||
*/
|
||||
public function test_member_can_update_profile(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.profile.edit'))
|
||||
->assertPresent('input[name="phone"]')
|
||||
->assertPresent('input[name="address"]');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member dashboard shows expiry date
|
||||
*/
|
||||
public function test_member_dashboard_shows_expiry_date(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
$user->member->update([
|
||||
'membership_expires_at' => now()->addYear(),
|
||||
]);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.dashboard'))
|
||||
->assertSee('到期');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test logout functionality
|
||||
*/
|
||||
public function test_logout_functionality(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit(route('member.dashboard'))
|
||||
->click('@user-menu')
|
||||
->clickLink('登出')
|
||||
->assertPathIs('/');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test member cannot access admin pages
|
||||
*/
|
||||
public function test_member_cannot_access_admin_pages(): void
|
||||
{
|
||||
$user = $this->createMemberUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user) {
|
||||
$browser->loginAs($user)
|
||||
->visit('/admin/dashboard')
|
||||
->assertPathIsNot('/admin/dashboard');
|
||||
});
|
||||
}
|
||||
}
|
||||
167
tests/Browser/MemberRegistrationBrowserTest.php
Normal file
167
tests/Browser/MemberRegistrationBrowserTest.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* Member Registration Browser Tests
|
||||
*
|
||||
* Tests the member registration user interface and user experience.
|
||||
*/
|
||||
class MemberRegistrationBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
|
||||
/**
|
||||
* Test registration page loads correctly
|
||||
*/
|
||||
public function test_registration_page_loads_correctly(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->assertSee('會員註冊')
|
||||
->assertPresent('input[name="full_name"]')
|
||||
->assertPresent('input[name="email"]')
|
||||
->assertPresent('input[name="password"]');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test form validation messages display
|
||||
*/
|
||||
public function test_form_validation_messages_display(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->press('註冊')
|
||||
->assertSee('必填');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful registration redirects correctly
|
||||
*/
|
||||
public function test_successful_registration_redirects(): void
|
||||
{
|
||||
$this->artisan('db:seed', ['--class' => 'FinancialWorkflowPermissionsSeeder']);
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->type('full_name', '測試會員')
|
||||
->type('email', 'test@example.com')
|
||||
->type('password', 'password123')
|
||||
->type('password_confirmation', 'password123')
|
||||
->type('national_id', 'A123456789')
|
||||
->type('birthday', '1990-01-01')
|
||||
->type('phone', '0912345678')
|
||||
->type('address', '台北市信義區')
|
||||
->press('註冊')
|
||||
->waitForLocation('/member/dashboard')
|
||||
->assertPathIs('/member/dashboard');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test email field validation
|
||||
*/
|
||||
public function test_email_field_validation(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->type('email', 'invalid-email')
|
||||
->press('註冊')
|
||||
->assertSee('電子郵件');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test password confirmation validation
|
||||
*/
|
||||
public function test_password_confirmation_validation(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->type('password', 'password123')
|
||||
->type('password_confirmation', 'different')
|
||||
->press('註冊')
|
||||
->assertSee('密碼');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test national ID format validation
|
||||
*/
|
||||
public function test_national_id_format_validation(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->type('national_id', 'invalid')
|
||||
->press('註冊')
|
||||
->assertSee('身分證');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test duplicate email detection
|
||||
*/
|
||||
public function test_duplicate_email_detection(): void
|
||||
{
|
||||
User::factory()->create(['email' => 'existing@example.com']);
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->type('email', 'existing@example.com')
|
||||
->press('註冊')
|
||||
->assertSee('電子郵件');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test file upload for ID photo
|
||||
*/
|
||||
public function test_file_upload_for_id_photo(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->assertPresent('input[type="file"]');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test form preserves input on validation error
|
||||
*/
|
||||
public function test_form_preserves_input_on_validation_error(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->visit(route('register.member'))
|
||||
->type('full_name', '測試會員')
|
||||
->type('email', 'test@example.com')
|
||||
->press('註冊')
|
||||
->assertInputValue('full_name', '測試會員')
|
||||
->assertInputValue('email', 'test@example.com');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test registration form is responsive
|
||||
*/
|
||||
public function test_registration_form_is_responsive(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
// Test on mobile viewport
|
||||
$browser->resize(375, 667)
|
||||
->visit(route('register.member'))
|
||||
->assertPresent('input[name="full_name"]');
|
||||
|
||||
// Test on desktop viewport
|
||||
$browser->resize(1920, 1080)
|
||||
->visit(route('register.member'))
|
||||
->assertPresent('input[name="full_name"]');
|
||||
});
|
||||
}
|
||||
}
|
||||
36
tests/Browser/Pages/HomePage.php
Normal file
36
tests/Browser/Pages/HomePage.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser\Pages;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class HomePage extends Page
|
||||
{
|
||||
/**
|
||||
* Get the URL for the page.
|
||||
*/
|
||||
public function url(): string
|
||||
{
|
||||
return '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the browser is on the page.
|
||||
*/
|
||||
public function assert(Browser $browser): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element shortcuts for the page.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [
|
||||
'@element' => '#selector',
|
||||
];
|
||||
}
|
||||
}
|
||||
20
tests/Browser/Pages/Page.php
Normal file
20
tests/Browser/Pages/Page.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser\Pages;
|
||||
|
||||
use Laravel\Dusk\Page as BasePage;
|
||||
|
||||
abstract class Page extends BasePage
|
||||
{
|
||||
/**
|
||||
* Get the global element shortcuts for the site.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function siteElements(): array
|
||||
{
|
||||
return [
|
||||
'@element' => '#selector',
|
||||
];
|
||||
}
|
||||
}
|
||||
2
tests/Browser/console/.gitignore
vendored
Normal file
2
tests/Browser/console/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
tests/Browser/screenshots/.gitignore
vendored
Normal file
2
tests/Browser/screenshots/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
tests/Browser/source/.gitignore
vendored
Normal file
2
tests/Browser/source/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
Reference in New Issue
Block a user