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>
This commit is contained in:
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Blog\Support\PluginManager;
|
||||
use App\Filament\Support\AdminEnvironment;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Plugins\Demo\HelloWorld\ServiceProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AdminPagesTest extends TestCase
|
||||
@@ -32,12 +35,40 @@ class AdminPagesTest extends TestCase
|
||||
public function test_post_resource_loads(): void
|
||||
{
|
||||
$this->actingAs($this->admin)->get('/admin/posts')->assertOk();
|
||||
$this->actingAs($this->admin)->get('/admin/posts/create')->assertOk();
|
||||
$this->actingAs($this->admin)->get('/admin/posts/create')
|
||||
->assertOk()
|
||||
->assertSee('Slug(留空自动生成)', false)
|
||||
->assertSee('封面图', false)
|
||||
->assertSee('ll-media-picker-preview', false)
|
||||
->assertSee('评论状态', false)
|
||||
->assertSee('访问设置', false)
|
||||
->assertSee('会员专享', false)
|
||||
->assertSee('价格', false)
|
||||
->assertSee('置顶头条', false)
|
||||
->assertSee('轻置顶', false);
|
||||
}
|
||||
|
||||
public function test_settings_page_loads(): void
|
||||
{
|
||||
$this->actingAs($this->admin)->get('/admin/blog-settings')->assertOk();
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/blog-settings')
|
||||
->assertOk()
|
||||
->assertSee('首页浏览模式', false);
|
||||
}
|
||||
|
||||
public function test_page_cache_and_language_pages_load(): void
|
||||
{
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/page-cache')
|
||||
->assertOk()
|
||||
->assertSee('页面缓存', false)
|
||||
->assertSee('已缓存的前台页面', false);
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/languages')
|
||||
->assertOk()
|
||||
->assertSee('文章多语言', false)
|
||||
->assertSee('未开通', false);
|
||||
}
|
||||
|
||||
public function test_comment_resource_loads(): void
|
||||
@@ -77,8 +108,8 @@ class AdminPagesTest extends TestCase
|
||||
$original = app()->environment();
|
||||
app()['env'] = 'production';
|
||||
|
||||
$this->assertSame('prod', \App\Filament\Support\AdminEnvironment::name());
|
||||
$this->assertSame('danger', \App\Filament\Support\AdminEnvironment::color());
|
||||
$this->assertSame('prod', AdminEnvironment::name());
|
||||
$this->assertSame('danger', AdminEnvironment::color());
|
||||
|
||||
app()['env'] = $original;
|
||||
}
|
||||
@@ -88,16 +119,17 @@ class AdminPagesTest extends TestCase
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin')
|
||||
->assertOk()
|
||||
->assertSee(\App\Filament\Support\AdminEnvironment::name(), false)
|
||||
->assertSee(AdminEnvironment::name(), false)
|
||||
->assertSee('写文章', false)
|
||||
->assertSee('网站', false)
|
||||
->assertSee('/admin/posts/create', false);
|
||||
}
|
||||
|
||||
public function test_hello_world_plugin_pages_load_when_enabled(): void
|
||||
{
|
||||
$manager = app(\App\Blog\Support\PluginManager::class);
|
||||
$manager = app(PluginManager::class);
|
||||
$manager->enable('demo.hello-world');
|
||||
\Plugins\Demo\HelloWorld\ServiceProvider::bootPlugin($manager);
|
||||
ServiceProvider::bootPlugin($manager);
|
||||
|
||||
$this->actingAs($this->admin)->get('/admin')->assertOk();
|
||||
$this->actingAs($this->admin)->get('/admin/posts/create')->assertOk();
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Filament\Resources\Posts\Pages\EditPost;
|
||||
use App\Filament\Resources\Posts\RelationManagers\CommentsRelationManager;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FilamentV5CompatTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_no_legacy_filament_table_action_imports(): void
|
||||
{
|
||||
$legacy = [];
|
||||
|
||||
foreach ([app_path(), base_path('plugins')] as $root) {
|
||||
foreach (File::allFiles($root) as $file) {
|
||||
if ($file->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $file->getPathname();
|
||||
if (str_contains(File::get($path), 'Filament\\Tables\\Actions\\')) {
|
||||
$legacy[] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame(
|
||||
[],
|
||||
$legacy,
|
||||
'Filament v5 table actions must use Filament\\Actions, not Filament\\Tables\\Actions'
|
||||
);
|
||||
}
|
||||
|
||||
public function test_edit_post_boots_comments_relation_manager(): void
|
||||
{
|
||||
$this->seed();
|
||||
|
||||
$admin = User::query()->where('email', 'admin@laralog.test')->firstOrFail();
|
||||
$this->actingAs($admin);
|
||||
|
||||
$post = Post::create([
|
||||
'user_id' => $admin->id,
|
||||
'title' => '评论关系测试',
|
||||
'slug' => 'comment-relation-test',
|
||||
'content' => '内容',
|
||||
'content_format' => 'markdown',
|
||||
'status' => 'published',
|
||||
'published_at' => now(),
|
||||
]);
|
||||
|
||||
$this->get('/admin/posts/'.$post->id.'/edit')->assertOk();
|
||||
|
||||
Livewire::test(EditPost::class, ['record' => $post->getRouteKey()])
|
||||
->assertSuccessful();
|
||||
|
||||
// The comments table only instantiates here. A GET of the edit page does not catch a bad Action import.
|
||||
Livewire::test(CommentsRelationManager::class, [
|
||||
'ownerRecord' => $post,
|
||||
'pageClass' => EditPost::class,
|
||||
])->assertSuccessful();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class HomeViewModeTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Post $post;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$user = User::create(['name' => '作者', 'email' => 'view@test.com', 'password' => bcrypt('x')]);
|
||||
$category = Category::create(['name' => '技术', 'slug' => 'tech-view']);
|
||||
$this->post = Post::create([
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category->id,
|
||||
'title' => '浏览模式测试文',
|
||||
'slug' => 'view-mode-test',
|
||||
'excerpt' => '列表摘要文字',
|
||||
'content' => '这是完整正文内容 FULL_BODY_ONLY_TOKEN',
|
||||
'content_format' => 'markdown',
|
||||
'status' => 'published',
|
||||
'published_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_list_mode_shows_excerpt_not_full_body(): void
|
||||
{
|
||||
$this->get('/?view=list')
|
||||
->assertOk()
|
||||
->assertSee('浏览模式', false)
|
||||
->assertSee('列表摘要文字', false)
|
||||
->assertDontSee('FULL_BODY_ONLY_TOKEN', false);
|
||||
}
|
||||
|
||||
public function test_full_mode_shows_article_body(): void
|
||||
{
|
||||
$this->get('/?view=full')
|
||||
->assertOk()
|
||||
->assertSee('浏览模式', false)
|
||||
->assertSee('FULL_BODY_ONLY_TOKEN', false);
|
||||
}
|
||||
|
||||
public function test_compact_pagination_shows_page_info(): void
|
||||
{
|
||||
$userId = $this->post->user_id;
|
||||
$categoryId = $this->post->category_id;
|
||||
|
||||
for ($index = 1; $index <= 12; $index++) {
|
||||
Post::create([
|
||||
'user_id' => $userId,
|
||||
'category_id' => $categoryId,
|
||||
'title' => '分页文章 '.$index,
|
||||
'slug' => 'pager-'.$index,
|
||||
'content' => '正文 '.$index,
|
||||
'content_format' => 'markdown',
|
||||
'status' => 'published',
|
||||
'published_at' => now()->subMinutes($index),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->get('/')
|
||||
->assertOk()
|
||||
->assertSee('第 1 /', false)
|
||||
->assertSee('页', false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ImportedContentTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Post $post;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$user = User::create(['name' => '作者', 'email' => 'import-html@test.com', 'password' => bcrypt('x')]);
|
||||
$category = Category::create(['name' => '运维', 'slug' => 'ops']);
|
||||
|
||||
$html = '<p> 不多说,上代码:</p>'
|
||||
.'<div class="codeText"><div class="codeHead">XML/HTML代码</div>'
|
||||
.'<ol class="dp-xml">'
|
||||
.'<li>docker run -d\\</li>'
|
||||
.'<li> --restart always \\</li>'
|
||||
.'<li> --name derper</li>'
|
||||
.'</ol></div>'
|
||||
.'<div><div>curl -fsSL example.com \\</div><div> | sh</div></div>';
|
||||
|
||||
$this->post = Post::create([
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category->id,
|
||||
'title' => '还是得derp',
|
||||
'slug' => 'derp-import',
|
||||
'excerpt' => '',
|
||||
'content' => $html,
|
||||
'content_format' => 'html',
|
||||
'status' => 'published',
|
||||
'published_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_show_page_hides_nbsp_and_wraps_code(): void
|
||||
{
|
||||
$this->get('/show-'.$this->post->id.'.shtml')
|
||||
->assertOk()
|
||||
->assertDontSee(' ', false)
|
||||
->assertDontSee('codeText', false)
|
||||
->assertSee('<pre', false)
|
||||
->assertSee('docker run', false)
|
||||
->assertSee('language-bash', false);
|
||||
}
|
||||
|
||||
public function test_homepage_excerpt_does_not_show_nbsp(): void
|
||||
{
|
||||
$this->get('/')
|
||||
->assertOk()
|
||||
->assertSee('不多说,上代码', false)
|
||||
->assertDontSee(' ', false);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Comment;
|
||||
use App\Models\Post;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\Tags\Tag;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -23,7 +23,7 @@ class LegacyRoutesTest extends TestCase
|
||||
|
||||
$user = User::create(['name' => '作者', 'email' => 'author@test.com', 'password' => bcrypt('x')]);
|
||||
$category = Category::create(['name' => '技术', 'slug' => 'tech']);
|
||||
\Illuminate\Support\Facades\DB::table('posts')->insert([
|
||||
DB::table('posts')->insert([
|
||||
'id' => 12,
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category->id,
|
||||
@@ -93,6 +93,8 @@ class LegacyRoutesTest extends TestCase
|
||||
$this->get('/rss.xml')->assertOk();
|
||||
$this->get('/sitemap.xml')->assertOk();
|
||||
$this->get('/robots.txt')->assertOk();
|
||||
$this->get('/llms.txt')->assertOk();
|
||||
$this->get('/posts/legacy-post.md')->assertOk();
|
||||
}
|
||||
|
||||
public function test_missing_records_return_404(): void
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Blog\Support\PageCacheMiddleware;
|
||||
use App\Blog\Support\ThemeManager;
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LlmsFeedTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $author;
|
||||
|
||||
private Category $category;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$this->author = User::query()->where('email', 'admin@laralog.test')->firstOrFail();
|
||||
$this->category = Category::create([
|
||||
'name' => '索引',
|
||||
'slug' => 'llms-cat',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_llms_txt_lists_new_published_posts_and_skips_drafts(): void
|
||||
{
|
||||
$published = $this->makePost('给 Agent 看的新文章', 'agent-new-post', 'published');
|
||||
$this->makePost('还没发布', 'agent-draft', 'draft');
|
||||
|
||||
$this->get('/llms.txt')
|
||||
->assertOk()
|
||||
->assertHeader('Content-Type', 'text/plain; charset=utf-8')
|
||||
->assertSee('给 Agent 看的新文章', false)
|
||||
->assertSee('/posts/agent-new-post.md', false)
|
||||
->assertSee($published->url, false)
|
||||
->assertDontSee('还没发布', false);
|
||||
}
|
||||
|
||||
public function test_markdown_export_is_live_and_hides_restricted_posts(): void
|
||||
{
|
||||
$this->makePost('Markdown 导出', 'md-export', 'published', '这里是正文。');
|
||||
|
||||
$this->get('/posts/md-export.md')
|
||||
->assertOk()
|
||||
->assertHeader('Content-Type', 'text/markdown; charset=utf-8')
|
||||
->assertSee('# Markdown 导出', false)
|
||||
->assertSee('这里是正文。', false);
|
||||
|
||||
$this->makePost('会员专享', 'members-only-md', 'published', '秘密', [
|
||||
'members_only' => true,
|
||||
]);
|
||||
|
||||
$this->get('/posts/members-only-md.md')->assertNotFound();
|
||||
$this->get('/llms.txt')->assertDontSee('会员专享', false);
|
||||
}
|
||||
|
||||
public function test_llms_and_feeds_are_not_page_cached(): void
|
||||
{
|
||||
$this->makePost('缓存外的文章', 'no-cache-llms', 'published');
|
||||
PageCacheMiddleware::flush();
|
||||
|
||||
$this->get('/llms.txt')->assertOk();
|
||||
$this->get('/rss.xml')->assertOk();
|
||||
$this->get('/posts/no-cache-llms.md')->assertOk();
|
||||
|
||||
$this->assertSame(0, PageCacheMiddleware::count());
|
||||
}
|
||||
|
||||
public function test_frontend_feed_links_open_in_a_new_tab(): void
|
||||
{
|
||||
app(ThemeManager::class)->activate('sablog');
|
||||
|
||||
$this->get('/')
|
||||
->assertOk()
|
||||
->assertSee('target="_blank"', false)
|
||||
->assertSee('rel="noopener noreferrer"', false)
|
||||
->assertSee(route('feed.rss'), false)
|
||||
->assertSee(route('feed.sitemap'), false)
|
||||
->assertSee(route('feed.llms'), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function makePost(string $title, string $slug, string $status, string $content = '内容', array $meta = []): Post
|
||||
{
|
||||
return Post::create([
|
||||
'user_id' => $this->author->id,
|
||||
'category_id' => $this->category->id,
|
||||
'title' => $title,
|
||||
'slug' => $slug,
|
||||
'content' => $content,
|
||||
'content_format' => 'markdown',
|
||||
'status' => $status,
|
||||
'meta' => $meta,
|
||||
'published_at' => $status === 'published' ? now() : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PageCacheTest extends TestCase
|
||||
@@ -44,6 +43,9 @@ class PageCacheTest extends TestCase
|
||||
// 第二次命中缓存
|
||||
$this->get('/')->assertOk();
|
||||
$this->assertSame(1, PageCacheMiddleware::count());
|
||||
|
||||
$this->get('/?view=full')->assertOk();
|
||||
$this->assertSame(2, PageCacheMiddleware::count());
|
||||
}
|
||||
|
||||
public function test_authenticated_user_not_cached(): void
|
||||
@@ -63,7 +65,7 @@ class PageCacheTest extends TestCase
|
||||
$post = Post::where('slug', 'cache-test')->first();
|
||||
$views = $post->views;
|
||||
|
||||
$this->get('/posts/cache-test.shtml')->assertOk();
|
||||
$this->get($post->url)->assertOk();
|
||||
$this->get('/track-view/'.$post->id)->assertStatus(204);
|
||||
|
||||
$post->refresh();
|
||||
|
||||
@@ -6,7 +6,11 @@ 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;
|
||||
@@ -18,31 +22,51 @@ class PostFormInjectionTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_post_form_includes_paid_section_from_membership_plugin(): void
|
||||
public function test_post_form_uses_two_to_one_layout(): void
|
||||
{
|
||||
$schema = Schema::make();
|
||||
PostResource::form($schema);
|
||||
|
||||
// Section 的标题在 heading 而非 label
|
||||
$headings = collect($schema->getComponents())
|
||||
->map(fn ($component) => method_exists($component, 'getHeading') ? $component->getHeading() : $component->getLabel())
|
||||
->all();
|
||||
$roots = $schema->getComponents();
|
||||
$this->assertCount(2, $roots);
|
||||
|
||||
$this->assertContains('付费设置', $headings);
|
||||
[$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'));
|
||||
}
|
||||
|
||||
// 找到付费设置 Section;无 Livewire 环境下用反射读取默认内容槽(childComponents 按 schema key 分组)
|
||||
$section = collect($schema->getComponents())
|
||||
->first(fn ($component) => $component->getHeading() === '付费设置');
|
||||
public function test_post_form_includes_paid_fields_in_access_section(): void
|
||||
{
|
||||
$schema = Schema::make();
|
||||
PostResource::form($schema);
|
||||
|
||||
$property = (new ReflectionClass($section))->getProperty('childComponents');
|
||||
$fieldNames = collect($property->getValue($section)['default'] ?? [])
|
||||
->map(fn ($field) => $field->getName())
|
||||
->all();
|
||||
$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 组件,测试中绕过构造器
|
||||
@@ -93,4 +117,81 @@ class PostFormInjectionTest extends TestCase
|
||||
$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 : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,30 @@ class SablogImportTest extends TestCase
|
||||
$this->assertSame('photo.jpg', $media->file_name);
|
||||
}
|
||||
|
||||
public function test_import_normalizes_highlighter_before_markdown(): void
|
||||
{
|
||||
$pdo = new PDO('sqlite:'.$this->legacyDb);
|
||||
$html = '<p> 不多说,上代码:</p>'
|
||||
.'<div class="codeText"><div class="codeHead">XML/HTML代码</div>'
|
||||
.'<ol class="dp-xml"><li>docker run -d\\</li><li> --name derper</li></ol></div>';
|
||||
$statement = $pdo->prepare('INSERT INTO sablog_articles VALUES (12, 2, 1, ?, ?, ?, ?, ?, 1, 0, ?, 0, 0, 0, 1, 0, ?)');
|
||||
$statement->execute(['还是得derp', '', $html, '', time(), '', '']);
|
||||
|
||||
$this->import(['--convert-markdown' => true]);
|
||||
|
||||
$post = Post::find(12);
|
||||
$this->assertNotNull($post);
|
||||
$this->assertSame('markdown', $post->content_format);
|
||||
$this->assertStringNotContainsString(' ', $post->content);
|
||||
$this->assertStringNotContainsString('codeText', $post->content);
|
||||
|
||||
$this->get('/show-12.shtml')
|
||||
->assertOk()
|
||||
->assertDontSee(' ', false)
|
||||
->assertSee('docker run', false)
|
||||
->assertSee('<pre', false);
|
||||
}
|
||||
|
||||
public function test_import_is_idempotent(): void
|
||||
{
|
||||
$this->import(['--sync-attachments' => true]);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Blog\Support\PageCacheMiddleware;
|
||||
use App\Models\Category;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StickyPreviewTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $admin;
|
||||
|
||||
private Category $category;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed();
|
||||
|
||||
$this->admin = User::query()->where('email', 'admin@laralog.test')->firstOrFail();
|
||||
$this->category = Category::query()->first() ?? Category::create([
|
||||
'name' => '测试分类',
|
||||
'slug' => 'sticky-test-cat',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_sticky_headline_stays_on_top(): void
|
||||
{
|
||||
$headline = $this->makePost('头条文章', Post::STICKY_HEADLINE, now()->subDays(3));
|
||||
$strong = $this->makePost('强置顶文章', Post::STICKY_STRONG, now()->subDay());
|
||||
$normal = $this->makePost('普通文章', Post::STICKY_NONE, now());
|
||||
|
||||
$ids = Post::query()->published()->orderedForList()->pluck('id')->all();
|
||||
|
||||
$this->assertSame(
|
||||
[$headline->id, $strong->id, $normal->id],
|
||||
array_values(array_intersect($ids, [$headline->id, $strong->id, $normal->id]))
|
||||
);
|
||||
|
||||
$headline->refresh();
|
||||
$this->assertTrue($headline->is_sticky);
|
||||
$this->assertSame(Post::STICKY_HEADLINE, $headline->sticky_level);
|
||||
$this->assertSame('置顶头条', $headline->stickyLabel());
|
||||
}
|
||||
|
||||
public function test_guest_cannot_preview_draft_but_editor_can(): void
|
||||
{
|
||||
$draft = $this->makePost('草稿预览', Post::STICKY_NONE, null, 'draft');
|
||||
|
||||
$this->get($draft->url)->assertNotFound();
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->get($draft->url)
|
||||
->assertOk()
|
||||
->assertSee('草稿预览', false)
|
||||
->assertSee('预览中', false);
|
||||
|
||||
$editor = User::factory()->create([
|
||||
'email' => 'editor-preview@laralog.test',
|
||||
]);
|
||||
$editor->assignRole('editor');
|
||||
|
||||
$this->actingAs($editor)
|
||||
->get($draft->url)
|
||||
->assertOk()
|
||||
->assertSee('预览中', false);
|
||||
|
||||
$member = User::factory()->create([
|
||||
'email' => 'member-preview@laralog.test',
|
||||
]);
|
||||
$member->assignRole('member');
|
||||
|
||||
$this->actingAs($member)
|
||||
->get($draft->url)
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_edit_page_has_preview_action(): void
|
||||
{
|
||||
$post = $this->makePost('可预览文章', Post::STICKY_LIGHT, now());
|
||||
|
||||
$this->actingAs($this->admin)
|
||||
->get('/admin/posts/'.$post->id.'/edit')
|
||||
->assertOk()
|
||||
->assertSee('预览', false)
|
||||
->assertSee($post->url, false);
|
||||
}
|
||||
|
||||
public function test_page_cache_entries_store_url(): void
|
||||
{
|
||||
PageCacheMiddleware::flush();
|
||||
|
||||
$this->get('/')->assertOk();
|
||||
|
||||
$entries = PageCacheMiddleware::entries();
|
||||
$this->assertNotEmpty($entries);
|
||||
$this->assertNotSame('', $entries[0]['url']);
|
||||
$this->assertSame(1, PageCacheMiddleware::count());
|
||||
}
|
||||
|
||||
private function makePost(string $title, int $level, mixed $publishedAt, string $status = 'published'): Post
|
||||
{
|
||||
return Post::create([
|
||||
'user_id' => $this->admin->id,
|
||||
'category_id' => $this->category->id,
|
||||
'title' => $title,
|
||||
'slug' => str()->slug($title).'-'.uniqid(),
|
||||
'content' => '内容',
|
||||
'content_format' => 'markdown',
|
||||
'status' => $status,
|
||||
'sticky_level' => $level,
|
||||
'published_at' => $publishedAt,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user