Files
laralog/tests/Feature/PostFormInjectionTest.php
T
goukiandCursor ae47447062
Build and Deploy / verify (push) Successful in 40s
Build and Deploy / deploy (push) Successful in 36s
feat: restore sablog browse modes and clean imported HTML
Keep the public site readable after import (nbsp, highlighter, wrapping code, compact pager) and finish the admin extras already in this tree: sticky levels, preview, page cache, and live llms.txt.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 21:33:02 +00:00

198 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Filament\Resources\Posts\Pages\EditPost;
use App\Filament\Resources\Posts\PostResource;
use App\Filament\Resources\Posts\Schemas\PostForm;
use App\Models\Post;
use Filament\Forms\Components\Field;
use Filament\Forms\Components\ToggleButtons;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use ReflectionClass;
use Tests\TestCase;
class PostFormInjectionTest extends TestCase
{
use RefreshDatabase;
public function test_post_form_uses_two_to_one_layout(): void
{
$schema = Schema::make();
PostResource::form($schema);
$roots = $schema->getComponents();
$this->assertCount(2, $roots);
[$content, $sidebar] = $roots;
$this->assertSame('正文', $content->getHeading());
$this->assertSame(PostForm::SIDEBAR_KEY, $sidebar->getKey(isAbsolute: false));
$this->assertSame(2, $content->getColumnSpan('default'));
$this->assertSame(2, $content->getColumnSpan('lg'));
$this->assertSame(1, $sidebar->getColumnSpan('default'));
$this->assertSame(1, $sidebar->getColumnSpan('lg'));
}
public function test_post_form_includes_paid_fields_in_access_section(): void
{
$schema = Schema::make();
PostResource::form($schema);
$access = $this->findKeyedComponent($schema->getComponents(), PostForm::ACCESS_KEY);
$this->assertNotNull($access);
$this->assertSame('访问设置', $access->getHeading());
$fieldNames = $this->fieldNames($access);
$this->assertContains('read_password', $fieldNames);
$this->assertContains('meta.price', $fieldNames);
$this->assertContains('meta.members_only', $fieldNames);
}
public function test_content_format_and_status_are_single_choice(): void
{
$schema = Schema::make();
PostResource::form($schema);
$fields = $this->collectFields($schema->getComponents());
$this->assertInstanceOf(ToggleButtons::class, $fields['content_format'] ?? null);
$this->assertInstanceOf(ToggleButtons::class, $fields['status'] ?? null);
$this->assertInstanceOf(ToggleButtons::class, $fields['close_comment'] ?? null);
$this->assertInstanceOf(ToggleButtons::class, $fields['sticky_level'] ?? null);
}
public function test_post_table_includes_paid_badge_column(): void
{
// Table 构造器要求 HasTable Livewire 组件,测试中绕过构造器
$table = (new ReflectionClass(Table::class))->newInstanceWithoutConstructor();
PostResource::table($table);
$names = array_keys($table->getColumns());
// 会员插件注入的付费徽章列(meta.members_only 承载价格/会员判定)
$this->assertContains('meta.members_only', $names);
$this->assertContains('title', $names);
}
public function test_meta_price_maps_to_post_meta_json(): void
{
$post = Post::create([
'title' => '付费文章',
'content' => '内容',
'content_format' => 'markdown',
'status' => 'published',
'published_at' => now(),
'meta' => ['price' => 9900, 'members_only' => true],
]);
$this->assertSame(9900, $post->meta['price']);
$this->assertTrue($post->meta['members_only']);
}
public function test_edit_post_save_preserves_other_meta_keys(): void
{
$post = Post::create([
'title' => '已解锁文章',
'slug' => 'unlocked-post',
'content' => '内容',
'content_format' => 'markdown',
'status' => 'published',
'published_at' => now(),
'meta' => ['price' => 9900, 'members_only' => true, 'unlocked_user_ids' => [1, 2]],
]);
Livewire::test(EditPost::class, ['record' => $post->getRouteKey()])
->call('save')
->assertHasNoFormErrors();
$post->refresh();
// 表单只含 price/members_only,保存时不得覆盖解锁用户等其他 meta 键
$this->assertSame([1, 2], $post->meta['unlocked_user_ids']);
$this->assertSame(9900, $post->meta['price']);
$this->assertTrue($post->meta['members_only']);
}
/**
* @param array<int, mixed> $components
*/
private function findKeyedComponent(array $components, string $key): ?Component
{
foreach ($components as $component) {
if (! $component instanceof Component) {
continue;
}
$property = (new ReflectionClass($component))->getProperty('key');
$componentKey = $property->getValue($component);
if ($componentKey === $key) {
return $component;
}
$found = $this->findKeyedComponent($this->childComponents($component), $key);
if ($found instanceof Component) {
return $found;
}
}
return null;
}
/**
* @return list<string>
*/
private function fieldNames(Component $section): array
{
return collect($this->childComponents($section))
->filter(fn ($field) => is_object($field) && method_exists($field, 'getName'))
->map(fn ($field) => $field->getName())
->values()
->all();
}
/**
* @param array<int, mixed> $components
* @return array<string, Field>
*/
private function collectFields(array $components): array
{
$fields = [];
foreach ($components as $component) {
if ($component instanceof Field) {
$fields[$component->getName()] = $component;
}
if ($component instanceof Component) {
$fields = [
...$fields,
...$this->collectFields($this->childComponents($component)),
];
}
}
return $fields;
}
/**
* @return array<int, mixed>
*/
private function childComponents(?Component $component): array
{
if (! $component instanceof Component) {
return [];
}
$property = (new ReflectionClass($component))->getProperty('childComponents');
$children = $property->getValue($component)['default'] ?? [];
return is_array($children) ? $children : [];
}
}