mirror of
https://github.com/filamentphp/filament.git
synced 2026-08-28 18:53:05 +08:00
fix: Search and sort same relationship table (#18499)
* fix: Search and sort on same relationship table
* Update InteractsWithTableQuery.php
* add test
* Update ColumnTest.php
* Update Group.php
* grouping tests
* clean up
* fix column qualification
* dont use joins for ordering anything
* test
* fix migrations order
* phpstan and summary issues
* update tests
* fix
* fix tests
* Update GroupingTest.php
* Revert "fix"
This reverts commit 160c3e0d18.
* Update TestsSummaries.php
* fix concurrency
* Update TestsSummaries.php
* Update 0009_modify_users_table.php
* BelongsToThrough support
* nested belongstothrough
* Update ColumnTest.php
* fix snapshots
* fix sorting based on sql
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Testing Environment Configuration
|
||||
# Copy this file to .env.testing and adjust values for your local setup
|
||||
#
|
||||
# USAGE:
|
||||
# These values are automatically loaded by PHPUnit's bootstrap file.
|
||||
# Use database-specific variables to configure ports and credentials for each database.
|
||||
|
||||
# Default Database Configuration
|
||||
DB_HOST=127.0.0.1
|
||||
# DB_DATABASE is intentionally not set here - let TestCase.php defaults handle it
|
||||
# (SQLite needs ":memory:", MySQL/Postgres need "testing")
|
||||
|
||||
# PostgreSQL-specific configuration (for non-standard ports)
|
||||
# PGSQL_PORT=5432
|
||||
# PGSQL_USERNAME=postgres
|
||||
# PGSQL_PASSWORD=
|
||||
|
||||
# MySQL-specific configuration (uncomment if needed)
|
||||
# MYSQL_PORT=3306
|
||||
# MYSQL_USERNAME=root
|
||||
# MYSQL_PASSWORD=
|
||||
@@ -4,6 +4,10 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -4,6 +4,10 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -12,13 +16,32 @@ jobs:
|
||||
matrix:
|
||||
php: [8.4, 8.3, 8.2]
|
||||
laravel: [12.*, 11.*]
|
||||
db: [sqlite, mysql, pgsql]
|
||||
dependency-version: [prefer-stable]
|
||||
include:
|
||||
- laravel: 12.*
|
||||
testbench: 10.*
|
||||
- laravel: 11.*
|
||||
testbench: 9.*
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - DB:${{ matrix.db }}
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: testing
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache dependencies
|
||||
@@ -30,11 +53,29 @@ jobs:
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: mbstring, pdo, pdo_sqlite
|
||||
extensions: mbstring, pdo, pdo_sqlite, pdo_mysql, pdo_pgsql
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "filament/support" --no-interaction --no-update
|
||||
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
|
||||
- name: Wait for MySQL
|
||||
if: matrix.db == 'mysql'
|
||||
run: |
|
||||
for i in {1..60}; do
|
||||
nc -z 127.0.0.1 3306 && echo "MySQL is up" && exit 0
|
||||
echo "Waiting for MySQL ($i) ..." && sleep 2
|
||||
done
|
||||
echo "MySQL did not become ready in time" && exit 1
|
||||
- name: Wait for Postgres
|
||||
if: matrix.db == 'pgsql'
|
||||
run: |
|
||||
for i in {1..60}; do
|
||||
nc -z 127.0.0.1 5432 && echo "Postgres is up" && exit 0
|
||||
echo "Waiting for Postgres ($i) ..." && sleep 2
|
||||
done
|
||||
echo "Postgres did not become ready in time" && exit 1
|
||||
- name: Execute tests
|
||||
run: ./vendor/bin/pest
|
||||
run: ./vendor/bin/pest --configuration=phpunit.${{ matrix.db }}.xml
|
||||
env:
|
||||
DB_PASSWORD: password
|
||||
|
||||
@@ -13,3 +13,5 @@
|
||||
composer.phar
|
||||
Thumbs.db
|
||||
phpunit.xml
|
||||
.env.testing
|
||||
!phpunit.*.xml
|
||||
|
||||
+9
-1
@@ -84,9 +84,17 @@
|
||||
"pint": "pint --config pint-strict-imports.json",
|
||||
"rector": "rector",
|
||||
"test:pest": "pest --parallel",
|
||||
"test:sqlite": "pest --configuration=phpunit.sqlite.xml --parallel",
|
||||
"test:mysql": "pest --configuration=phpunit.mysql.xml",
|
||||
"test:pgsql": "pest --configuration=phpunit.pgsql.xml",
|
||||
"test:all-databases": [
|
||||
"@test:sqlite",
|
||||
"@test:mysql",
|
||||
"@test:pgsql"
|
||||
],
|
||||
"test:phpstan": "phpstan analyse",
|
||||
"test": [
|
||||
"@test:pest",
|
||||
"@test:sqlite",
|
||||
"@test:phpstan"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Services;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use InvalidArgumentException;
|
||||
use Znck\Eloquent\Relations\BelongsToThrough;
|
||||
|
||||
class RelationshipOrderer
|
||||
{
|
||||
public function buildSubquery(EloquentBuilder $query, string $relationshipName, string $column): Builder
|
||||
{
|
||||
$relationshipChain = $this->buildRelationshipChain($query->getModel(), $relationshipName);
|
||||
$targetModel = $this->getTargetModel($relationshipChain);
|
||||
|
||||
$subquery = $this->initializeSubquery($targetModel, $column);
|
||||
$this->applyRelationshipConstraints($subquery, $relationshipChain, $query->getModel());
|
||||
|
||||
return $subquery->limit(1)->toBase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Relation>
|
||||
*/
|
||||
protected function buildRelationshipChain(Model $baseModel, string $relationshipPath): array
|
||||
{
|
||||
$relationshipSegments = explode('.', $relationshipPath);
|
||||
$currentModel = $baseModel;
|
||||
$chain = [];
|
||||
|
||||
foreach ($relationshipSegments as $relationshipSegment) {
|
||||
$relationship = $currentModel->{$relationshipSegment}();
|
||||
|
||||
$this->validateRelationshipType($relationship);
|
||||
|
||||
$chain[] = $relationship;
|
||||
|
||||
$currentModel = $relationship->getRelated();
|
||||
}
|
||||
|
||||
return $chain;
|
||||
}
|
||||
|
||||
protected function validateRelationshipType(Relation $relationship): void
|
||||
{
|
||||
if ($relationship instanceof BelongsTo || $relationship instanceof HasOne || $relationship instanceof MorphOne || $relationship instanceof BelongsToThrough) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
'Nested sorting only supports [BelongsTo], [HasOne], [MorphOne], and [BelongsToThrough] relationships, [' . $relationship::class . '] found.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Relation> $relationshipChain
|
||||
*/
|
||||
protected function getTargetModel(array $relationshipChain): Model
|
||||
{
|
||||
$lastRelationship = end($relationshipChain);
|
||||
|
||||
return $lastRelationship->getRelated();
|
||||
}
|
||||
|
||||
protected function initializeSubquery(Model $targetModel, string $column): EloquentBuilder
|
||||
{
|
||||
return $targetModel::query()->select($targetModel->qualifyColumn($column));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Relation> $relationshipChain
|
||||
*/
|
||||
protected function applyRelationshipConstraints(
|
||||
EloquentBuilder $subquery,
|
||||
array $relationshipChain,
|
||||
Model $baseModel
|
||||
): void {
|
||||
$chainLength = count($relationshipChain);
|
||||
|
||||
for ($i = $chainLength - 1; $i >= 0; $i--) {
|
||||
$isFirstRelationship = $i === 0;
|
||||
|
||||
if ($isFirstRelationship) {
|
||||
$this->applyFirstRelationshipConstraint($subquery, $relationshipChain[$i], $baseModel); /** @phpstan-ignore argument.type */
|
||||
} else {
|
||||
$this->applyIntermediateRelationshipJoin($subquery, $relationshipChain[$i], $relationshipChain[$i - 1]); /** @phpstan-ignore argument.type, argument.type */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyFirstRelationshipConstraint(
|
||||
EloquentBuilder $subquery,
|
||||
BelongsTo | HasOne | MorphOne | BelongsToThrough $relationship,
|
||||
Model $baseModel
|
||||
): void {
|
||||
$baseTable = $baseModel->getTable();
|
||||
|
||||
if ($relationship instanceof BelongsTo) {
|
||||
$this->applyBelongsToConstraint($subquery, $relationship, $baseTable);
|
||||
} elseif ($relationship instanceof MorphOne) {
|
||||
$this->applyMorphOneConstraint($subquery, $relationship, $baseModel);
|
||||
} elseif ($relationship instanceof HasOne) {
|
||||
$this->applyHasOneConstraint($subquery, $relationship, $baseModel);
|
||||
} elseif ($relationship instanceof BelongsToThrough) {
|
||||
$this->applyBelongsToThroughConstraint($subquery, $relationship, $baseModel);
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyBelongsToConstraint(
|
||||
EloquentBuilder $subquery,
|
||||
BelongsTo $relationship,
|
||||
string $baseTable
|
||||
): void {
|
||||
$subquery->whereColumn(
|
||||
$relationship->getQualifiedOwnerKeyName(),
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function applyHasOneConstraint(
|
||||
EloquentBuilder $subquery,
|
||||
HasOne $relationship,
|
||||
Model $baseModel
|
||||
): void {
|
||||
$subquery->whereColumn(
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
$baseModel->qualifyColumn($relationship->getLocalKeyName()),
|
||||
);
|
||||
}
|
||||
|
||||
protected function applyMorphOneConstraint(
|
||||
EloquentBuilder $subquery,
|
||||
MorphOne $relationship,
|
||||
Model $baseModel
|
||||
): void {
|
||||
$subquery->whereColumn(
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
$baseModel->qualifyColumn($relationship->getLocalKeyName()),
|
||||
)->where(
|
||||
$relationship->getQualifiedMorphType(),
|
||||
$relationship->getMorphClass()
|
||||
);
|
||||
}
|
||||
|
||||
protected function applyBelongsToThroughConstraint(
|
||||
EloquentBuilder $subquery,
|
||||
BelongsToThrough $relationship,
|
||||
Model $baseModel,
|
||||
): void {
|
||||
$throughParents = $relationship->getThroughParents();
|
||||
|
||||
foreach ($throughParents as $i => $throughParent) {
|
||||
$isFirstThroughParent = $i === 0;
|
||||
|
||||
if ($isFirstThroughParent) {
|
||||
$predecessor = $relationship->getRelated();
|
||||
$first = $throughParent->qualifyColumn($relationship->getForeignKeyName($predecessor));
|
||||
$second = $predecessor->qualifyColumn($relationship->getLocalKeyName($predecessor));
|
||||
|
||||
$subquery->join($throughParent->getTable(), $first, '=', $second);
|
||||
} else {
|
||||
$predecessor = $throughParents[$i - 1];
|
||||
$first = $throughParent->qualifyColumn($relationship->getForeignKeyName($predecessor));
|
||||
$second = $predecessor->qualifyColumn($relationship->getLocalKeyName($predecessor));
|
||||
|
||||
$subquery->join($throughParent->getTable(), $first, '=', $second);
|
||||
}
|
||||
}
|
||||
|
||||
$subquery->whereColumn(
|
||||
$relationship->getQualifiedFirstLocalKeyName(),
|
||||
$baseModel->qualifyColumn($relationship->getFirstForeignKeyName()),
|
||||
);
|
||||
}
|
||||
|
||||
protected function applyIntermediateRelationshipJoin(
|
||||
EloquentBuilder $subquery,
|
||||
BelongsTo | HasOne | MorphOne | BelongsToThrough $currentRelationship,
|
||||
BelongsTo | HasOne | MorphOne | BelongsToThrough $previousRelationship
|
||||
): void {
|
||||
$previousTable = $previousRelationship->getRelated()->getTable();
|
||||
|
||||
if ($currentRelationship instanceof BelongsTo) {
|
||||
$this->joinBelongsTo($subquery, $currentRelationship, $previousTable);
|
||||
} elseif ($currentRelationship instanceof MorphOne) {
|
||||
$this->joinMorphOne($subquery, $currentRelationship, $previousTable);
|
||||
} elseif ($currentRelationship instanceof HasOne) {
|
||||
$this->joinHasOne($subquery, $currentRelationship, $previousTable);
|
||||
} elseif ($currentRelationship instanceof BelongsToThrough) {
|
||||
$this->joinBelongsToThrough($subquery, $currentRelationship, $previousTable);
|
||||
}
|
||||
}
|
||||
|
||||
protected function joinBelongsTo(
|
||||
EloquentBuilder $subquery,
|
||||
BelongsTo $relationship,
|
||||
string $previousTable
|
||||
): void {
|
||||
$subquery->join(
|
||||
$previousTable,
|
||||
$relationship->getQualifiedOwnerKeyName(),
|
||||
'=',
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function joinHasOne(
|
||||
EloquentBuilder $subquery,
|
||||
HasOne $relationship,
|
||||
string $previousTable
|
||||
): void {
|
||||
$subquery->join(
|
||||
$previousTable,
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
'=',
|
||||
$relationship->getQualifiedParentKeyName(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function joinMorphOne(
|
||||
EloquentBuilder $subquery,
|
||||
MorphOne $relationship,
|
||||
string $previousTable
|
||||
): void {
|
||||
$subquery->join(
|
||||
$previousTable,
|
||||
$relationship->getQualifiedForeignKeyName(),
|
||||
'=',
|
||||
$relationship->getQualifiedParentKeyName(),
|
||||
)->where(
|
||||
$relationship->getQualifiedMorphType(),
|
||||
$relationship->getMorphClass(),
|
||||
);
|
||||
}
|
||||
|
||||
protected function joinBelongsToThrough(
|
||||
EloquentBuilder $subquery,
|
||||
BelongsToThrough $relationship,
|
||||
string $previousTable
|
||||
): void {
|
||||
$throughParents = $relationship->getThroughParents();
|
||||
$targetModel = $relationship->getRelated();
|
||||
|
||||
// Join through parents from target to previousTable
|
||||
// For User->Company via Team: join Team to Company, then User to Team
|
||||
foreach ($throughParents as $i => $throughParent) {
|
||||
$isFirstThroughParent = $i === 0;
|
||||
|
||||
if ($isFirstThroughParent) {
|
||||
// Join first through parent to the target model
|
||||
$subquery->join(
|
||||
$throughParent->getTable(),
|
||||
$targetModel->qualifyColumn($relationship->getLocalKeyName($targetModel)),
|
||||
'=',
|
||||
$throughParent->qualifyColumn($relationship->getForeignKeyName($targetModel)),
|
||||
);
|
||||
} else {
|
||||
// Join subsequent through parents
|
||||
$predecessor = $throughParents[$i - 1];
|
||||
$subquery->join(
|
||||
$throughParent->getTable(),
|
||||
$predecessor->qualifyColumn($relationship->getLocalKeyName($predecessor)),
|
||||
'=',
|
||||
$throughParent->qualifyColumn($relationship->getForeignKeyName($predecessor)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, join the previous table to the last through parent
|
||||
$lastThroughParent = end($throughParents);
|
||||
$subquery->join(
|
||||
$previousTable,
|
||||
$lastThroughParent->qualifyColumn($relationship->getLocalKeyName($lastThroughParent)),
|
||||
'=',
|
||||
"{$previousTable}.{$relationship->getForeignKeyName($lastThroughParent)}",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
namespace Filament\Tables\Columns\Concerns;
|
||||
|
||||
use Filament\Support\Services\RelationshipOrderer;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Znck\Eloquent\Relations\BelongsToThrough;
|
||||
|
||||
use function Filament\Support\generate_search_column_expression;
|
||||
use function Filament\Support\generate_search_term_expression;
|
||||
@@ -87,14 +89,28 @@ trait InteractsWithTableQuery
|
||||
fn (EloquentBuilder $query): EloquentBuilder => $translatableContentDriver->applySearchConstraintToQuery($query, $searchColumn, $search, $whereClause, $isSearchForcedCaseInsensitive),
|
||||
fn (EloquentBuilder $query) => $query->when(
|
||||
$this->hasRelationship($query->getModel()),
|
||||
fn (EloquentBuilder $query): EloquentBuilder => $query->{"{$whereClause}Relation"}(
|
||||
$this->getRelationshipName($query->getModel()),
|
||||
generate_search_column_expression((string) str($searchColumn)->replace('.', '->'), $isSearchForcedCaseInsensitive, $databaseConnection),
|
||||
'like',
|
||||
"%{$nonTranslatableSearch}%",
|
||||
),
|
||||
function (EloquentBuilder $query) use ($model, $whereClause, $searchColumn, $isSearchForcedCaseInsensitive, $databaseConnection, $nonTranslatableSearch): EloquentBuilder {
|
||||
$relationshipName = $this->getRelationshipName($query->getModel());
|
||||
$relationship = $this->getRelationship($query->getModel(), $relationshipName);
|
||||
|
||||
$relatedTable = $model->getTable();
|
||||
|
||||
if ($relationship instanceof BelongsToThrough) {
|
||||
$relatedTable = $relationship->getRelated()->getTable();
|
||||
$searchColumn = str($searchColumn)->startsWith("{$relatedTable}.")
|
||||
? $searchColumn
|
||||
: $relationship->getRelated()->qualifyColumn($searchColumn);
|
||||
}
|
||||
|
||||
return $query->{"{$whereClause}Relation"}(
|
||||
$relationshipName,
|
||||
generate_search_column_expression($this->getJsonSafeColumnName($searchColumn, $relatedTable), $isSearchForcedCaseInsensitive, $databaseConnection),
|
||||
'like',
|
||||
"%{$nonTranslatableSearch}%",
|
||||
);
|
||||
},
|
||||
fn (EloquentBuilder $query) => $query->{$whereClause}(
|
||||
generate_search_column_expression((string) str($searchColumn)->replace('.', '->'), $isSearchForcedCaseInsensitive, $databaseConnection),
|
||||
generate_search_column_expression($this->getJsonSafeColumnName($searchColumn, $model->getTable()), $isSearchForcedCaseInsensitive, $databaseConnection),
|
||||
'like',
|
||||
"%{$nonTranslatableSearch}%",
|
||||
),
|
||||
@@ -107,6 +123,15 @@ trait InteractsWithTableQuery
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getJsonSafeColumnName(string $column, string $tableName): string
|
||||
{
|
||||
if (str($column)->startsWith("{$tableName}.")) {
|
||||
return (string) str($column)->after('.')->replace('.', '->')->prepend("{$tableName}.");
|
||||
}
|
||||
|
||||
return (string) str($column)->replace('.', '->');
|
||||
}
|
||||
|
||||
public function applySort(EloquentBuilder $query, string $direction = 'asc'): EloquentBuilder
|
||||
{
|
||||
if ($this->sortQuery) {
|
||||
@@ -121,10 +146,13 @@ trait InteractsWithTableQuery
|
||||
$relationshipName = $this->getRelationshipName($query->getModel());
|
||||
|
||||
foreach (array_reverse($this->getSortColumns($query->getModel())) as $sortColumn) {
|
||||
$sortColumn = (string) str($sortColumn)->replace('.', '->');
|
||||
$sortColumn = $this->getJsonSafeColumnName($sortColumn, $query->getModel()->getTable());
|
||||
|
||||
if ($relationshipName) {
|
||||
$query->orderByPowerJoins("{$relationshipName}.{$sortColumn}", $direction, joinType: 'leftJoin'); /** @phpstan-ignore method.notFound */
|
||||
if (filled($relationshipName)) {
|
||||
$query->orderBy(
|
||||
app(RelationshipOrderer::class)->buildSubquery($query, $relationshipName, $sortColumn),
|
||||
$direction
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,17 @@ class Summarizer extends ViewComponent implements HasEmbeddedView
|
||||
$attribute = $column->getName();
|
||||
$query = $this->getQuery()?->clone();
|
||||
|
||||
if ($query && $column->hasRelationship($query->getModel())) {
|
||||
$hasRelationship = $query && $column->hasRelationship($query->getModel());
|
||||
|
||||
if ($this->hasQueryModification() && $hasRelationship) {
|
||||
$baseQueryForModification = $query->toBase();
|
||||
$this->evaluate($this->modifyQueryUsing, [
|
||||
'attribute' => $attribute,
|
||||
'query' => $baseQueryForModification,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($hasRelationship) {
|
||||
$relationship = $column->getRelationship($query->getModel());
|
||||
$attribute = $column->getFullAttributeName($query->getModel());
|
||||
|
||||
@@ -129,7 +139,7 @@ class Summarizer extends ViewComponent implements HasEmbeddedView
|
||||
$query = $query?->getModel()->resolveConnection($query->getModel()->getConnectionName())
|
||||
->table($query->toBase(), $asName);
|
||||
|
||||
if ($this->hasQueryModification()) {
|
||||
if ($this->hasQueryModification() && ! $hasRelationship) {
|
||||
$query = $this->evaluate($this->modifyQueryUsing, [
|
||||
'attribute' => $attribute,
|
||||
'query' => $query,
|
||||
|
||||
@@ -8,6 +8,7 @@ use Carbon\CarbonInterface;
|
||||
use Closure;
|
||||
use Filament\Support\Components\Component;
|
||||
use Filament\Support\Contracts\HasLabel as LabelInterface;
|
||||
use Filament\Support\Services\RelationshipOrderer;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -321,8 +322,11 @@ class Group extends Component
|
||||
]) ?? $query;
|
||||
}
|
||||
|
||||
if ($relationshipName = $this->getRelationshipName()) {
|
||||
return $query->orderByPowerJoins("{$relationshipName}.{$this->getRelationshipAttribute()}", $direction, joinType: 'leftJoin'); /** @phpstan-ignore method.notFound */
|
||||
if (filled($relationshipName = $this->getRelationshipName())) {
|
||||
return $query->orderBy(
|
||||
app(RelationshipOrderer::class)->buildSubquery($query, $relationshipName, $this->getRelationshipAttribute()),
|
||||
$direction
|
||||
);
|
||||
}
|
||||
|
||||
return $query->orderBy($this->getRelationshipAttribute(), $direction);
|
||||
|
||||
@@ -23,7 +23,7 @@ class TestsSummaries
|
||||
$this->assertTableColumnSummarizerExists($columnName, $summarizerId);
|
||||
|
||||
$normalizeState = fn ($state): string => strval(
|
||||
is_numeric($state) ? round(floatval($state), 5) : $state,
|
||||
is_numeric($state) ? round(floatval($state), 4) : $state,
|
||||
);
|
||||
|
||||
$state = is_array($state) ? array_map($normalizeState, $state) : $normalizeState($state);
|
||||
@@ -57,7 +57,7 @@ class TestsSummaries
|
||||
$this->assertTableColumnSummarizerExists($columnName, $summarizerId);
|
||||
|
||||
$normalizeState = fn ($state): string => strval(
|
||||
is_numeric($state) ? round(floatval($state), 5) : $state,
|
||||
is_numeric($state) ? round(floatval($state), 4) : $state,
|
||||
);
|
||||
|
||||
$state = is_array($state) ? array_map($normalizeState, $state) : $normalizeState($state);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="tests/bootstrap.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
|
||||
<testsuites>
|
||||
<testsuite name="Tests">
|
||||
<directory suffix=".php">./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging>
|
||||
<junit outputFile="build/report.junit.xml"/>
|
||||
</logging>
|
||||
<php>
|
||||
<env name="APP_ENV" value="self-testing"/>
|
||||
<env name="APP_KEY" value="base64:yk+bUVuZa1p86Dqjk9OjVK2R1pm6XHxC6xEKFq8utH0="/>
|
||||
<env name="DB_CONNECTION" value="mysql"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="tests/bootstrap.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
|
||||
<testsuites>
|
||||
<testsuite name="Tests">
|
||||
<directory suffix=".php">./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging>
|
||||
<junit outputFile="build/report.junit.xml"/>
|
||||
</logging>
|
||||
<php>
|
||||
<env name="APP_ENV" value="self-testing"/>
|
||||
<env name="APP_KEY" value="base64:yk+bUVuZa1p86Dqjk9OjVK2R1pm6XHxC6xEKFq8utH0="/>
|
||||
<env name="DB_CONNECTION" value="pgsql"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="tests/bootstrap.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
|
||||
<testsuites>
|
||||
<testsuite name="Tests">
|
||||
<directory suffix=".php">./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging>
|
||||
<junit outputFile="build/report.junit.xml"/>
|
||||
</logging>
|
||||
<php>
|
||||
<env name="APP_ENV" value="self-testing"/>
|
||||
<env name="APP_KEY" value="base64:yk+bUVuZa1p86Dqjk9OjVK2R1pm6XHxC6xEKFq8utH0="/>
|
||||
<env name="DB_CONNECTION" value="testing"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
+2
-2
@@ -17,8 +17,7 @@ class PostForm
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->required(),
|
||||
->relationship('author', 'name'),
|
||||
Textarea::make('content')
|
||||
->columnSpanFull(),
|
||||
Toggle::make('is_published')
|
||||
@@ -31,6 +30,7 @@ class PostForm
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
TextInput::make('title2'),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
|
||||
+2
-2
@@ -33,8 +33,7 @@ class CreatePostWithFields extends Component implements HasActions, HasSchemas
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->required(),
|
||||
->relationship('author', 'name'),
|
||||
Textarea::make('content')
|
||||
->columnSpanFull(),
|
||||
Toggle::make('is_published')
|
||||
@@ -47,6 +46,7 @@ class CreatePostWithFields extends Component implements HasActions, HasSchemas
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
TextInput::make('title2'),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
|
||||
+4
@@ -32,6 +32,8 @@ class ManageUserTeams extends ManageRelatedRecords
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Forms\Components\Select::make('company_id')
|
||||
->relationship('company', 'name'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -45,6 +47,8 @@ class ManageUserTeams extends ManageRelatedRecords
|
||||
Tables\Columns\TextColumn::make('budget')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('company.name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
|
||||
+4
@@ -25,6 +25,8 @@ class TeamsRelationManager extends RelationManager
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Forms\Components\Select::make('company_id')
|
||||
->relationship('company', 'name'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -38,6 +40,8 @@ class TeamsRelationManager extends RelationManager
|
||||
Tables\Columns\TextColumn::make('budget')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('company.name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
|
||||
+4
-2
@@ -25,8 +25,7 @@ class PostResource extends Resource
|
||||
return $schema
|
||||
->components([
|
||||
Forms\Components\Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->required(),
|
||||
->relationship('author', 'name'),
|
||||
Forms\Components\Textarea::make('content')
|
||||
->columnSpanFull(),
|
||||
Forms\Components\Toggle::make('is_published')
|
||||
@@ -39,6 +38,7 @@ class PostResource extends Resource
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('title')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('title2'),
|
||||
Forms\Components\Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('json')
|
||||
@@ -63,6 +63,8 @@ class PostResource extends Resource
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('title2')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('string_backed_enum')
|
||||
->badge()
|
||||
->searchable(),
|
||||
|
||||
+5
@@ -12,6 +12,7 @@ use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\DetachAction;
|
||||
use Filament\Actions\DetachBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Resources\Pages\ManageRelatedRecords;
|
||||
@@ -40,6 +41,8 @@ class ManageUserTeams extends ManageRelatedRecords
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Select::make('company_id')
|
||||
->relationship('company', 'name'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -53,6 +56,8 @@ class ManageUserTeams extends ManageRelatedRecords
|
||||
TextColumn::make('budget')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('company.name')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
|
||||
+5
@@ -10,6 +10,7 @@ use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\DetachAction;
|
||||
use Filament\Actions\DetachBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
@@ -33,6 +34,8 @@ class TeamsRelationManager extends RelationManager
|
||||
->required()
|
||||
->numeric()
|
||||
->default(0),
|
||||
Select::make('company_id')
|
||||
->relationship('company', 'name'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -46,6 +49,8 @@ class TeamsRelationManager extends RelationManager
|
||||
TextColumn::make('budget')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('company.name')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
|
||||
+8
-3
@@ -37,8 +37,7 @@ class PostResource extends Resource
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->required(),
|
||||
->relationship('author', 'name'),
|
||||
Textarea::make('content')
|
||||
->columnSpanFull(),
|
||||
Toggle::make('is_published')
|
||||
@@ -51,6 +50,7 @@ class PostResource extends Resource
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
TextInput::make('title2'),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
@@ -67,7 +67,8 @@ class PostResource extends Resource
|
||||
return $schema
|
||||
->components([
|
||||
TextEntry::make('author.name')
|
||||
->label('Author'),
|
||||
->label('Author')
|
||||
->placeholder('-'),
|
||||
TextEntry::make('content')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
@@ -79,6 +80,8 @@ class PostResource extends Resource
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
TextEntry::make('title'),
|
||||
TextEntry::make('title2')
|
||||
->placeholder('-'),
|
||||
TextEntry::make('config')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
@@ -116,6 +119,8 @@ class PostResource extends Resource
|
||||
->sortable(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('title2')
|
||||
->searchable(),
|
||||
TextColumn::make('string_backed_enum')
|
||||
->badge()
|
||||
->searchable(),
|
||||
|
||||
+2
-2
@@ -16,8 +16,7 @@ class PostForm
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('author_id')
|
||||
->relationship('author', 'name')
|
||||
->required(),
|
||||
->relationship('author', 'name'),
|
||||
Textarea::make('content')
|
||||
->columnSpanFull(),
|
||||
Toggle::make('is_published')
|
||||
@@ -30,6 +29,7 @@ class PostForm
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
TextInput::make('title2'),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
|
||||
+4
-1
@@ -14,7 +14,8 @@ class PostInfolist
|
||||
return $schema
|
||||
->components([
|
||||
TextEntry::make('author.name')
|
||||
->label('Author'),
|
||||
->label('Author')
|
||||
->placeholder('-'),
|
||||
TextEntry::make('content')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
@@ -26,6 +27,8 @@ class PostInfolist
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
TextEntry::make('title'),
|
||||
TextEntry::make('title2')
|
||||
->placeholder('-'),
|
||||
TextEntry::make('config')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
|
||||
+2
@@ -24,6 +24,8 @@ class PostsTable
|
||||
->sortable(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('title2')
|
||||
->searchable(),
|
||||
TextColumn::make('string_backed_enum')
|
||||
->badge()
|
||||
->searchable(),
|
||||
|
||||
+51
-3
@@ -1,3 +1,51 @@
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\BlogPost;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Livewire\Component;
|
||||
|
||||
class ListBlogPosts extends Component implements HasActions, HasSchemas, HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithTable;
|
||||
use InteractsWithSchemas;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(fn (): Builder => BlogPost::query())
|
||||
->columns([
|
||||
//
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
//
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
//
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.list-blog-posts');
|
||||
}
|
||||
}
|
||||
|
||||
+51
-3
@@ -1,3 +1,51 @@
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Blog;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\Blog\Category;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Livewire\Component;
|
||||
|
||||
class ListCategories extends Component implements HasActions, HasSchemas, HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithTable;
|
||||
use InteractsWithSchemas;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(fn (): Builder => Category::query())
|
||||
->columns([
|
||||
//
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
//
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
//
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.blog.list-categories');
|
||||
}
|
||||
}
|
||||
|
||||
+51
-3
@@ -1,3 +1,51 @@
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Blog;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Livewire\Component;
|
||||
|
||||
class ListPosts extends Component implements HasActions, HasSchemas, HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithTable;
|
||||
use InteractsWithSchemas;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(fn (): Builder => Post::query())
|
||||
->columns([
|
||||
//
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
//
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
//
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.blog.list-posts');
|
||||
}
|
||||
}
|
||||
|
||||
+51
-3
@@ -1,3 +1,51 @@
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Livewire\Component;
|
||||
|
||||
class ListPosts extends Component implements HasActions, HasSchemas, HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithTable;
|
||||
use InteractsWithSchemas;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(fn (): Builder => Post::query())
|
||||
->columns([
|
||||
//
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
//
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
//
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.list-posts');
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -37,6 +37,8 @@ class ListPostsWithColumns extends Component implements HasActions, HasSchemas,
|
||||
->sortable(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('title2')
|
||||
->searchable(),
|
||||
TextColumn::make('string_backed_enum')
|
||||
->badge()
|
||||
->searchable(),
|
||||
|
||||
+78
-3
@@ -1,3 +1,78 @@
|
||||
<div>
|
||||
{{ $this->table }}
|
||||
</div>
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Livewire\Component;
|
||||
|
||||
class ListPostsWithColumns extends Component implements HasActions, HasSchemas, HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithTable;
|
||||
use InteractsWithSchemas;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(fn (): Builder => Post::query())
|
||||
->columns([
|
||||
TextColumn::make('author.name')
|
||||
->searchable(),
|
||||
IconColumn::make('is_published')
|
||||
->boolean(),
|
||||
TextColumn::make('rating')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('title2')
|
||||
->searchable(),
|
||||
TextColumn::make('string_backed_enum')
|
||||
->badge()
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('deleted_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
//
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
//
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.list-posts-with-columns');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Load .env.testing file if it exists
|
||||
// Using safeLoad() to avoid overriding environment variables that are already set
|
||||
if (file_exists(__DIR__ . '/../.env.testing')) {
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/..', '.env.testing');
|
||||
$dotenv->safeLoad();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Database\Factories;
|
||||
|
||||
use Filament\Tests\Fixtures\Models\Company;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class CompanyFactory extends Factory
|
||||
{
|
||||
protected $model = Company::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->company(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Database\Factories;
|
||||
|
||||
use Filament\Tests\Fixtures\Models\Image;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ImageFactory extends Factory
|
||||
{
|
||||
protected $model = Image::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'url' => $this->faker->imageUrl(),
|
||||
'alt_text' => $this->faker->sentence(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Database\Factories;
|
||||
|
||||
use Filament\Tests\Fixtures\Models\Company;
|
||||
use Filament\Tests\Fixtures\Models\Profile;
|
||||
use Filament\Tests\Fixtures\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ProfileFactory extends Factory
|
||||
{
|
||||
protected $model = Profile::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => User::factory(),
|
||||
'company_id' => Company::factory(),
|
||||
'bio' => $this->faker->sentence(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Database\Factories;
|
||||
|
||||
use Filament\Tests\Fixtures\Models\Profile;
|
||||
use Filament\Tests\Fixtures\Models\Setting;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class SettingFactory extends Factory
|
||||
{
|
||||
protected $model = Setting::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'profile_id' => Profile::factory(),
|
||||
'theme' => $this->faker->randomElement(['light', 'dark', 'auto']),
|
||||
'language' => $this->faker->randomElement(['en', 'es', 'fr', 'de']),
|
||||
];
|
||||
}
|
||||
}
|
||||
+1
@@ -13,6 +13,7 @@ return new class extends Migration
|
||||
$table->string('name');
|
||||
$table->text('description')->nullable();
|
||||
$table->decimal('budget', 10, 2)->default(0);
|
||||
$table->foreignId('company_id')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('companies', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('companies');
|
||||
}
|
||||
};
|
||||
+2
-1
@@ -10,12 +10,13 @@ return new class extends Migration
|
||||
{
|
||||
Schema::create('posts', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('author_id');
|
||||
$table->foreignId('author_id')->nullable();
|
||||
$table->text('content')->nullable();
|
||||
$table->boolean('is_published')->default(true);
|
||||
$table->unsignedTinyInteger('rating')->default(0);
|
||||
$table->json('tags')->nullable();
|
||||
$table->string('title');
|
||||
$table->string('title2')->nullable();
|
||||
$table->json('config')->nullable();
|
||||
$table->json('json')->nullable();
|
||||
$table->json('json_array_of_objects')->nullable();
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('profiles', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('company_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('bio')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('profiles');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('settings', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('profile_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('theme')->default('light');
|
||||
$table->string('language')->default('en');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('settings');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('images', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->morphs('imageable');
|
||||
$table->string('url');
|
||||
$table->string('alt_text')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('images');
|
||||
}
|
||||
};
|
||||
+1
-1
@@ -15,7 +15,7 @@ return new class extends Migration
|
||||
$table->after('password', function (Blueprint $table): void {
|
||||
$table->json('json')->nullable();
|
||||
$table->boolean('has_email_authentication')->default(false);
|
||||
$table->string('app_authentication_secret')->nullable();
|
||||
$table->text('app_authentication_secret')->nullable();
|
||||
$table->text('app_authentication_recovery_codes')->nullable();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
if (! Schema::hasColumn('users', 'team_id')) {
|
||||
$table->foreignId('team_id')->nullable()->constrained('teams')->nullOnDelete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
if (Schema::hasColumn('users', 'team_id')) {
|
||||
$table->dropForeign(['team_id']);
|
||||
$table->dropColumn('team_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -39,8 +39,14 @@ class PostsTable extends Component implements HasActions, HasSchemas, Tables\Con
|
||||
return $table
|
||||
->query(Post::query())
|
||||
->groups(fn () => [
|
||||
Tables\Grouping\Group::make('title'),
|
||||
Tables\Grouping\Group::make('author.name')
|
||||
->label(fn (Table $table, self $livewire) => 'Dynamic label'),
|
||||
Tables\Grouping\Group::make('author.team.name'),
|
||||
Tables\Grouping\Group::make('author.profile.bio'),
|
||||
Tables\Grouping\Group::make('author.profile.company.name'),
|
||||
Tables\Grouping\Group::make('author.image.url'),
|
||||
Tables\Grouping\Group::make('author.profile.image.alt_text'),
|
||||
])
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
@@ -64,6 +70,37 @@ class PostsTable extends Component implements HasActions, HasSchemas, Tables\Con
|
||||
]),
|
||||
Tables\Columns\TextColumn::make('author.email')
|
||||
->searchable(isIndividual: true, isGlobal: false),
|
||||
Tables\Columns\TextColumn::make('author.team.name')
|
||||
->label('Author Team')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('author.company.name')
|
||||
->label('Author Company (BelongsTo -> BelongsToThrough)')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('team.name')
|
||||
->label('Team (BelongsToThrough)')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('team.company.name')
|
||||
->label('Team Company (Nested BelongsToThrough)')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('author.profile.bio')
|
||||
->label('Author Profile Bio')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('author.profile.company.name')
|
||||
->label('Author Company')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('author.image.url')
|
||||
->label('Author Image URL')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('author.profile.image.alt_text')
|
||||
->label('Profile Image Alt')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\IconColumn::make('is_published')
|
||||
->boolean()
|
||||
->summarize([
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Livewire;
|
||||
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class PostsTableWithQualifiedColumns extends Component implements HasActions, HasSchemas, Tables\Contracts\HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithSchemas;
|
||||
use Tables\Concerns\InteractsWithTable;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(Post::query())
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')
|
||||
->sortable()
|
||||
->searchable(['posts.id']),
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('author.team.name')
|
||||
->label('Author Team')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
])
|
||||
->paginated(false);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.table');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Livewire;
|
||||
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class UsersTable extends Component implements HasActions, HasSchemas, Tables\Contracts\HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithSchemas;
|
||||
use Tables\Concerns\InteractsWithTable;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(User::query())
|
||||
->groups(fn () => [
|
||||
Tables\Grouping\Group::make('name'),
|
||||
Tables\Grouping\Group::make('profile.company.name'),
|
||||
Tables\Grouping\Group::make('profile.setting.theme'),
|
||||
Tables\Grouping\Group::make('image.url'),
|
||||
])
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('profile.bio')
|
||||
->label('Profile Bio')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('profile.company.name')
|
||||
->label('Company')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('profile.setting.theme')
|
||||
->label('Theme')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('profile.setting.language')
|
||||
->label('Language')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('image.url')
|
||||
->label('Image URL')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.table');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Livewire;
|
||||
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tests\Fixtures\Models\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class UsersWithTeamTable extends Component implements HasActions, HasSchemas, Tables\Contracts\HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithSchemas;
|
||||
use Tables\Concerns\InteractsWithTable;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(User::query())
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label('Name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('team.name')
|
||||
->label('Team Name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
])
|
||||
->paginated(false);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.table');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Models;
|
||||
|
||||
use Filament\Tests\Database\Factories\CompanyFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Company extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function newFactory()
|
||||
{
|
||||
return CompanyFactory::new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Models;
|
||||
|
||||
use Filament\Tests\Database\Factories\ImageFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class Image extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function imageable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
protected static function newFactory()
|
||||
{
|
||||
return ImageFactory::new();
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Znck\Eloquent\Relations\BelongsToThrough;
|
||||
use Znck\Eloquent\Traits\BelongsToThrough as BelongsToThroughTrait;
|
||||
|
||||
class Post extends Model
|
||||
{
|
||||
use BelongsToThroughTrait;
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
@@ -36,6 +39,15 @@ class Post extends Model
|
||||
return $this->belongsTo(User::class, 'author_id');
|
||||
}
|
||||
|
||||
public function team(): BelongsToThrough
|
||||
{
|
||||
return $this->belongsToThrough(
|
||||
Team::class,
|
||||
User::class,
|
||||
foreignKeyLookup: [User::class => 'author_id']
|
||||
);
|
||||
}
|
||||
|
||||
public function config(string $key): mixed
|
||||
{
|
||||
return $this->config[$key] ?? null;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Models;
|
||||
|
||||
use Filament\Tests\Database\Factories\ProfileFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
|
||||
class Profile extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function company(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function setting(): HasOne
|
||||
{
|
||||
return $this->hasOne(Setting::class);
|
||||
}
|
||||
|
||||
public function image(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Image::class, 'imageable');
|
||||
}
|
||||
|
||||
protected static function newFactory()
|
||||
{
|
||||
return ProfileFactory::new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Models;
|
||||
|
||||
use Filament\Tests\Database\Factories\SettingFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function profile(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
|
||||
protected static function newFactory()
|
||||
{
|
||||
return SettingFactory::new();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace Filament\Tests\Fixtures\Models;
|
||||
use Filament\Tests\Database\Factories\TeamFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Team extends Model
|
||||
{
|
||||
@@ -12,6 +13,11 @@ class Team extends Model
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function company(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
protected static function newFactory()
|
||||
{
|
||||
return TeamFactory::new();
|
||||
|
||||
@@ -12,14 +12,19 @@ use Filament\Tests\Database\Factories\UserFactory;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Znck\Eloquent\Traits\BelongsToThrough as BelongsToThroughTrait;
|
||||
|
||||
class User extends Authenticatable implements FilamentUser, HasAppAuthentication, HasAppAuthenticationRecovery, HasEmailAuthentication, HasTenants, MustVerifyEmail
|
||||
{
|
||||
use BelongsToThroughTrait;
|
||||
use HasFactory;
|
||||
use Notifiable;
|
||||
|
||||
@@ -109,8 +114,28 @@ class User extends Authenticatable implements FilamentUser, HasAppAuthentication
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function teams(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Team::class);
|
||||
}
|
||||
|
||||
public function profile(): HasOne
|
||||
{
|
||||
return $this->hasOne(Profile::class);
|
||||
}
|
||||
|
||||
public function image(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Image::class, 'imageable');
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsToThrough(Company::class, Team::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,11 @@ it('can generate a form schema class for a model', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Schemas/PostForm.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a form schema class in a nested directory', function (): void {
|
||||
@@ -56,6 +59,9 @@ it('can generate a form schema class for a model in a nested directory', functio
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Schemas/Blog/CategoryForm.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -72,12 +72,16 @@ it('can generate a Livewire form component with generated fields', function ():
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Livewire/CreatePostWithFields.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
|
||||
assertFileExists($viewPath = resource_path('views/livewire/create-post-with-fields.blade.php'));
|
||||
expect(file_get_contents($viewPath))
|
||||
->toMatchSnapshot();
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($viewPath))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a Livewire form component in a nested directory', function (): void {
|
||||
|
||||
@@ -407,8 +407,10 @@ it('can generate a manage related records page class in a resource with a genera
|
||||
->expectsQuestion($questions['relationshipType'], BelongsToMany::class);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/UserResource/Pages/ManageUserTeams.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a manage related records page class in a resource with a view operation', function () use ($runGenerateManageRelatedRecordsPageCommand, $generateManageRelatedRecordsPageCommandQuestions): void {
|
||||
|
||||
@@ -97,8 +97,10 @@ it('can generate a relation manager with a generated form schema and table colum
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/UserResource/RelationManagers/TeamsRelationManager.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a relation manager with a view operation', function (): void {
|
||||
|
||||
@@ -33,8 +33,11 @@ it('can generate a resource class', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource list page', function (): void {
|
||||
@@ -46,8 +49,11 @@ it('can generate a resource list page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource/Pages/ListPosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource create page', function (): void {
|
||||
@@ -59,8 +65,11 @@ it('can generate a resource create page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource/Pages/CreatePost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource edit page', function (): void {
|
||||
@@ -72,8 +81,11 @@ it('can generate a resource edit page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource/Pages/EditPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource view page', function (): void {
|
||||
@@ -86,8 +98,11 @@ it('can generate a resource view page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource/Pages/ViewPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate the form and table of a resource class', function (): void {
|
||||
@@ -100,8 +115,11 @@ it('can generate the form and table of a resource class', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class with soft-deletes', function (): void {
|
||||
@@ -114,8 +132,11 @@ it('can generate a resource class with soft-deletes', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource edit page with soft-deletes', function (): void {
|
||||
@@ -128,8 +149,11 @@ it('can generate a resource edit page with soft-deletes', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource/Pages/EditPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a simple resource class', function (): void {
|
||||
@@ -142,8 +166,11 @@ it('can generate a simple resource class', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a simple resource manage page', function (): void {
|
||||
@@ -156,8 +183,11 @@ it('can generate a simple resource manage page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/PostResource/Pages/ManagePosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class in a nested directory', function (): void {
|
||||
@@ -169,8 +199,11 @@ it('can generate a resource class in a nested directory', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource list page in a nested directory', function (): void {
|
||||
@@ -182,8 +215,11 @@ it('can generate a resource list page in a nested directory', function (): void
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/PostResource/Pages/ListPosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource create page in a nested directory', function (): void {
|
||||
@@ -195,8 +231,11 @@ it('can generate a resource create page in a nested directory', function (): voi
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/PostResource/Pages/CreatePost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource edit page in a nested directory', function (): void {
|
||||
@@ -208,8 +247,11 @@ it('can generate a resource edit page in a nested directory', function (): void
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/PostResource/Pages/EditPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource view page in a nested directory', function (): void {
|
||||
@@ -222,8 +264,11 @@ it('can generate a resource view page in a nested directory', function (): void
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/PostResource/Pages/ViewPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a simple resource manage page in a nested directory', function (): void {
|
||||
@@ -236,6 +281,9 @@ it('can generate a simple resource manage page in a nested directory', function
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/PostResource/Pages/ManagePosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -425,8 +425,10 @@ it('can generate a manage related records page class in a resource with a genera
|
||||
->expectsQuestion($questions['relationshipType'], BelongsToMany::class);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Pages/ManageUserTeams.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a manage related records page class in a resource with a view operation', function () use ($runGenerateManageRelatedRecordsPageCommand, $generateManageRelatedRecordsPageCommandQuestions): void {
|
||||
|
||||
@@ -108,8 +108,10 @@ it('can generate a relation manager with a generated form schema and table colum
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/RelationManagers/TeamsRelationManager.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a relation manager with a view operation', function (): void {
|
||||
|
||||
@@ -26,8 +26,11 @@ it('can generate a resource class', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class with a record title attribute', function (): void {
|
||||
@@ -40,8 +43,11 @@ it('can generate a resource class with a record title attribute', function (): v
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource form', function (): void {
|
||||
@@ -53,8 +59,11 @@ it('can generate a resource form', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Schemas/PostForm.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource infolist', function (): void {
|
||||
@@ -67,8 +76,11 @@ it('can generate a resource infolist', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Schemas/PostInfolist.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource table', function (): void {
|
||||
@@ -80,8 +92,11 @@ it('can generate a resource table', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Tables/PostsTable.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource list page', function (): void {
|
||||
@@ -93,8 +108,11 @@ it('can generate a resource list page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Pages/ListPosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource create page', function (): void {
|
||||
@@ -106,8 +124,11 @@ it('can generate a resource create page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Pages/CreatePost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource edit page', function (): void {
|
||||
@@ -119,8 +140,11 @@ it('can generate a resource edit page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Pages/EditPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource view page', function (): void {
|
||||
@@ -133,8 +157,11 @@ it('can generate a resource view page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Pages/ViewPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class with embedded form', function (): void {
|
||||
@@ -147,8 +174,11 @@ it('can generate a resource class with embedded form', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class with embedded infolist', function (): void {
|
||||
@@ -162,8 +192,11 @@ it('can generate a resource class with embedded infolist', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class with embedded table', function (): void {
|
||||
@@ -176,8 +209,11 @@ it('can generate a resource class with embedded table', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate the resource form content', function (): void {
|
||||
@@ -190,8 +226,11 @@ it('can generate the resource form content', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Schemas/PostForm.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate the resource infolist content', function (): void {
|
||||
@@ -205,8 +244,11 @@ it('can generate the resource infolist content', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Schemas/PostInfolist.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate the resource table content', function (): void {
|
||||
@@ -219,8 +261,11 @@ it('can generate the resource table content', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Tables/PostsTable.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate the form, infolist, and table content embedded in a resource class', function (): void {
|
||||
@@ -236,8 +281,11 @@ it('can generate the form, infolist, and table content embedded in a resource cl
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class with soft-deletes', function (): void {
|
||||
@@ -250,8 +298,11 @@ it('can generate a resource class with soft-deletes', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource table with soft-deletes', function (): void {
|
||||
@@ -264,8 +315,11 @@ it('can generate a resource table with soft-deletes', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Tables/PostsTable.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource edit page with soft-deletes', function (): void {
|
||||
@@ -278,8 +332,11 @@ it('can generate a resource edit page with soft-deletes', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Pages/EditPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a simple resource class', function (): void {
|
||||
@@ -304,8 +361,11 @@ it('can generate a simple resource class', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
|
||||
assertFileDoesNotExist(app_path('Filament/Resources/Posts/Schemas/PostForm.php'));
|
||||
assertFileDoesNotExist(app_path('Filament/Resources/Posts/Schemas/PostInfolist.php'));
|
||||
@@ -322,8 +382,11 @@ it('can generate a simple resource manage page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/Pages/ManagePosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a simple resource class without embedded schemas and table', function (): void {
|
||||
@@ -338,8 +401,11 @@ it('can generate a simple resource class without embedded schemas and table', fu
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource class in a nested directory', function (): void {
|
||||
@@ -351,8 +417,11 @@ it('can generate a resource class in a nested directory', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource form in a nested directory', function (): void {
|
||||
@@ -364,8 +433,11 @@ it('can generate a resource form in a nested directory', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Schemas/PostForm.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource infolist in a nested directory', function (): void {
|
||||
@@ -378,8 +450,11 @@ it('can generate a resource infolist in a nested directory', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Schemas/PostInfolist.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource table in a nested directory', function (): void {
|
||||
@@ -391,8 +466,11 @@ it('can generate a resource table in a nested directory', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Tables/PostsTable.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource list page in a nested directory', function (): void {
|
||||
@@ -404,8 +482,11 @@ it('can generate a resource list page in a nested directory', function (): void
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Pages/ListPosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource create page in a nested directory', function (): void {
|
||||
@@ -417,8 +498,11 @@ it('can generate a resource create page in a nested directory', function (): voi
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Pages/CreatePost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource edit page in a nested directory', function (): void {
|
||||
@@ -430,8 +514,11 @@ it('can generate a resource edit page in a nested directory', function (): void
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Pages/EditPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a resource view page in a nested directory', function (): void {
|
||||
@@ -444,8 +531,11 @@ it('can generate a resource view page in a nested directory', function (): void
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Pages/ViewPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a simple resource manage page in a nested directory', function (): void {
|
||||
@@ -458,8 +548,11 @@ it('can generate a simple resource manage page in a nested directory', function
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Blog/Posts/Pages/ManagePosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource class', function (): void {
|
||||
@@ -488,8 +581,11 @@ it('can generate a nested resource class', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource class with a plural parent resource name', function (): void {
|
||||
@@ -518,8 +614,11 @@ it('can generate a nested resource class with a plural parent resource name', fu
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource class with a parent resource name with `Resource` at the end', function (): void {
|
||||
@@ -548,8 +647,11 @@ it('can generate a nested resource class with a parent resource name with `Resou
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource form', function (): void {
|
||||
@@ -578,8 +680,11 @@ it('can generate a nested resource form', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/Schemas/PostForm.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource infolist', function (): void {
|
||||
@@ -609,8 +714,11 @@ it('can generate a nested resource infolist', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/Schemas/PostInfolist.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource create page', function (): void {
|
||||
@@ -639,8 +747,11 @@ it('can generate a nested resource create page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/Pages/CreatePost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource edit page', function (): void {
|
||||
@@ -669,8 +780,11 @@ it('can generate a nested resource edit page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/Pages/EditPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource view page', function (): void {
|
||||
@@ -700,8 +814,11 @@ it('can generate a nested resource view page', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Posts/Pages/ViewPost.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a nested resource class in a nested directory', function (): void {
|
||||
@@ -730,6 +847,9 @@ it('can generate a nested resource class in a nested directory', function (): vo
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Resources/Users/Resources/Blog/Posts/PostResource.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -79,8 +79,10 @@ it('can generate a page class with a generated form schema', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Filament/Pages/ManageSettings.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
class Settings extends BaseSettings
|
||||
|
||||
@@ -43,13 +43,21 @@ it('can create', function (): void {
|
||||
->assertHasNoFormErrors()
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseHas(Post::class, [
|
||||
'author_id' => $newData->author->getKey(),
|
||||
'content' => $newData->content,
|
||||
'tags' => json_encode($newData->tags),
|
||||
'title' => $newData->title,
|
||||
'rating' => $newData->rating,
|
||||
]);
|
||||
$record = Post::query()
|
||||
|
||||
->where('author_id', $newData->author->getKey())
|
||||
|
||||
->where('content', $newData->content)
|
||||
|
||||
->where('title', $newData->title)
|
||||
|
||||
->where('rating', $newData->rating)
|
||||
|
||||
->first();
|
||||
|
||||
expect($record)->not->toBeNull();
|
||||
|
||||
expect($record->tags)->toBe($newData->tags);
|
||||
|
||||
Event::assertDispatched(RecordCreated::class);
|
||||
Event::assertDispatched(RecordSaved::class);
|
||||
@@ -88,21 +96,31 @@ it('can create another', function (): void {
|
||||
->assertHasNoFormErrors()
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseHas(Post::class, [
|
||||
'author_id' => $newData->author->getKey(),
|
||||
'content' => $newData->content,
|
||||
'tags' => json_encode($newData->tags),
|
||||
'title' => $newData->title,
|
||||
'rating' => $newData->rating,
|
||||
]);
|
||||
$record = Post::query()
|
||||
|
||||
$this->assertDatabaseHas(Post::class, [
|
||||
'author_id' => $newData2->author->getKey(),
|
||||
'content' => $newData2->content,
|
||||
'tags' => json_encode($newData2->tags),
|
||||
'title' => $newData2->title,
|
||||
'rating' => $newData2->rating,
|
||||
]);
|
||||
->where('author_id', $newData->author->getKey())
|
||||
|
||||
->where('content', $newData->content)
|
||||
|
||||
->where('title', $newData->title)
|
||||
|
||||
->where('rating', $newData->rating)
|
||||
|
||||
->first();
|
||||
|
||||
expect($record)->not->toBeNull();
|
||||
|
||||
expect($record->tags)->toBe($newData->tags);
|
||||
|
||||
$record2 = Post::query()
|
||||
->where('author_id', $newData2->author->getKey())
|
||||
->where('content', $newData2->content)
|
||||
->where('title', $newData2->title)
|
||||
->where('rating', $newData2->rating)
|
||||
->first();
|
||||
|
||||
expect($record2)->not->toBeNull();
|
||||
expect($record2->tags)->toBe($newData2->tags);
|
||||
});
|
||||
|
||||
it('can create another and preserve data', function (): void {
|
||||
@@ -136,21 +154,31 @@ it('can create another and preserve data', function (): void {
|
||||
->assertHasNoFormErrors()
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseHas(Post::class, [
|
||||
'author_id' => $newData->author->getKey(),
|
||||
'content' => $newData->content,
|
||||
'tags' => json_encode($newData->tags),
|
||||
'title' => $newData->title,
|
||||
'rating' => $newData->rating,
|
||||
]);
|
||||
$record = Post::query()
|
||||
|
||||
$this->assertDatabaseHas(Post::class, [
|
||||
'author_id' => $newData2->author->getKey(),
|
||||
'content' => $newData2->content,
|
||||
'tags' => json_encode($newData->tags),
|
||||
'title' => $newData2->title,
|
||||
'rating' => $newData->rating,
|
||||
]);
|
||||
->where('author_id', $newData->author->getKey())
|
||||
|
||||
->where('content', $newData->content)
|
||||
|
||||
->where('title', $newData->title)
|
||||
|
||||
->where('rating', $newData->rating)
|
||||
|
||||
->first();
|
||||
|
||||
expect($record)->not->toBeNull();
|
||||
|
||||
expect($record->tags)->toBe($newData->tags);
|
||||
|
||||
$record2 = Post::query()
|
||||
->where('author_id', $newData2->author->getKey())
|
||||
->where('content', $newData2->content)
|
||||
->where('title', $newData2->title)
|
||||
->where('rating', $newData->rating)
|
||||
->first();
|
||||
|
||||
expect($record2)->not->toBeNull();
|
||||
expect($record2->tags)->toBe($newData->tags);
|
||||
});
|
||||
|
||||
it('can validate input', function (): void {
|
||||
|
||||
@@ -15,6 +15,7 @@ use Filament\Facades\Filament;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Filament\Tests\Fixtures\Models\Ticket;
|
||||
use Filament\Tests\Fixtures\Models\TicketMessage;
|
||||
use Filament\Tests\Fixtures\Models\User;
|
||||
use Filament\Tests\Fixtures\Policies\TicketPolicy;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\Pages\ListPosts;
|
||||
use Filament\Tests\Fixtures\Resources\Posts\PostResource;
|
||||
@@ -63,40 +64,64 @@ it('can render post authors', function (): void {
|
||||
});
|
||||
|
||||
it('can sort posts by title', function (): void {
|
||||
$posts = Post::factory()->count(10)->create();
|
||||
Post::factory()->count(10)->create();
|
||||
|
||||
$sortedAsc = Post::query()->orderBy('title')->get();
|
||||
$sortedDesc = Post::query()->orderByDesc('title')->get();
|
||||
|
||||
livewire(ListPosts::class)
|
||||
->sortTable('title')
|
||||
->assertCanSeeTableRecords($posts->sortBy('title'), inOrder: true)
|
||||
->assertCanSeeTableRecords($sortedAsc, inOrder: true)
|
||||
->sortTable('title', 'desc')
|
||||
->assertCanSeeTableRecords($posts->sortByDesc('title'), inOrder: true);
|
||||
->assertCanSeeTableRecords($sortedDesc, inOrder: true);
|
||||
});
|
||||
|
||||
it('can sort posts by author', function (): void {
|
||||
$posts = Post::factory()->count(10)->create();
|
||||
Post::factory()->count(10)->create();
|
||||
|
||||
$sortedAsc = Post::query()
|
||||
->orderBy(
|
||||
User::query()
|
||||
->select('name')
|
||||
->whereColumn('users.id', 'posts.author_id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
$sortedDesc = Post::query()
|
||||
->orderByDesc(
|
||||
User::query()
|
||||
->select('name')
|
||||
->whereColumn('users.id', 'posts.author_id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(ListPosts::class)
|
||||
->sortTable('author.name')
|
||||
->assertCanSeeTableRecords($posts->sortBy('author.name'), inOrder: true)
|
||||
->assertCanSeeTableRecords($sortedAsc, inOrder: true)
|
||||
->sortTable('author.name', 'desc')
|
||||
->assertCanSeeTableRecords($posts->sortByDesc('author.name'), inOrder: true);
|
||||
->assertCanSeeTableRecords($sortedDesc, inOrder: true);
|
||||
});
|
||||
|
||||
it('can sort posts with default sort key', function (): void {
|
||||
|
||||
$faker = fake()->unique();
|
||||
$posts = Post::factory()->count(10)->state(function () use ($faker) {
|
||||
Post::factory()->count(10)->state(function () use ($faker) {
|
||||
return [
|
||||
'id' => $faker->randomDigit(),
|
||||
'title' => 'Lorem Ipsum',
|
||||
];
|
||||
})->create();
|
||||
|
||||
$sortedAsc = Post::query()->orderBy('title')->orderBy('id')->get();
|
||||
$sortedDesc = Post::query()->orderByDesc('title')->orderByDesc('id')->get();
|
||||
|
||||
livewire(ListPosts::class)
|
||||
->sortTable('title')
|
||||
->assertCanSeeTableRecords($posts->sortBy([['title', 'asc'], ['id', 'asc']]), inOrder: true)
|
||||
->assertCanSeeTableRecords($sortedAsc, inOrder: true)
|
||||
->sortTable('title', 'desc')
|
||||
->assertCanSeeTableRecords($posts->sortBy([['title', 'desc'], ['id', 'desc']]), inOrder: true);
|
||||
->assertCanSeeTableRecords($sortedDesc, inOrder: true);
|
||||
});
|
||||
|
||||
it('can search posts by title', function (): void {
|
||||
|
||||
@@ -58,5 +58,5 @@ it('resolves the tenant with custom path correctly from the route', function ():
|
||||
Filament::setCurrentPanel($panel);
|
||||
Filament::setTenant($team);
|
||||
|
||||
expect(Filament::getUrl($team))->toBe('http://localhost/tenancy/1');
|
||||
expect(Filament::getUrl($team))->toBe('http://localhost/tenancy/' . $team->getKey());
|
||||
});
|
||||
|
||||
+1052
-14
File diff suppressed because it is too large
Load Diff
@@ -21,12 +21,18 @@ it('can generate a Livewire table component', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Livewire/ListBlogPosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
|
||||
assertFileExists($viewPath = resource_path('views/livewire/list-blog-posts.blade.php'));
|
||||
expect(file_get_contents($viewPath))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($viewPath));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a Livewire table component with a model', function (): void {
|
||||
@@ -38,12 +44,18 @@ it('can generate a Livewire table component with a model', function (): void {
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Livewire/ListPosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
|
||||
assertFileExists($viewPath = resource_path('views/livewire/list-posts.blade.php'));
|
||||
expect(file_get_contents($viewPath))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($viewPath));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a Livewire table component with generated columns', function (): void {
|
||||
@@ -56,12 +68,18 @@ it('can generate a Livewire table component with generated columns', function ()
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Livewire/ListPostsWithColumns.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
|
||||
assertFileExists($viewPath = resource_path('views/livewire/list-posts-with-columns.blade.php'));
|
||||
expect(file_get_contents($viewPath))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($viewPath));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a Livewire table component in a nested directory', function (): void {
|
||||
@@ -73,12 +91,18 @@ it('can generate a Livewire table component in a nested directory', function ():
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Livewire/Blog/ListPosts.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
|
||||
assertFileExists($viewPath = resource_path('views/livewire/blog/list-posts.blade.php'));
|
||||
expect(file_get_contents($viewPath))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($viewPath));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('can generate a Livewire table component for a model in a nested directory', function (): void {
|
||||
@@ -90,10 +114,16 @@ it('can generate a Livewire table component for a model in a nested directory',
|
||||
]);
|
||||
|
||||
assertFileExists($path = app_path('Livewire/Blog/ListCategories.php'));
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($path));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
|
||||
assertFileExists($viewPath = resource_path('views/livewire/blog/list-categories.blade.php'));
|
||||
expect(file_get_contents($viewPath))
|
||||
->toMatchSnapshot();
|
||||
expect(file_get_contents($viewPath));
|
||||
if (config('database.default') === 'testing') {
|
||||
expect(file_get_contents($path))
|
||||
->toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tests\Fixtures\Livewire\PostsTable;
|
||||
use Filament\Tests\Fixtures\Livewire\UsersTable;
|
||||
use Filament\Tests\Fixtures\Models\Company;
|
||||
use Filament\Tests\Fixtures\Models\Image;
|
||||
use Filament\Tests\Fixtures\Models\Post;
|
||||
use Filament\Tests\Fixtures\Models\Profile;
|
||||
use Filament\Tests\Fixtures\Models\Setting;
|
||||
use Filament\Tests\Fixtures\Models\Team;
|
||||
use Filament\Tests\Fixtures\Models\User;
|
||||
use Filament\Tests\Tables\TestCase;
|
||||
use Livewire\Features\SupportTesting\Testable;
|
||||
|
||||
@@ -41,3 +48,329 @@ it('can group a table', function (): void {
|
||||
->getLabel()->toBe('Dynamic label');
|
||||
});
|
||||
});
|
||||
|
||||
it('can group records by column', function (): void {
|
||||
// Create posts with different titles to group by
|
||||
Post::factory()->create(['title' => 'Apple Post']);
|
||||
Post::factory()->create(['title' => 'Banana Post']);
|
||||
Post::factory()->create(['title' => 'Apple Post']);
|
||||
Post::factory()->create(['title' => 'Cherry Post']);
|
||||
Post::factory()->create(['title' => 'Banana Post']);
|
||||
|
||||
$sortedPosts = Post::query()->orderBy('title')->get();
|
||||
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'title')
|
||||
->assertCanSeeTableRecords($sortedPosts, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records by relationship', function (): void {
|
||||
// Create users with specific names to control order
|
||||
$userAlice = User::factory()->create(['name' => 'Alice']);
|
||||
$userBob = User::factory()->create(['name' => 'Bob']);
|
||||
$userCharlie = User::factory()->create(['name' => 'Charlie']);
|
||||
|
||||
// Create posts with those authors
|
||||
Post::factory()->create(['author_id' => $userBob->id]);
|
||||
Post::factory()->create(['author_id' => $userAlice->id]);
|
||||
Post::factory()->create(['author_id' => $userCharlie->id]);
|
||||
Post::factory()->create(['author_id' => $userAlice->id]);
|
||||
Post::factory()->create(['author_id' => $userBob->id]);
|
||||
|
||||
$sortedPosts = Post::query()
|
||||
->orderBy(
|
||||
User::query()
|
||||
->select('name')
|
||||
->whereColumn('users.id', 'posts.author_id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'author.name')
|
||||
->assertCanSeeTableRecords($sortedPosts, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records by nested relationship', function (): void {
|
||||
// Create teams with specific names to control order
|
||||
$teamAlpha = Team::factory()->create(['name' => 'Alpha Team']);
|
||||
$teamBeta = Team::factory()->create(['name' => 'Beta Team']);
|
||||
$teamGamma = Team::factory()->create(['name' => 'Gamma Team']);
|
||||
|
||||
// Create users with teams
|
||||
$userWithAlpha = User::factory()->create(['team_id' => $teamAlpha->id]);
|
||||
$userWithBeta = User::factory()->create(['team_id' => $teamBeta->id]);
|
||||
$userWithGamma = User::factory()->create(['team_id' => $teamGamma->id]);
|
||||
|
||||
// Create posts with those authors
|
||||
Post::factory()->create(['author_id' => $userWithBeta->id]);
|
||||
Post::factory()->create(['author_id' => $userWithAlpha->id]);
|
||||
Post::factory()->create(['author_id' => $userWithGamma->id]);
|
||||
Post::factory()->create(['author_id' => $userWithAlpha->id]);
|
||||
Post::factory()->create(['author_id' => $userWithBeta->id]);
|
||||
|
||||
$sortedPosts = Post::query()
|
||||
->orderBy(
|
||||
Team::query()
|
||||
->select('teams.name')
|
||||
->whereColumn('teams.id', 'users.team_id')
|
||||
->join('users', 'users.team_id', '=', 'teams.id')
|
||||
->whereColumn('users.id', 'posts.author_id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'author.team.name')
|
||||
->assertCanSeeTableRecords($sortedPosts, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records by `BelongsTo` -> `HasOne` relationship', function (): void {
|
||||
// Create posts with unique profile bios
|
||||
$bios = ['Alpha bio', 'Beta bio', 'Gamma bio', 'Delta bio', 'Epsilon bio'];
|
||||
foreach ($bios as $bio) {
|
||||
$user = User::factory()->has(
|
||||
Profile::factory()->state(['bio' => $bio]),
|
||||
'profile'
|
||||
)->create();
|
||||
Post::factory()->create(['author_id' => $user->id]);
|
||||
}
|
||||
|
||||
$sortedPosts = Post::query()
|
||||
->orderBy(
|
||||
Profile::query()
|
||||
->select('bio')
|
||||
->whereColumn('profiles.user_id', 'users.id')
|
||||
->join('users', 'users.id', '=', 'profiles.user_id')
|
||||
->whereColumn('users.id', 'posts.author_id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'author.profile.bio')
|
||||
->assertCanSeeTableRecords($sortedPosts, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records by `BelongsTo` -> `HasOne` -> `BelongsTo` relationship', function (): void {
|
||||
// Create posts with users that have profiles linked to companies
|
||||
$companyNames = ['Acme Corp', 'Beta Inc', 'Gamma LLC', 'Delta Co', 'Epsilon Ltd'];
|
||||
foreach ($companyNames as $companyName) {
|
||||
$company = Company::factory()->create(['name' => $companyName]);
|
||||
$user = User::factory()->has(
|
||||
Profile::factory()->for($company, 'company'),
|
||||
'profile'
|
||||
)->create();
|
||||
Post::factory()->create(['author_id' => $user->id]);
|
||||
}
|
||||
|
||||
$sortedPosts = Post::query()
|
||||
->orderBy(
|
||||
Company::query()
|
||||
->select('companies.name')
|
||||
->whereColumn('companies.id', 'profiles.company_id')
|
||||
->join('profiles', 'profiles.company_id', '=', 'companies.id')
|
||||
->join('users', 'users.id', '=', 'profiles.user_id')
|
||||
->whereColumn('users.id', 'posts.author_id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'author.profile.company.name')
|
||||
->assertCanSeeTableRecords($sortedPosts, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records by `HasOne` -> `BelongsTo` relationship', function (): void {
|
||||
// Create users with profiles linked to different companies
|
||||
$companyNames = ['Alpha Corp', 'Beta Corp', 'Gamma Corp', 'Delta Corp', 'Epsilon Corp'];
|
||||
foreach ($companyNames as $companyName) {
|
||||
$company = Company::factory()->create(['name' => $companyName]);
|
||||
User::factory()->has(
|
||||
Profile::factory()->for($company, 'company'),
|
||||
'profile'
|
||||
)->create();
|
||||
}
|
||||
|
||||
$sortedUsers = User::query()
|
||||
->orderBy(
|
||||
Company::query()
|
||||
->select('companies.name')
|
||||
->whereColumn('companies.id', 'profiles.company_id')
|
||||
->join('profiles', 'profiles.company_id', '=', 'companies.id')
|
||||
->whereColumn('profiles.user_id', 'users.id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(UsersTable::class)
|
||||
->set('tableGrouping', 'profile.company.name')
|
||||
->assertCanSeeTableRecords($sortedUsers, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records by `HasOne` -> `HasOne` relationship', function (): void {
|
||||
// Create users with profiles that have settings
|
||||
$themes = ['alpha-theme', 'beta-theme', 'gamma-theme', 'delta-theme', 'epsilon-theme'];
|
||||
foreach ($themes as $theme) {
|
||||
User::factory()->has(
|
||||
Profile::factory()->has(
|
||||
Setting::factory()->state(['theme' => $theme]),
|
||||
'setting'
|
||||
),
|
||||
'profile'
|
||||
)->create();
|
||||
}
|
||||
|
||||
$sortedUsers = User::query()
|
||||
->orderBy(
|
||||
Setting::query()
|
||||
->select('theme')
|
||||
->whereColumn('settings.profile_id', 'profiles.id')
|
||||
->join('profiles', 'profiles.id', '=', 'settings.profile_id')
|
||||
->whereColumn('profiles.user_id', 'users.id')
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(UsersTable::class)
|
||||
->set('tableGrouping', 'profile.setting.theme')
|
||||
->assertCanSeeTableRecords($sortedUsers, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records by `MorphOne` relationship', function (): void {
|
||||
$urls = ['alpha.jpg', 'beta.jpg', 'gamma.jpg', 'delta.jpg', 'epsilon.jpg'];
|
||||
foreach ($urls as $url) {
|
||||
$user = User::factory()->create();
|
||||
Image::factory()->create([
|
||||
'url' => $url,
|
||||
'imageable_type' => User::class,
|
||||
'imageable_id' => $user->id,
|
||||
]);
|
||||
}
|
||||
|
||||
$sortedUsers = User::query()
|
||||
->orderBy(
|
||||
Image::query()
|
||||
->select('url')
|
||||
->whereColumn('images.imageable_id', 'users.id')
|
||||
->where('images.imageable_type', User::class)
|
||||
->limit(1)
|
||||
)
|
||||
->get();
|
||||
|
||||
livewire(UsersTable::class)
|
||||
->set('tableGrouping', 'image.url')
|
||||
->assertCanSeeTableRecords($sortedUsers, inOrder: true);
|
||||
});
|
||||
|
||||
it('can group records with nullable `BelongsTo` relationship', function (): void {
|
||||
$userAlpha = User::factory()->create(['name' => 'Alpha']);
|
||||
$userBeta = User::factory()->create(['name' => 'Beta']);
|
||||
|
||||
$postWithAlpha = Post::factory()->create(['author_id' => $userAlpha->id]);
|
||||
$postWithBeta = Post::factory()->create(['author_id' => $userBeta->id]);
|
||||
$postWithoutAuthor1 = Post::factory()->create(['author_id' => null]);
|
||||
$postWithoutAuthor2 = Post::factory()->create(['author_id' => null]);
|
||||
|
||||
$allPosts = collect([$postWithAlpha, $postWithBeta, $postWithoutAuthor1, $postWithoutAuthor2]);
|
||||
|
||||
// Just verify grouping doesn't crash with nullable relationships
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'author.name')
|
||||
->assertCanSeeTableRecords($allPosts);
|
||||
});
|
||||
|
||||
it('can group records with nullable `HasOne` relationship', function (): void {
|
||||
$userWithProfile1 = User::factory()->has(
|
||||
Profile::factory()->state(['bio' => 'Alpha bio']),
|
||||
'profile'
|
||||
)->create();
|
||||
|
||||
$userWithProfile2 = User::factory()->has(
|
||||
Profile::factory()->state(['bio' => 'Beta bio']),
|
||||
'profile'
|
||||
)->create();
|
||||
|
||||
$userWithoutProfile1 = User::factory()->create();
|
||||
$userWithoutProfile2 = User::factory()->create();
|
||||
|
||||
$allUsers = collect([$userWithProfile1, $userWithProfile2, $userWithoutProfile1, $userWithoutProfile2]);
|
||||
|
||||
// Just verify grouping doesn't crash with nullable relationships
|
||||
livewire(UsersTable::class)
|
||||
->set('tableGrouping', 'profile.bio')
|
||||
->assertCanSeeTableRecords($allUsers);
|
||||
});
|
||||
|
||||
it('can group records with nullable `MorphOne` relationship', function (): void {
|
||||
$userWithImage1 = User::factory()->create();
|
||||
Image::factory()->create([
|
||||
'url' => 'alpha.jpg',
|
||||
'imageable_type' => User::class,
|
||||
'imageable_id' => $userWithImage1->id,
|
||||
]);
|
||||
|
||||
$userWithImage2 = User::factory()->create();
|
||||
Image::factory()->create([
|
||||
'url' => 'beta.jpg',
|
||||
'imageable_type' => User::class,
|
||||
'imageable_id' => $userWithImage2->id,
|
||||
]);
|
||||
|
||||
$userWithoutImage1 = User::factory()->create();
|
||||
$userWithoutImage2 = User::factory()->create();
|
||||
|
||||
$allUsers = collect([$userWithImage1, $userWithImage2, $userWithoutImage1, $userWithoutImage2]);
|
||||
|
||||
// Just verify grouping doesn't crash with nullable relationships
|
||||
livewire(UsersTable::class)
|
||||
->set('tableGrouping', 'image.url')
|
||||
->assertCanSeeTableRecords($allUsers);
|
||||
});
|
||||
|
||||
it('can group records with nullable nested `BelongsTo` -> `HasOne` relationship', function (): void {
|
||||
$userWithProfile = User::factory()->has(
|
||||
Profile::factory()->state(['bio' => 'Alpha bio']),
|
||||
'profile'
|
||||
)->create();
|
||||
$postWithAuthorAndProfile = Post::factory()->create(['author_id' => $userWithProfile->id]);
|
||||
|
||||
$userWithoutProfile = User::factory()->create();
|
||||
$postWithAuthorNoProfile = Post::factory()->create(['author_id' => $userWithoutProfile->id]);
|
||||
|
||||
$postWithoutAuthor = Post::factory()->create(['author_id' => null]);
|
||||
|
||||
$allPosts = collect([$postWithAuthorAndProfile, $postWithAuthorNoProfile, $postWithoutAuthor]);
|
||||
|
||||
// Just verify grouping doesn't crash with nullable nested relationships
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'author.profile.bio')
|
||||
->assertCanSeeTableRecords($allPosts);
|
||||
});
|
||||
|
||||
it('can group records with nullable nested `BelongsTo` -> `HasOne` -> `BelongsTo` relationship', function (): void {
|
||||
$company = Company::factory()->create(['name' => 'Acme Corp']);
|
||||
$userWithProfileAndCompany = User::factory()->has(
|
||||
Profile::factory()->for($company, 'company'),
|
||||
'profile'
|
||||
)->create();
|
||||
$postComplete = Post::factory()->create(['author_id' => $userWithProfileAndCompany->id]);
|
||||
|
||||
$userWithProfileNoCompany = User::factory()->has(
|
||||
Profile::factory()->state(['company_id' => null]),
|
||||
'profile'
|
||||
)->create();
|
||||
$postNoCompany = Post::factory()->create(['author_id' => $userWithProfileNoCompany->id]);
|
||||
|
||||
$userNoProfile = User::factory()->create();
|
||||
$postNoProfile = Post::factory()->create(['author_id' => $userNoProfile->id]);
|
||||
|
||||
$postNoAuthor = Post::factory()->create(['author_id' => null]);
|
||||
|
||||
$allPosts = collect([$postComplete, $postNoCompany, $postNoProfile, $postNoAuthor]);
|
||||
|
||||
// Just verify grouping doesn't crash with nullable nested relationships
|
||||
livewire(PostsTable::class)
|
||||
->set('tableGrouping', 'author.profile.company.name')
|
||||
->assertCanSeeTableRecords($allPosts);
|
||||
});
|
||||
|
||||
+49
-2
@@ -30,17 +30,18 @@ use Filament\Tests\Fixtures\Providers\RequiredMultiFactorAuthenticationPanelProv
|
||||
use Filament\Tests\Fixtures\Providers\SlugsPanelProvider;
|
||||
use Filament\Tests\Fixtures\Providers\TenancyPanelProvider;
|
||||
use Filament\Widgets\WidgetsServiceProvider;
|
||||
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Kirschbaum\PowerJoins\PowerJoinsServiceProvider;
|
||||
use Livewire\LivewireServiceProvider;
|
||||
use Orchestra\Testbench\Concerns\WithWorkbench;
|
||||
use Orchestra\Testbench\TestCase as BaseTestCase;
|
||||
use PDO;
|
||||
use RyanChandler\BladeCaptureDirective\BladeCaptureDirectiveServiceProvider;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
use LazilyRefreshDatabase;
|
||||
use RefreshDatabase;
|
||||
use WithWorkbench;
|
||||
|
||||
protected function getPackageProviders($app): array
|
||||
@@ -89,5 +90,51 @@ abstract class TestCase extends BaseTestCase
|
||||
...$app['config']->get('view.paths'),
|
||||
__DIR__ . '/../resources/views',
|
||||
]);
|
||||
|
||||
$app['config']->set('database.connections.sqlite', [
|
||||
'driver' => 'sqlite',
|
||||
'database' => env('DB_DATABASE', ':memory:'),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => true,
|
||||
]);
|
||||
|
||||
$app['config']->set('database.connections.mysql', [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('MYSQL_PORT', env('DB_PORT', '3306')),
|
||||
'database' => env('DB_DATABASE', 'testing'),
|
||||
'username' => env('MYSQL_USERNAME', env('DB_USERNAME', 'root')),
|
||||
'password' => env('MYSQL_PASSWORD', env('DB_PASSWORD', '')),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'timezone' => '+00:00',
|
||||
'options' => [
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
],
|
||||
]);
|
||||
|
||||
$app['config']->set('database.connections.pgsql', [
|
||||
'driver' => 'pgsql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('PGSQL_PORT', env('DB_PORT', '5432')),
|
||||
'database' => env('DB_DATABASE', 'testing'),
|
||||
'username' => env('PGSQL_USERNAME', env('DB_USERNAME', 'postgres')),
|
||||
'password' => env('PGSQL_PASSWORD', env('DB_PASSWORD', '')),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'schema' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
'options' => [
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
],
|
||||
]);
|
||||
|
||||
$app['config']->set('database.default', env('DB_CONNECTION', 'testing'));
|
||||
$app['config']->set('database.connections.testing', $app['config']->get('database.connections.sqlite'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user