diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index cc6b173..ba72235 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -121,12 +121,16 @@ export default defineConfig({ { text: '🛠️ Expanding PowerGrid', items: [ - { text: 'Perfomance Monitoring', link: '/expanding-powergrid/performance-monitoring' }, + { text: 'Performance Monitoring', link: '/expanding-powergrid/performance-monitoring' }, { text: 'Publish Views', link: '/expanding-powergrid/publish-views' }, { text: 'Custom Theme', link: '/expanding-powergrid/custom-theme' }, ], collapsed: false }, + { + text: 'Testing', + link: '/testing/index.md' + } ], socialLinks: [ diff --git a/docs/expanding-powergrid/custom-theme.md b/docs/expanding-powergrid/custom-theme.md index 4cd6d2e..de64de4 100644 --- a/docs/expanding-powergrid/custom-theme.md +++ b/docs/expanding-powergrid/custom-theme.md @@ -30,117 +30,155 @@ The example below modifies the Tailwind theme: namespace App\PowerGridThemes; use \PowerComponents\LivewirePowerGrid\Themes\Tailwind; -use \PowerComponents\LivewirePowerGrid\Themes\Theme; -use PowerComponents\LivewirePowerGrid\Themes\Components\{Actions, Checkbox, ClickToCopy, Cols, Editable, FilterBoolean, FilterDatePicker, FilterInputText, FilterMultiSelect, FilterNumber, FilterSelect, Footer, Table}; class BigFonts extends Tailwind { public string $name = 'tailwind'; - public function table(): Table + public function table(): array { - return Theme::table('rounded-lg min-w-full border border-slate-200 dark:bg-slate-600 dark:border-slate-500') - ->div('my-3 overflow-x-auto bg-white shadow-lg rounded-lg overflow-y-auto relative') - ->thead('shadow-sm bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-500') - ->thAction('!font-bold text-right') - ->tdAction('') - ->tr('') - ->trFilters('bg-white shadow-sm dark:bg-slate-700') - ->th('font-semibold px-2 pr-4 py-3 text-left text-xl font-semibold text-slate-700 tracking-wider whitespace-nowrap dark:text-slate-300') - ->tbody('text-slate-800') - ->trBody('border border-slate-100 dark:border-slate-400 hover:bg-slate-50 dark:bg-slate-700 dark:odd:bg-slate-800 dark:odd:hover:bg-slate-900 dark:hover:bg-slate-700') - ->tdBody('px-3 py-2 whitespace-nowrap dark:text-slate-200') - ->tdBodyTotalColumns('px-3 py-2 whitespace-nowrap dark:text-slate-200 text-sm text-slate-600 text-right space-y-2'); + return [ + 'layout' => [ + 'base' => 'p-3 align-middle inline-block min-w-full w-full sm:px-6 lg:px-8', + 'div' => 'rounded-t-lg relative border-x border-t border-pg-primary-200 dark:bg-pg-primary-700 dark:border-pg-primary-600', + 'table' => 'min-w-full dark:!bg-primary-800', + 'container' => '-my-2 overflow-x-auto sm:-mx-3 lg:-mx-8', + 'actions' => 'flex gap-2', + ], + + 'header' => [ + 'thead' => 'shadow-sm rounded-t-lg bg-pg-primary-100 dark:bg-pg-primary-900', + 'tr' => '', + 'th' => 'font-extrabold px-3 py-3 text-left text-xs text-pg-primary-700 tracking-wider whitespace-nowrap dark:text-pg-primary-300', + 'thAction' => '!font-bold', + ], + + 'body' => [ + 'tbody' => 'text-pg-primary-800', + 'tbodyEmpty' => '', + 'tr' => 'border-b border-pg-primary-100 dark:border-pg-primary-600 hover:bg-pg-primary-50 dark:bg-pg-primary-800 dark:hover:bg-pg-primary-700', + 'td' => 'px-3 py-2 whitespace-nowrap dark:text-pg-primary-200', + 'tdEmpty' => 'p-2 whitespace-nowrap dark:text-pg-primary-200', + 'tdSummarize' => 'p-2 whitespace-nowrap dark:text-pg-primary-200 text-sm text-pg-primary-600 text-right space-y-2', + 'trSummarize' => '', + 'tdFilters' => '', + 'trFilters' => '', + 'tdActionsContainer' => 'flex gap-2', + ], + ]; } - public function footer(): Footer + public function footer(): array { - return Theme::footer() - ->view($this->root() . '.footer') - ->select('block appearance-none bg-slate-50 border border-slate-300 text-slate-700 py-2 px-3 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-slate-500 dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500'); + return [ + 'view' => $this->root() . '.footer', + 'select' => 'appearance-none !bg-none focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 rounded-md border-0 bg-transparent py-1.5 px-4 pr-7 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-auto', + ]; } - public function actions(): Actions + public function cols(): array { - return Theme::actions() - ->headerBtn('block w-full bg-slate-50 text-slate-700 border border-slate-200 rounded py-2 px-3 leading-tight focus:outline-none focus:bg-white focus:border-slate-600 dark:border-slate-500 dark:bg-slate-600 2xl:dark:placeholder-slate-300 dark:text-slate-200 dark:text-slate-300') - ->rowsBtn('focus:outline-none text-sm py-2.5 px-5 rounded border'); + return [ + 'div' => 'select-none flex items-center gap-1', + ]; } - public function cols(): Cols + public function editable(): array { - return Theme::cols() - ->div('') - ->clearFilter('', ''); + return [ + 'view' => $this->root() . '.editable', + 'input' => 'focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 w-full rounded-md border-0 bg-transparent py-1.5 px-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-full', + ]; } - public function editable(): Editable + public function toggleable(): array { - return Theme::editable() - ->view($this->root() . '.editable') - ->span('flex justify-between') - ->input('dark:bg-slate-700 bg-slate-50 text-black-700 border border-slate-400 rounded py-2 px-3 leading-tight focus:outline-none focus:bg-white focus:border-slate-500 dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500 p-2'); + return [ + 'view' => $this->root() . '.toggleable', + ]; } - public function clickToCopy(): ClickToCopy + public function checkbox(): array { - return Theme::clickToCopy() - ->span('flex justify-between'); + return [ + 'th' => 'px-6 py-3 text-left text-xs font-medium text-pg-primary-500 tracking-wider', + 'base' => '', + 'label' => 'flex items-center space-x-3', + 'input' => 'form-checkbox dark:border-dark-600 border-1 dark:bg-dark-800 rounded border-gray-300 bg-white transition duration-100 ease-in-out h-4 w-4 text-primary-500 focus:ring-primary-500 dark:ring-offset-dark-900', + ]; } - public function checkbox(): Checkbox + public function radio(): array { - return Theme::checkbox() - ->th('px-6 py-3 text-left text-xs font-medium text-slate-500 tracking-wider') - ->label('flex items-center space-x-3') - ->input('h-4 w-4'); + return [ + 'th' => 'px-6 py-3 text-left text-xs font-medium text-pg-primary-500 tracking-wider', + 'base' => '', + 'label' => 'flex items-center space-x-3', + 'input' => 'form-radio rounded-full transition ease-in-out duration-100', + ]; } - public function filterBoolean(): FilterBoolean + public function filterBoolean(): array { - return Theme::filterBoolean() - ->view($this->root() . '.filters.boolean') - ->base('min-w-[5rem]') - ->select('appearance-none block mt-1 mb-1 bg-white border border-slate-300 text-slate-700 py-2 px-3 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-slate-500 w-full active dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500', 'max-width: 370px'); + return [ + 'view' => $this->root() . '.filters.boolean', + 'base' => 'min-w-[5rem]', + 'select' => 'appearance-none !bg-none focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 w-full rounded-md border-0 bg-transparent py-1.5 px-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-full', + ]; } - public function filterDatePicker(): FilterDatePicker + public function filterDatePicker(): array { - return Theme::filterDatePicker() - ->base('p-2') - ->view($this->root() . '.filters.date-picker') - ->input('flatpickr flatpickr-input block my-1 bg-white border border-slate-300 text-slate-700 py-2 px-3 rounded leading-tight focus:outline-none focus:bg-white focus:border-slate-500 w-full active dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500', 'min-width: 12rem'); + return [ + 'base' => '', + 'view' => $this->root() . '.filters.date-picker', + 'input' => 'flatpickr flatpickr-input focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 w-full rounded-md border-0 bg-transparent py-1.5 px-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-auto', + ]; } - public function filterMultiSelect(): FilterMultiSelect + public function filterMultiSelect(): array { - return Theme::filterMultiSelect() - ->base('inline-block relative w-full p-2 min-w-[180px]') - ->view($this->root() . '.filters.multi-select'); + return [ + 'view' => $this->root() . '.filters.multi-select', + 'base' => 'inline-block relative w-full', + 'select' => 'mt-1', + ]; } - public function filterNumber(): FilterNumber + public function filterNumber(): array { - return Theme::filterNumber() - ->view($this->root() . '.filters.number') - ->input('block bg-white border border-slate-300 text-slate-700 py-2 px-3 rounded leading-tight focus:outline-none focus:bg-white focus:border-slate-500 w-full active dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500 min-w-[5rem]'); + return [ + 'view' => $this->root() . '.filters.number', + 'input' => 'w-full min-w-[5rem] block focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 rounded-md border-0 bg-transparent py-1.5 pl-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6', + ]; } - public function filterSelect(): FilterSelect + public function filterSelect(): array { - return Theme::filterSelect() - ->view($this->root() . '.filters.select') - ->base('min-w-[9.5rem]') - ->select('appearance-none block bg-white border border-slate-300 text-slate-700 py-2 px-3 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-slate-500 w-full active dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500'); + return [ + 'view' => $this->root() . '.filters.select', + 'base' => '', + 'select' => 'appearance-none !bg-none focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 rounded-md border-0 bg-transparent py-1.5 px-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-full', + ]; } - public function filterInputText(): FilterInputText + public function filterInputText(): array { - return Theme::filterInputText() - ->view($this->root() . '.filters.input-text') - ->base('min-w-[9.5rem]') - ->select('appearance-none block bg-white border border-slate-300 text-slate-700 py-2 px-3 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-slate-500 w-full active dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500') - ->input('w-full block bg-white text-slate-700 border border-slate-300 rounded py-2 px-3 leading-tight focus:outline-none focus:bg-white focus:border-slate-500 dark:bg-slate-600 dark:text-slate-200 dark:placeholder-slate-200 dark:border-slate-500'); + return [ + 'view' => $this->root() . '.filters.input-text', + 'base' => 'min-w-[9.5rem]', + 'select' => 'appearance-none !bg-none focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 w-full rounded-md border-0 bg-transparent py-1.5 px-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-full', + 'input' => 'focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 w-full rounded-md border-0 bg-transparent py-1.5 px-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-full', + ]; + } + + public function searchBox(): array + { + return [ + 'input' => 'focus:ring-primary-600 focus-within:focus:ring-primary-600 focus-within:ring-primary-600 dark:focus-within:ring-primary-600 flex items-center rounded-md ring-1 transition focus-within:ring-2 dark:ring-pg-primary-600 dark:text-pg-primary-300 text-gray-600 ring-gray-300 dark:bg-pg-primary-800 bg-white dark:placeholder-pg-primary-400 w-full rounded-md border-0 bg-transparent py-1.5 px-2 ring-0 placeholder:text-gray-400 focus:outline-none sm:text-sm sm:leading-6 w-full pl-8', + 'iconClose' => 'text-pg-primary-400 dark:text-pg-primary-200', + 'iconSearch' => 'text-pg-primary-300 mr-2 w-5 h-5 dark:text-pg-primary-200', + ]; } } ``` @@ -161,16 +199,15 @@ class BigFonts extends Tailwind ``` - - Alternatively, you can apply your custom theme only in specific PowerGrid Tables: -Just modify the `template()` to return your new theme class. +Just modify the `customThemeClass()` to return your new theme class. ```php class DishTable extends PowerGridComponent { - public function template(): ?string + public function customThemeClass(): ?string { return \App\PowerGridThemes\BigFonts::class; - }``` + } +``` diff --git a/docs/expanding-powergrid/performance-monitoring.md b/docs/expanding-powergrid/performance-monitoring.md index cd29e8a..43399cd 100644 --- a/docs/expanding-powergrid/performance-monitoring.md +++ b/docs/expanding-powergrid/performance-monitoring.md @@ -22,6 +22,12 @@ You can access the results of PowerGrid Performance Measurement using Laravel Ev To retrieve the PowerGrid Performance Measurement via event, you must add an event listener to `PowerGridPerformanceData::class` in your Application Service Provider. +#### First, add the PowerGrid table component you want to listen to: + +```php +public bool $measurePerformance = true; +``` + You can use several options to access the data sent with the event. The most straightforward approach is using [Laravel Logs](https://laravel.com/docs/logging). The example below uses [LaraDumps](https://laradumps.dev) to capture and display the event data. LaraDumps is a free, open-source debug tool that helps you assess your Component performance in a convenient way. diff --git a/docs/release-notes-and-upgrade/upgrade-guide.md b/docs/release-notes-and-upgrade/upgrade-guide.md index 7492339..c0c3504 100644 --- a/docs/release-notes-and-upgrade/upgrade-guide.md +++ b/docs/release-notes-and-upgrade/upgrade-guide.md @@ -75,7 +75,7 @@ PowerGrid::detail(); // [!code ++] ```php public function template(); // [!code --] -public function customTemplateClass(); // [!code ++] +public function customThemeClass(); // [!code ++] ``` diff --git a/docs/testing/index.md b/docs/testing/index.md new file mode 100644 index 0000000..3b5aa9d --- /dev/null +++ b/docs/testing/index.md @@ -0,0 +1,128 @@ +# Testing + +Create a test component inside the `tests` folder in your application, for example `tests/Feature/Livewire/UsersTableTest.php` + +You should follow the same concept defined in the [livewire documentation](https://livewire.laravel.com/docs/testing), and then you will be able to use other native assertions in PowerGrid: + +### Testing Actions Buttons + +Reference: [action button](.././table-features/button-class.html) + +PowerGrid provides a simple testing utility to ensure that a button's structure complies with standards for later rendering with JavaScript. + +See this example. + +Component: **UsersTable** +```php +class UsersTable extends PowerGridComponent { + // ... + + public function datasource(): Collection + { + return collect([ + [ + 'id' => 29, + 'name' => 'Luan', + 'balance' => 241.86, + 'is_online' => true, + 'created_at' => '2023-01-01 00:00:00', + ], + [ + 'id' => 57, + 'name' => 'Daniel', + 'balance' => 166.51, + 'is_online' => true, + 'created_at' => '2023-02-02 00:00:00', + ], + [ + 'id' => 93, + 'name' => 'Claudio', + 'balance' => 219.01, + 'is_online' => false, + 'created_at' => '2023-03-03 00:00:00', + ], + [ + 'id' => 104, + 'name' => 'Vitor', + 'balance' => 44.28, + 'is_online' => true, + 'created_at' => '2023-04-04 00:00:00', + ], + ]); + } + + public function actions($row): array + { + return [ + Button::add('view') + ->icon('default-eye', [ + 'class' => '!text-green-500', + ]) + ->slot('View') + ->class('text-slate-500 flex gap-2 hover:text-slate-700 hover:bg-slate-100 font-bold p-1 px-2 rounded') + ->dispatch('clickToEdit', ['dishId' => $row?->id, 'dishName' => $row?->name]), + ]; + } +}; +``` + +Component Test: **UsersTableTest.php** + +### assertHasAction + +```php{3} +it('should be able to see "view" action', function (string $component, object $params) { + livewire(UsersTableTest::class) + ->assertHasAction('view') + // other assertions; +}) +``` + +### assertActionContainsAttribute + +`class` attribute example: + +```php{3-6} +it('should be able to see "class" attribute in "view" action', function (string $component, object $params) { + livewire(UsersTableTest::class) + ->assertActionContainsAttribute( + action: 'view', + attribute: 'class', + expected: 'flex gap-2 hover:text-slate-700' + ) + // other assertions; +}) +``` + +### assertActionContainsAttribute + +`wire:click` attribute example: + +```php{3-7} +it('should be able to see "wire:click" in "view" action', function (string $component, object $params) { + livewire(UsersTableTest::class) + ->assertActionContainsAttribute( + action: 'view', + attribute: 'wire:click', + expected: 'clickToEdit', + expectedParams: ['dishId' => 29, 'dishName' => 'Luan'] + ) + // other assertions; +}) +``` + +### assertActionHasIcon + +`icon` example: + +```php{3-6} +it('should be able to see "wire:click" in "view" action', function (string $component, object $params) { + livewire(UsersTableTest::class) + ->assertActionContainsAttribute( + action: 'view', + icon: 'default-eye', + expected: '!text-green-500' + ) + // other assertions; +}) +```