Skip to content

Commit ca011c6

Browse files
committed
test: add standalone board tests without Filament panel
Adds a StandaloneTestCase and TestStandaloneBoard fixture that verify board rendering, card movement, and pagination work without a Filament panel registered. This directly tests the fix for #84. Moves ArchTest into Feature/ directory to support separate Pest test case bindings per directory.
1 parent 96d4aa3 commit ca011c6

File tree

5 files changed

+240
-1
lines changed

5 files changed

+240
-1
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Relaticle\Flowforge\Tests\Fixtures;
6+
7+
use Filament\Actions\Concerns\InteractsWithActions;
8+
use Filament\Actions\Contracts\HasActions;
9+
use Filament\Forms\Concerns\InteractsWithForms;
10+
use Filament\Forms\Contracts\HasForms;
11+
use Livewire\Component;
12+
use Relaticle\Flowforge\Board;
13+
use Relaticle\Flowforge\Column;
14+
use Relaticle\Flowforge\Concerns\InteractsWithBoard;
15+
use Relaticle\Flowforge\Contracts\HasBoard;
16+
17+
/**
18+
* Standalone Livewire component for testing without Filament Panel.
19+
*/
20+
class TestStandaloneBoard extends Component implements HasActions, HasBoard, HasForms
21+
{
22+
use InteractsWithActions;
23+
use InteractsWithBoard {
24+
InteractsWithBoard::getDefaultActionRecord insteadof InteractsWithActions;
25+
}
26+
use InteractsWithForms;
27+
28+
public function board(Board $board): Board
29+
{
30+
return $board
31+
->query(Task::query())
32+
->recordTitleAttribute('title')
33+
->columnIdentifier('status')
34+
->positionIdentifier('order_position')
35+
->columns([
36+
Column::make('todo')->label('To Do')->color('gray'),
37+
Column::make('in_progress')->label('In Progress')->color('blue'),
38+
Column::make('completed')->label('Completed')->color('green'),
39+
]);
40+
}
41+
42+
public function render()
43+
{
44+
return <<<'BLADE'
45+
<div>
46+
{{ $this->board }}
47+
</div>
48+
BLADE;
49+
}
50+
}

tests/Pest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
use Relaticle\Flowforge\Tests\StandaloneTestCase;
34
use Relaticle\Flowforge\Tests\TestCase;
45

5-
pest()->extends(TestCase::class)->in(__DIR__);
6+
pest()->extends(TestCase::class)->in('Feature', 'Unit');
7+
pest()->extends(StandaloneTestCase::class)->in('Standalone');
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Livewire\Livewire;
6+
use Relaticle\Flowforge\Services\DecimalPosition;
7+
use Relaticle\Flowforge\Tests\Fixtures\Task;
8+
use Relaticle\Flowforge\Tests\Fixtures\TestStandaloneBoard;
9+
10+
/**
11+
* Tests that verify FlowForge works WITHOUT a Filament Panel registered.
12+
* This uses StandaloneTestCase which excludes FilamentServiceProvider and TestPanelProvider.
13+
*
14+
* @see https://github.com/relaticle/flowforge/issues/84
15+
*/
16+
17+
describe('standalone board rendering', function () {
18+
test('renders board without a panel registered', function () {
19+
Livewire::test(TestStandaloneBoard::class)
20+
->assertStatus(200)
21+
->assertSee('To Do')
22+
->assertSee('In Progress')
23+
->assertSee('Completed');
24+
});
25+
26+
test('displays cards in correct columns without a panel', function () {
27+
Task::factory()->todo()->create(['title' => 'Standalone Todo']);
28+
Task::factory()->inProgress()->create(['title' => 'Standalone In Progress']);
29+
Task::factory()->completed()->create(['title' => 'Standalone Completed']);
30+
31+
Livewire::test(TestStandaloneBoard::class)
32+
->assertSee('Standalone Todo')
33+
->assertSee('Standalone In Progress')
34+
->assertSee('Standalone Completed');
35+
});
36+
});
37+
38+
describe('standalone card movement', function () {
39+
test('moves card to different column without a panel', function () {
40+
$task = Task::factory()->todo()->withPosition('65535.0000000000')->create();
41+
42+
Livewire::test(TestStandaloneBoard::class)
43+
->call('moveCard', (string) $task->id, 'in_progress', null, null)
44+
->assertDispatched('kanban-card-moved');
45+
46+
expect($task->fresh()->status)->toBe('in_progress');
47+
});
48+
49+
test('moves card between two cards without a panel', function () {
50+
$task1 = Task::factory()->inProgress()->withPosition('65535.0000000000')->create();
51+
$task2 = Task::factory()->inProgress()->withPosition('131070.0000000000')->create();
52+
$taskToMove = Task::factory()->todo()->withPosition('65535.0000000000')->create();
53+
54+
Livewire::test(TestStandaloneBoard::class)
55+
->call('moveCard', (string) $taskToMove->id, 'in_progress', (string) $task1->id, (string) $task2->id);
56+
57+
$movedTask = $taskToMove->fresh();
58+
expect($movedTask->status)->toBe('in_progress')
59+
->and((float) $movedTask->order_position)->toBeGreaterThan(65535)
60+
->and((float) $movedTask->order_position)->toBeLessThan(131070);
61+
});
62+
63+
test('moves card to empty column without a panel', function () {
64+
$task = Task::factory()->todo()->withPosition('65535.0000000000')->create();
65+
66+
Livewire::test(TestStandaloneBoard::class)
67+
->call('moveCard', (string) $task->id, 'completed', null, null)
68+
->assertDispatched('kanban-card-moved');
69+
70+
$movedTask = $task->fresh();
71+
expect($movedTask->status)->toBe('completed')
72+
->and((float) $movedTask->order_position)->toBe((float) DecimalPosition::DEFAULT_GAP);
73+
});
74+
});
75+
76+
describe('standalone pagination', function () {
77+
test('loads more items without a panel', function () {
78+
Task::factory(30)->todo()->create();
79+
80+
Livewire::test(TestStandaloneBoard::class)
81+
->call('loadMoreItems', 'todo', 20)
82+
->assertDispatched('kanban-items-loaded');
83+
});
84+
});

