Skip to content

Testing

This guide covers testing strategies for CMS-backed components using Pest.

Ensure you have the testing dependencies:

Terminal window
composer require --dev pestphp/pest pestphp/pest-plugin-laravel

The package provides test mixins:

tests/Pest.php
uses(
Tests\TestCase::class,
Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature', 'Unit');
use App\Livewire\Pages\About;
use JFA\FilamentCMSCore\Models\Page;
use JFA\FilamentCMSCore\Models\Section;
it('renders the about page with sections', function () {
$page = Page::factory()->create([
'slug' => 'about',
'status' => 'published',
]);
$section = Section::factory()->create([
'slug' => 'hero',
'status' => 'active',
]);
$page->sections()->attach($section, ['order_column' => 1]);
Livewire::test(About::class)
->assertStatus(200)
->assertSee('Welcome');
});
it('injects metatags from cms page', function () {
$page = Page::factory()->create([
'slug' => 'about',
'metatags' => [
'title' => 'About Us - My Company',
'description' => 'Learn more about our team.',
],
]);
$response = $this->get('/about');
$response->assertSee('About Us - My Company', false);
$response->assertSee('Learn more about our team.', false);
});
use App\Livewire\Hero;
use JFA\FilamentCMSCore\CMS\SectionContent;
it('mounts with section content', function () {
$content = new SectionContent([
[
'type' => 'hero',
'data' => [
'label' => 'Welcome',
'headline' => 'Build Amazing Sites',
],
],
]);
$component = Livewire::test(Hero::class, ['content' => $content]);
$component->assertSet('label', 'Welcome');
$component->assertSet('headline', 'Build Amazing Sites');
});
it('renders plain values when visual editing is unavailable', function () {
$content = new SectionContent([
[
'type' => 'hero',
'data' => [
'label' => 'Welcome',
'headline' => 'Build Amazing Sites',
],
],
]);
$component = Livewire::test(Hero::class, ['content' => $content]);
// renderField should output plain value, not data-cms-source attributes
$component->assertSeeHtml('Welcome');
$component->assertDontSeeHtml('data-cms-source');
});
it('handles repeater content', function () {
$content = new SectionContent([
[
'type' => 'team',
'data' => [
'section_title' => 'Our Team',
'members' => [
['name' => 'Alice', 'role' => 'Developer'],
['name' => 'Bob', 'role' => 'Designer'],
],
],
],
]);
Livewire::test(Team::class, ['content' => $content])
->assertSet('sectionTitle', 'Our Team')
->assertSet('members', [
['name' => 'Alice', 'role' => 'Developer'],
['name' => 'Bob', 'role' => 'Designer'],
]);
});
use JFA\VeFilamentCMSLivewire\EditingMode;
use JFA\VeFilamentCMSLivewire\Livewire\VisualEditor;
beforeEach(function () {
Session::flush();
});
it('toggles editing mode', function () {
$user = actingAsAdmin(); // Helper from tests/Pest.php
Livewire::actingAs($user)->test(VisualEditor::class)
->call('toggle')
->assertRedirect();
expect(app(EditingMode::class)->isActive())->toBeTrue();
});
use JFA\VeFilamentCMSLivewire\Livewire\InlineEditForm;
use JFA\VeFilamentCMSLivewire\CmsSourceMap;
it('loads and saves content via source map', function () {
$user = actingAsAdmin();
$section = Section::factory()->create([
'content' => [
[
'type' => 'hero',
'data' => [
'label' => 'Old Label',
'headline' => 'Old Headline',
],
],
],
]);
$sourceMap = new CmsSourceMap(
sectionId: $section->id,
sectionSlug: 'hero',
blockType: 'hero',
blockIndex: 0,
pageId: 1,
field: 'headline',
fieldType: 'text',
);
Livewire::actingAs($user)->test(InlineEditForm::class)
->set('sourceMap', $sourceMap->toArray())
->set('data.value', 'New Headline')
->call('save')
->assertDispatched('contentUpdated');
$section->refresh();
expect($section->content[0]['data']['headline'])->toBe('New Headline');
});

refreshFromCMS Event Listener (from InteractsWithVisualEditing trait)

Section titled “refreshFromCMS Event Listener (from InteractsWithVisualEditing trait)”
it('refreshes content when contentUpdated event fires', function () {
$section = Section::factory()->create([
'content' => [
[
'type' => 'hero',
'data' => [
'headline' => 'Original',
],
],
],
]);
$content = new SectionContent([
[
'type' => 'hero',
'data' => ['headline' => 'Original'],
],
]);
$component = Livewire::test(Hero::class, ['content' => $content]);
// Simulate component with source map
$component->set('cmsSourceMap', [
'section_id' => $section->id,
'section_slug' => 'hero',
'block_type' => 'hero',
'block_index' => 0,
]);
// Update section in database
$section->content = [
[
'type' => 'hero',
'data' => ['headline' => 'Updated'],
],
];
$section->save();
// Fire the event
$component->dispatch('contentUpdated', sectionId: $section->id);
$component->assertSet('headline', 'Updated');
});
use App\CMS\Blocks\Hero;
use JFA\FilamentCMSCore\Forms\Blocks\ContentBlockRegistry;
it('registers the hero block', function () {
$registry = app(ContentBlockRegistry::class);
$block = $registry->getByName('hero');
expect($block)->not->toBeNull();
expect($block->getName())->toBe('hero');
});
it('has required fields', function () {
$block = App\CMS\Blocks\Hero::make();
$schema = $block->getChildSchema();
$fields = collect($schema)->map(fn ($field) => $field->getName())->all();
expect($fields)->toContain('label');
expect($fields)->toContain('headline');
});
it('allows frontend_editor to access editor panel', function () {
$user = User::factory()->create();
$user->assignRole('frontend_editor');
$this->actingAs($user)
->get('/editor')
->assertOk();
});
it('denies non-editors from editor panel', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get('/editor')
->assertRedirect('/editor/login');
});

Add to tests/Pest.php:

function actingAsAdmin(): App\Models\User
{
$user = App\Models\User::factory()->create();
$user->assignRole('super_admin');
return $user;
}
function actingAsFrontendEditor(): App\Models\User
{
$user = App\Models\User::factory()->create();
$user->assignRole('frontend_editor');
return $user;
}
function ensureShieldRolesExist(): void
{
// Create roles if they don't exist
Spatie\Permission\Models\Role::firstOrCreate(['name' => 'super_admin']);
Spatie\Permission\Models\Role::firstOrCreate(['name' => 'frontend_editor']);
}
Terminal window
# Run all tests
php artisan test --compact
# Run specific test file
php artisan test --compact --filter=SectionComponentsTest
# Run with coverage
php artisan test --compact --coverage
# Run and format
composer test
  • Use factories for model creation
  • Flush session before visual editing tests
  • Test both authorized and unauthorized access
  • Test empty content states
  • Test content update events
  • Test that renderField() outputs plain values when VE is unavailable
  • Run php artisan test --compact after changes