24-8: articles and legal pages — implemented, QA Review

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Naser Mansour
2026-10-31 18:54:18 +02:00
parent 7879ac7190
commit 2c068277a2
13 changed files with 203 additions and 129 deletions
@@ -0,0 +1,86 @@
<?php
use App\Models\Page;
use App\Models\Post;
use App\Models\User;
use Livewire\Volt\Volt;
test('posts index shows the section head, search and text-only cards', function () {
Post::factory()->published()->count(3)->create();
$html = $this->withSession(['locale' => 'en'])->get('/posts')->assertOk()->getContent();
expect(substr_count($html, '<h1'))->toBe(1);
expect($html)
->toContain('class="sec-head"')
->toContain('wire:model.live.debounce.300ms="search"')
->toContain('class="search"')
->toContain('posts-index');
expect(substr_count($html, 'class="post"'))->toBe(3);
preg_match('~<div class="posts posts-index">(.*?)</section>~s', $html, $block);
expect($block[1])->not->toContain('<img');
});
test('search filters and shows the count line, with a clear action', function () {
Post::factory()->published()->create(['title' => ['en' => 'Alimony guide', 'ar' => 'دليل النفقة']]);
Post::factory()->published()->create(['title' => ['en' => 'Contracts', 'ar' => 'العقود']]);
app()->setLocale('en');
Volt::test('pages.posts.index')->set('search', 'Alimony')
->assertSee('Alimony guide')->assertDontSee('Contracts')
->assertSee(__('posts.search_results', ['count' => 1, 'query' => 'Alimony']))
->assertSee(__('posts.clear_search'))
->set('search', 'zzzz')->assertSee(__('posts.no_results', ['query' => 'zzzz']));
});
test('empty index shows the no posts message', function () {
Volt::test('pages.posts.index')->assertSee(__('posts.no_posts'));
});
test('pagination is wrapped and styled when there are more than ten posts', function () {
Post::factory()->published()->count(12)->create();
expect($this->get('/posts')->assertOk()->getContent())->toContain('pagination-wrap');
});
test('a published post shows meta, h1, body and a back link; unpublished is 404', function () {
$post = Post::factory()->published()->create();
$html = $this->get(route('posts.show', $post))->assertOk()->getContent();
expect($html)->toContain('class="meta"')->toContain('site-prose')->toContain(route('posts.index'))->toContain(__('posts.back_to_posts'));
expect(substr_count($html, '<h1'))->toBe(1);
$draft = Post::factory()->draft()->create();
$this->get(route('posts.show', $draft))->assertNotFound();
});
test('legal pages show title, sanitised content and the last updated line', function () {
Page::factory()->terms()->create(['content_en' => '<p>Hello</p><script>alert(1)</script>', 'content_ar' => '<p>مرحبا</p><script>alert(1)</script>']);
$html = $this->withSession(['locale' => 'en'])->get('/page/terms')->assertOk()->getContent();
expect($html)->toContain('Terms of Service')->toContain('Hello')->not->toContain('<script>alert(1)')->toContain('site-prose')
->toContain(__('pages.last_updated'));
expect(substr_count($html, '<h1'))->toBe(1);
});
test('terms and privacy views render with one h1', function () {
foreach (['terms', 'privacy'] as $v) {
$html = view("pages.$v")->render();
expect(substr_count($html, '<h1'))->toBe(1);
expect($html)->toContain('class="wrap"')->not->toContain('class="wrap py-8"');
}
});
test('admin posts do not load the public foundation', function () {
$html = $this->actingAs(User::factory()->admin()->create())->get('/admin/posts')->assertOk()->getContent();
expect($html)->not->toContain('public.css')->not->toContain('class="site');
});
test('articles css rules exist', function () {
expect(file_get_contents(resource_path('css/public.css')))
->toContain('body.site .posts-index .post:last-child{grid-column:auto}')
->toContain('body.site .pagination-wrap')
->toContain('body.site .site-prose a{color:var(--olive)}');
});