tests/StandaloneTestCase.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Relaticle\Flowforge\Tests;
6+
7+
use BladeUI\Heroicons\BladeHeroiconsServiceProvider;
8+
use BladeUI\Icons\BladeIconsServiceProvider;
9+
use Filament\Actions\ActionsServiceProvider;
10+
use Filament\FilamentManager;
11+
use Filament\Forms\FormsServiceProvider;
12+
use Filament\Infolists\InfolistsServiceProvider;
13+
use Filament\Notifications\NotificationsServiceProvider;
14+
use Filament\Support\SupportServiceProvider;
15+
use Filament\Tables\TablesServiceProvider;
16+
use Illuminate\Database\Eloquent\Factories\Factory;
17+
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
18+
use Livewire\LivewireServiceProvider;
19+
use Orchestra\Testbench\Concerns\WithWorkbench;
20+
use Orchestra\Testbench\TestCase as Orchestra;
21+
use Relaticle\Flowforge\FlowforgeServiceProvider;
22+
use RyanChandler\BladeCaptureDirective\BladeCaptureDirectiveServiceProvider;
23+
24+
/**
25+
* Test case for standalone Livewire usage WITHOUT the Filament Panel Builder.
26+
*
27+
* Excludes FilamentServiceProvider and TestPanelProvider to verify the package
28+
* works without a panel registered.
29+
*
30+
* Note: In a real standalone install (without filament/filament), the Filament
31+
* facade class won't exist and class_exists() guards in filament/tables skip
32+
* panel-dependent calls entirely. In our test environment, filament/filament IS
33+
* installed as a dev dependency, so we register a minimal FilamentManager binding
34+
* to satisfy those guards without configuring any panel.
35+
*/
36+
class StandaloneTestCase extends Orchestra
37+
{
38+
use LazilyRefreshDatabase;
39+
use WithWorkbench;
40+
41+
protected function setUp(): void
42+
{
43+
parent::setUp();
44+
45+
Factory::guessFactoryNamesUsing(
46+
fn (string $modelName) => 'Relaticle\\Flowforge\\Database\\Factories\\' . class_basename($modelName) . 'Factory'
47+
);
48+
49+
// Register minimal FilamentManager binding. This is only needed because
50+
// filament/filament is a dev dependency (for panel tests), making the
51+
// Filament facade class available. The tables package's HasFilters trait
52+
// uses class_exists(Filament::class) to guard tenant-aware session keys,
53+
// which passes in our test env but would be false in a real standalone install.
54+
if (! $this->app->bound('filament')) {
55+
$this->app->scoped('filament', fn () => new FilamentManager);
56+
}
57+
}
58+
59+
protected function getPackageProviders($app): array
60+
{
61+
$providers = [
62+
ActionsServiceProvider::class,
63+
BladeCaptureDirectiveServiceProvider::class,
64+
BladeHeroiconsServiceProvider::class,
65+
BladeIconsServiceProvider::class,
66+
FormsServiceProvider::class,
67+
InfolistsServiceProvider::class,
68+
LivewireServiceProvider::class,
69+
NotificationsServiceProvider::class,
70+
SupportServiceProvider::class,
71+
TablesServiceProvider::class,
72+
FlowforgeServiceProvider::class,
73+
];
74+
75+
sort($providers);
76+
77+
return $providers;
78+
}
79+
80+
protected function defineEnvironment($app): void
81+
{
82+
config()->set('database.default', 'testing');
83+
config()->set('database.connections.testing', [
84+
'driver' => 'sqlite',
85+
'database' => ':memory:',
86+
'prefix' => '',
87+
]);
88+
89+
config()->set('app.key', 'base64:' . base64_encode(random_bytes(32)));
90+
config()->set('session.driver', 'array');
91+
config()->set('session.encrypt', false);
92+
93+
config()->set('view.paths', [
94+
resource_path('views'),
95+
__DIR__ . '/../resources/views',
96+
]);
97+
}
98+
99+
protected function defineDatabaseMigrations(): void
100+
{
101+
$this->loadMigrationsFrom(__DIR__ . '/database/migrations');
102+
}
103+
}

0 commit comments

Comments
 (0)