complete story 5.4 with qa tests
This commit is contained in:
parent
77d32d0dae
commit
6eef462732
|
|
@ -0,0 +1,45 @@
|
||||||
|
schema: 1
|
||||||
|
story: "5.4"
|
||||||
|
story_title: "Public Posts Display"
|
||||||
|
gate: PASS
|
||||||
|
status_reason: "All 15 acceptance criteria implemented with comprehensive test coverage (13 tests, 32 assertions). Clean implementation following all project conventions."
|
||||||
|
reviewer: "Quinn (Test Architect)"
|
||||||
|
updated: "2025-12-27T00:00:00Z"
|
||||||
|
|
||||||
|
waiver: { active: false }
|
||||||
|
|
||||||
|
top_issues: []
|
||||||
|
|
||||||
|
quality_score: 100
|
||||||
|
expires: "2026-01-10T00:00:00Z"
|
||||||
|
|
||||||
|
evidence:
|
||||||
|
tests_reviewed: 13
|
||||||
|
risks_identified: 0
|
||||||
|
trace:
|
||||||
|
ac_covered: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||||
|
ac_gaps: []
|
||||||
|
|
||||||
|
nfr_validation:
|
||||||
|
security:
|
||||||
|
status: PASS
|
||||||
|
notes: "Public read-only feature, proper status filtering for unpublished posts"
|
||||||
|
performance:
|
||||||
|
status: PASS
|
||||||
|
notes: "Simple paginated queries, no N+1 issues"
|
||||||
|
reliability:
|
||||||
|
status: PASS
|
||||||
|
notes: "Proper error handling with 404 for invalid/unpublished posts"
|
||||||
|
maintainability:
|
||||||
|
status: PASS
|
||||||
|
notes: "Clean Volt components, locale-aware methods, follows coding standards"
|
||||||
|
|
||||||
|
risk_summary:
|
||||||
|
totals: { critical: 0, high: 0, medium: 0, low: 0 }
|
||||||
|
recommendations:
|
||||||
|
must_fix: []
|
||||||
|
monitor: []
|
||||||
|
|
||||||
|
recommendations:
|
||||||
|
immediate: []
|
||||||
|
future: []
|
||||||
|
|
@ -278,3 +278,136 @@ test('individual post page returns 404 for non-existent posts', function () {
|
||||||
|
|
||||||
**Complexity:** Medium
|
**Complexity:** Medium
|
||||||
**Estimated Effort:** 3-4 hours
|
**Estimated Effort:** 3-4 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dev Agent Record
|
||||||
|
|
||||||
|
### Status
|
||||||
|
**Ready for Review**
|
||||||
|
|
||||||
|
### Agent Model Used
|
||||||
|
Claude Opus 4.5
|
||||||
|
|
||||||
|
### Tasks Completed
|
||||||
|
- [x] Add public posts routes to web.php (posts.index, posts.show)
|
||||||
|
- [x] Create Posts Index Volt component with pagination
|
||||||
|
- [x] Create Posts Show Volt component with 404 for unpublished
|
||||||
|
- [x] Add prose-navy styling to app.css
|
||||||
|
- [x] Add translation strings (read_more) to en/ar
|
||||||
|
- [x] Write feature tests for public posts (13 tests, 32 assertions)
|
||||||
|
- [x] Run Pint and verify all tests pass
|
||||||
|
|
||||||
|
### File List
|
||||||
|
| File | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `routes/web.php` | Modified - Added Volt routes for posts.index and posts.show |
|
||||||
|
| `resources/views/livewire/pages/posts/index.blade.php` | Created - Posts listing Volt component |
|
||||||
|
| `resources/views/livewire/pages/posts/show.blade.php` | Created - Individual post Volt component |
|
||||||
|
| `resources/css/app.css` | Modified - Added prose-navy styling |
|
||||||
|
| `lang/en/posts.php` | Modified - Added read_more translation |
|
||||||
|
| `lang/ar/posts.php` | Modified - Added read_more translation |
|
||||||
|
| `tests/Feature/Public/PostsTest.php` | Created - 13 feature tests |
|
||||||
|
| `resources/views/pages/posts/index.blade.php` | Deleted - Replaced by Volt component |
|
||||||
|
|
||||||
|
### Change Log
|
||||||
|
- Used `#[Layout('components.layouts.public')]` attribute for full-page Volt components
|
||||||
|
- Used `Post::published()->latest()` for reverse chronological order
|
||||||
|
- Used `$post->getTitle()`, `$post->getBody()`, `$post->getExcerpt()` methods for locale-aware content
|
||||||
|
- Used `translatedFormat()` for locale-aware date formatting
|
||||||
|
- Added `wire:navigate` for SPA-like navigation between pages
|
||||||
|
|
||||||
|
### Debug Log References
|
||||||
|
None - implementation completed without issues.
|
||||||
|
|
||||||
|
### Completion Notes
|
||||||
|
- All 13 public posts tests passing
|
||||||
|
- Full test suite (362 tests, 883 assertions) passing
|
||||||
|
- Code formatted with Pint
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QA Results
|
||||||
|
|
||||||
|
### Review Date: 2025-12-27
|
||||||
|
|
||||||
|
### Reviewed By: Quinn (Test Architect)
|
||||||
|
|
||||||
|
### Code Quality Assessment
|
||||||
|
|
||||||
|
**Overall: Excellent** - Implementation is clean, well-structured, and follows all project conventions. The class-based Volt components are properly implemented with appropriate use of Flux UI components, wire:navigate for SPA navigation, and wire:key for list items. The Post model's locale-aware methods (getTitle, getBody, getExcerpt) are used correctly throughout.
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- Clean separation using published() scope
|
||||||
|
- Proper 404 handling for unpublished posts using PostStatus enum
|
||||||
|
- Locale-aware content rendering with fallback to Arabic
|
||||||
|
- Professional prose-navy typography styling
|
||||||
|
- Comprehensive test coverage (13 tests, 32 assertions)
|
||||||
|
|
||||||
|
### Refactoring Performed
|
||||||
|
|
||||||
|
None required - implementation is solid and follows all coding standards.
|
||||||
|
|
||||||
|
### Compliance Check
|
||||||
|
|
||||||
|
- Coding Standards: ✓ Class-based Volt pattern, Flux UI components, proper naming
|
||||||
|
- Project Structure: ✓ Files in correct locations per source-tree.md
|
||||||
|
- Testing Strategy: ✓ Pest tests with comprehensive coverage
|
||||||
|
- All ACs Met: ✓ All 15 acceptance criteria verified
|
||||||
|
|
||||||
|
### Requirements Traceability
|
||||||
|
|
||||||
|
| Acceptance Criteria | Test Coverage |
|
||||||
|
|---------------------|---------------|
|
||||||
|
| Public access (no login required) | ✓ `posts listing page is accessible without authentication` |
|
||||||
|
| Reverse chronological order | ✓ `posts listing page displays in reverse chronological order` |
|
||||||
|
| Post cards show title, date, excerpt, read more | ✓ `posts listing page shows post excerpt` |
|
||||||
|
| Pagination | ✓ `posts listing page paginates results` |
|
||||||
|
| Full post content displayed | ✓ `individual post page shows full content` |
|
||||||
|
| Publication date shown | ✓ Verified in template |
|
||||||
|
| Back to posts link | ✓ `individual post page shows back to posts link` |
|
||||||
|
| Content in current locale | ✓ `posts listing page displays content in current locale` |
|
||||||
|
| Individual post locale support | ✓ `individual post page displays content in current locale` |
|
||||||
|
| Only published posts visible | ✓ `posts listing page shows only published posts` |
|
||||||
|
| 404 for unpublished posts | ✓ `individual post page returns 404 for unpublished posts` |
|
||||||
|
| 404 for non-existent posts | ✓ `individual post page returns 404 for non-existent posts` |
|
||||||
|
| No posts message | ✓ `posts listing page shows no posts message when empty` |
|
||||||
|
|
||||||
|
### Improvements Checklist
|
||||||
|
|
||||||
|
- [x] All acceptance criteria implemented and tested
|
||||||
|
- [x] Bilingual support with locale-aware methods
|
||||||
|
- [x] RTL support via public layout
|
||||||
|
- [x] Professional typography with prose-navy
|
||||||
|
- [x] Responsive design with max-width constraints
|
||||||
|
- [x] SPA-like navigation with wire:navigate
|
||||||
|
- [x] Proper pagination
|
||||||
|
- [x] Code formatted with Pint
|
||||||
|
|
||||||
|
### Security Review
|
||||||
|
|
||||||
|
**Status: PASS** - No security concerns identified.
|
||||||
|
- Public read-only feature with no sensitive data exposure
|
||||||
|
- Proper status check prevents accessing unpublished posts
|
||||||
|
- No user input processed beyond route parameters
|
||||||
|
- Route model binding handles non-existent posts safely
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
|
||||||
|
**Status: PASS** - No performance issues identified.
|
||||||
|
- Simple queries with pagination (10 per page)
|
||||||
|
- No N+1 query issues
|
||||||
|
- Uses eager loading pattern via published() scope
|
||||||
|
- CSS optimized with Tailwind
|
||||||
|
|
||||||
|
### Files Modified During Review
|
||||||
|
|
||||||
|
None - no modifications required.
|
||||||
|
|
||||||
|
### Gate Status
|
||||||
|
|
||||||
|
Gate: **PASS** → docs/qa/gates/5.4-public-posts-display.yml
|
||||||
|
|
||||||
|
### Recommended Status
|
||||||
|
|
||||||
|
✓ **Ready for Done** - All acceptance criteria met, comprehensive test coverage, no issues identified. Implementation follows all project conventions and coding standards.
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ return [
|
||||||
'search_placeholder' => 'البحث في المقالات...',
|
'search_placeholder' => 'البحث في المقالات...',
|
||||||
'last_updated' => 'آخر تحديث',
|
'last_updated' => 'آخر تحديث',
|
||||||
'created' => 'تاريخ الإنشاء',
|
'created' => 'تاريخ الإنشاء',
|
||||||
|
'read_more' => 'اقرأ المزيد',
|
||||||
|
|
||||||
// Create/Edit page
|
// Create/Edit page
|
||||||
'edit_post' => 'تعديل المقال',
|
'edit_post' => 'تعديل المقال',
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ return [
|
||||||
'search_placeholder' => 'Search posts...',
|
'search_placeholder' => 'Search posts...',
|
||||||
'last_updated' => 'Last Updated',
|
'last_updated' => 'Last Updated',
|
||||||
'created' => 'Created',
|
'created' => 'Created',
|
||||||
|
'read_more' => 'Read More',
|
||||||
|
|
||||||
// Create/Edit page
|
// Create/Edit page
|
||||||
'edit_post' => 'Edit Post',
|
'edit_post' => 'Edit Post',
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,19 @@ select:focus[data-flux-control] {
|
||||||
/* \[:where(&)\]:size-4 {
|
/* \[:where(&)\]:size-4 {
|
||||||
@apply size-4;
|
@apply size-4;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
/* Prose Navy styling for blog posts */
|
||||||
|
.prose-navy {
|
||||||
|
--tw-prose-headings: var(--color-navy);
|
||||||
|
--tw-prose-links: var(--color-gold);
|
||||||
|
--tw-prose-bold: var(--color-navy);
|
||||||
|
--tw-prose-body: var(--color-charcoal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-navy a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-navy a:hover {
|
||||||
|
color: var(--color-gold-light);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use Livewire\Attributes\Layout;
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
new #[Layout('components.layouts.public')] class extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
public function with(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'posts' => Post::published()
|
||||||
|
->latest()
|
||||||
|
->paginate(10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}; ?>
|
||||||
|
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<flux:heading size="xl" class="text-navy">{{ __('posts.posts') }}</flux:heading>
|
||||||
|
|
||||||
|
<div class="mt-8 space-y-6">
|
||||||
|
@forelse($posts as $post)
|
||||||
|
<article wire:key="post-{{ $post->id }}" class="bg-white p-6 rounded-lg shadow-sm hover:shadow-md transition-shadow">
|
||||||
|
<h2 class="text-xl font-semibold text-navy">
|
||||||
|
<a href="{{ route('posts.show', $post) }}" class="hover:text-gold" wire:navigate>
|
||||||
|
{{ $post->getTitle() }}
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<time class="text-sm text-charcoal/70 mt-2 block">
|
||||||
|
{{ $post->published_at?->translatedFormat('d F Y') ?? $post->created_at->translatedFormat('d F Y') }}
|
||||||
|
</time>
|
||||||
|
|
||||||
|
<p class="mt-3 text-charcoal">
|
||||||
|
{{ $post->getExcerpt() }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="{{ route('posts.show', $post) }}" class="text-gold hover:underline mt-4 inline-block" wire:navigate>
|
||||||
|
{{ __('posts.read_more') }} →
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
@empty
|
||||||
|
<div class="text-center text-charcoal/70 py-12 bg-white rounded-lg">
|
||||||
|
<flux:icon name="document-text" class="w-12 h-12 mx-auto mb-4 text-charcoal/40" />
|
||||||
|
<p>{{ __('posts.no_posts') }}</p>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
{{ $posts->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\PostStatus;
|
||||||
|
use App\Models\Post;
|
||||||
|
use Livewire\Attributes\Layout;
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new #[Layout('components.layouts.public')] class extends Component
|
||||||
|
{
|
||||||
|
public Post $post;
|
||||||
|
|
||||||
|
public function mount(Post $post): void
|
||||||
|
{
|
||||||
|
// Only show published posts
|
||||||
|
abort_unless($post->status === PostStatus::Published, 404);
|
||||||
|
|
||||||
|
$this->post = $post;
|
||||||
|
}
|
||||||
|
}; ?>
|
||||||
|
|
||||||
|
<article class="max-w-3xl mx-auto">
|
||||||
|
<header class="mb-8">
|
||||||
|
<flux:heading size="xl" class="text-navy">{{ $post->getTitle() }}</flux:heading>
|
||||||
|
|
||||||
|
<time class="text-charcoal/70 mt-2 block">
|
||||||
|
{{ $post->published_at?->translatedFormat('l, d F Y') ?? $post->created_at->translatedFormat('l, d F Y') }}
|
||||||
|
</time>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="prose prose-lg prose-navy max-w-none">
|
||||||
|
{!! $post->getBody() !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="mt-12 pt-6 border-t border-charcoal/20">
|
||||||
|
<a href="{{ route('posts.index') }}" class="text-gold hover:underline" wire:navigate>
|
||||||
|
← {{ __('posts.back_to_posts') }}
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<x-layouts.public>
|
|
||||||
<div class="py-8">
|
|
||||||
<h1 class="text-3xl font-bold text-navy mb-6">{{ __('navigation.posts') }}</h1>
|
|
||||||
<div class="bg-white p-8 rounded-lg shadow-md">
|
|
||||||
<p class="text-charcoal">{{ __('Legal insights and articles will be displayed here.') }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-layouts.public>
|
|
||||||
|
|
@ -15,9 +15,8 @@ Route::get('/booking', function () {
|
||||||
return view('pages.booking');
|
return view('pages.booking');
|
||||||
})->name('booking');
|
})->name('booking');
|
||||||
|
|
||||||
Route::get('/posts', function () {
|
Volt::route('/posts', 'pages.posts.index')->name('posts.index');
|
||||||
return view('pages.posts.index');
|
Volt::route('/posts/{post}', 'pages.posts.show')->name('posts.show');
|
||||||
})->name('posts.index');
|
|
||||||
|
|
||||||
Route::get('/terms', function () {
|
Route::get('/terms', function () {
|
||||||
return view('pages.terms');
|
return view('pages.terms');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
|
||||||
|
test('posts listing page shows only published posts', function () {
|
||||||
|
Post::factory()->published()->create(['title' => ['en' => 'Published Post', 'ar' => 'مقال منشور']]);
|
||||||
|
Post::factory()->draft()->create(['title' => ['en' => 'Draft Post', 'ar' => 'مقال مسودة']]);
|
||||||
|
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Published Post')
|
||||||
|
->assertDontSee('Draft Post');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('posts listing page displays in reverse chronological order', function () {
|
||||||
|
Post::factory()->published()->create([
|
||||||
|
'title' => ['en' => 'Older Post', 'ar' => 'مقال قديم'],
|
||||||
|
'created_at' => now()->subDays(5),
|
||||||
|
'published_at' => now()->subDays(5),
|
||||||
|
]);
|
||||||
|
Post::factory()->published()->create([
|
||||||
|
'title' => ['en' => 'Newer Post', 'ar' => 'مقال جديد'],
|
||||||
|
'created_at' => now(),
|
||||||
|
'published_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSeeInOrder(['Newer Post', 'Older Post']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('posts listing page paginates results', function () {
|
||||||
|
Post::factory()->count(15)->published()->create();
|
||||||
|
|
||||||
|
// First page should show navigation to next page
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Next');
|
||||||
|
|
||||||
|
// Second page should also work
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.index').'?page=2')
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('posts listing page shows no posts message when empty', function () {
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('No posts found.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('posts listing page shows post excerpt', function () {
|
||||||
|
Post::factory()->published()->create([
|
||||||
|
'title' => ['en' => 'Test Post', 'ar' => 'مقال اختباري'],
|
||||||
|
'body' => ['en' => 'This is a test body content that should be displayed as an excerpt.', 'ar' => 'محتوى اختباري'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Test Post')
|
||||||
|
->assertSee('This is a test body content');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('posts listing page is accessible without authentication', function () {
|
||||||
|
Post::factory()->published()->create();
|
||||||
|
|
||||||
|
$this->get(route('posts.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('individual post page shows full content', function () {
|
||||||
|
$post = Post::factory()->published()->create([
|
||||||
|
'title' => ['en' => 'Full Post Title', 'ar' => 'عنوان المقال الكامل'],
|
||||||
|
'body' => ['en' => '<p>Full post body content here.</p>', 'ar' => '<p>محتوى المقال الكامل هنا.</p>'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.show', $post))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Full Post Title')
|
||||||
|
->assertSee('Full post body content here.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('individual post page returns 404 for unpublished posts', function () {
|
||||||
|
$post = Post::factory()->draft()->create();
|
||||||
|
|
||||||
|
$this->get(route('posts.show', $post))
|
||||||
|
->assertNotFound();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('individual post page returns 404 for non-existent posts', function () {
|
||||||
|
$this->get(route('posts.show', 999))
|
||||||
|
->assertNotFound();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('individual post page shows back to posts link', function () {
|
||||||
|
$post = Post::factory()->published()->create();
|
||||||
|
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.show', $post))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Back to Posts');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('individual post page is accessible without authentication', function () {
|
||||||
|
$post = Post::factory()->published()->create();
|
||||||
|
|
||||||
|
$this->get(route('posts.show', $post))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('posts listing page displays content in current locale', function () {
|
||||||
|
Post::factory()->published()->create([
|
||||||
|
'title' => ['en' => 'English Title', 'ar' => 'عنوان عربي'],
|
||||||
|
'body' => ['en' => 'English content', 'ar' => 'محتوى عربي'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Test English
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('English Title');
|
||||||
|
|
||||||
|
// Test Arabic
|
||||||
|
$this->withSession(['locale' => 'ar'])
|
||||||
|
->get(route('posts.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('عنوان عربي');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('individual post page displays content in current locale', function () {
|
||||||
|
$post = Post::factory()->published()->create([
|
||||||
|
'title' => ['en' => 'English Title', 'ar' => 'عنوان عربي'],
|
||||||
|
'body' => ['en' => 'English content', 'ar' => 'محتوى عربي'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Test English
|
||||||
|
$this->withSession(['locale' => 'en'])
|
||||||
|
->get(route('posts.show', $post))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('English Title')
|
||||||
|
->assertSee('English content');
|
||||||
|
|
||||||
|
// Test Arabic
|
||||||
|
$this->withSession(['locale' => 'ar'])
|
||||||
|
->get(route('posts.show', $post))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('عنوان عربي')
|
||||||
|
->assertSee('محتوى عربي');
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue