admin = User::factory()->admin()->create();
$this->client = User::factory()->individual()->create();
$this->timeline = Timeline::factory()->create(['user_id' => $this->client->id]);
});
// ===========================================
// View & Access Tests
// ===========================================
test('admin can view timeline show page', function () {
$this->actingAs($this->admin)
->get(route('admin.timelines.show', $this->timeline))
->assertOk();
});
test('admin can view timeline with updates', function () {
$update = TimelineUpdate::factory()->create([
'timeline_id' => $this->timeline->id,
'admin_id' => $this->admin->id,
'update_text' => 'First update text here',
]);
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->assertSee('First update text here')
->assertSee($this->admin->full_name);
});
test('non-admin cannot access timeline show page', function () {
$this->actingAs($this->client)
->get(route('admin.timelines.show', $this->timeline))
->assertForbidden();
});
test('guest cannot access timeline show page', function () {
$this->get(route('admin.timelines.show', $this->timeline))
->assertRedirect(route('login'));
});
// ===========================================
// Add Update Tests
// ===========================================
test('admin can add update with valid text', function () {
Notification::fake();
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', 'This is a valid update text with enough characters.')
->call('addUpdate')
->assertHasNoErrors();
expect(TimelineUpdate::where('timeline_id', $this->timeline->id)->count())->toBe(1);
$update = TimelineUpdate::where('timeline_id', $this->timeline->id)->first();
expect($update->update_text)->toContain('This is a valid update text with enough characters.');
expect($update->admin_id)->toBe($this->admin->id);
});
test('admin can add update with minimum 10 characters', function () {
Notification::fake();
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', '1234567890')
->call('addUpdate')
->assertHasNoErrors();
expect(TimelineUpdate::where('timeline_id', $this->timeline->id)->exists())->toBeTrue();
});
test('cannot add update with empty text', function () {
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', '')
->call('addUpdate')
->assertHasErrors(['updateText' => 'required']);
});
test('cannot add update with less than 10 characters', function () {
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', 'short')
->call('addUpdate')
->assertHasErrors(['updateText' => 'min']);
});
test('admin name is automatically recorded when adding update', function () {
Notification::fake();
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', 'This is a valid update text.')
->call('addUpdate')
->assertHasNoErrors();
$update = TimelineUpdate::where('timeline_id', $this->timeline->id)->first();
expect($update->admin_id)->toBe($this->admin->id);
expect($update->admin->id)->toBe($this->admin->id);
});
test('timestamp is automatically recorded when adding update', function () {
Notification::fake();
$this->actingAs($this->admin);
$beforeTime = now()->subSecond();
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', 'This is a valid update text.')
->call('addUpdate')
->assertHasNoErrors();
$update = TimelineUpdate::where('timeline_id', $this->timeline->id)->first();
expect($update->created_at)->not->toBeNull();
expect($update->created_at->isAfter($beforeTime))->toBeTrue();
});
test('update text is cleared after adding update', function () {
Notification::fake();
$this->actingAs($this->admin);
$component = Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', 'This is a valid update text.')
->call('addUpdate')
->assertHasNoErrors();
expect($component->get('updateText'))->toBe('');
});
// ===========================================
// Edit Update Tests
// ===========================================
test('admin can edit existing update', function () {
Notification::fake();
$update = TimelineUpdate::factory()->create([
'timeline_id' => $this->timeline->id,
'admin_id' => $this->admin->id,
'update_text' => 'Original text here.',
]);
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->call('editUpdate', $update->id)
->set('updateText', 'Updated text with new content.')
->call('saveEdit')
->assertHasNoErrors();
$update->refresh();
expect($update->update_text)->toContain('Updated text with new content.');
});
test('edit preserves original created_at timestamp', function () {
Notification::fake();
$update = TimelineUpdate::factory()->create([
'timeline_id' => $this->timeline->id,
'admin_id' => $this->admin->id,
'update_text' => 'Original text here.',
]);
$originalCreatedAt = $update->created_at->toDateTimeString();
$this->actingAs($this->admin);
// Wait a moment to ensure time difference
sleep(1);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->call('editUpdate', $update->id)
->set('updateText', 'Updated text with new content.')
->call('saveEdit')
->assertHasNoErrors();
$update->refresh();
expect($update->created_at->toDateTimeString())->toBe($originalCreatedAt);
});
test('edit updates the updated_at timestamp', function () {
Notification::fake();
$update = TimelineUpdate::factory()->create([
'timeline_id' => $this->timeline->id,
'admin_id' => $this->admin->id,
'update_text' => 'Original text here.',
]);
$originalUpdatedAt = $update->updated_at;
$this->actingAs($this->admin);
// Wait a moment to ensure time difference
sleep(1);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->call('editUpdate', $update->id)
->set('updateText', 'Updated text with new content.')
->call('saveEdit')
->assertHasNoErrors();
$update->refresh();
expect($update->updated_at->gt($originalUpdatedAt))->toBeTrue();
});
test('cannot change admin on edit', function () {
Notification::fake();
$otherAdmin = User::factory()->admin()->create();
$update = TimelineUpdate::factory()->create([
'timeline_id' => $this->timeline->id,
'admin_id' => $otherAdmin->id,
'update_text' => 'Original text here.',
]);
$this->actingAs($this->admin);
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->call('editUpdate', $update->id)
->set('updateText', 'Updated text with new content.')
->call('saveEdit')
->assertHasNoErrors();
$update->refresh();
expect($update->admin_id)->toBe($otherAdmin->id);
});
test('cancel edit clears form', function () {
$update = TimelineUpdate::factory()->create([
'timeline_id' => $this->timeline->id,
'admin_id' => $this->admin->id,
'update_text' => 'Original text here.',
]);
$this->actingAs($this->admin);
$component = Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->call('editUpdate', $update->id)
->call('cancelEdit');
expect($component->get('editingUpdateId'))->toBeNull();
expect($component->get('updateText'))->toBe('');
});
test('edit update loads text into form', function () {
$update = TimelineUpdate::factory()->create([
'timeline_id' => $this->timeline->id,
'admin_id' => $this->admin->id,
'update_text' => 'Original text here.',
]);
$this->actingAs($this->admin);
$component = Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->call('editUpdate', $update->id);
expect($component->get('editingUpdateId'))->toBe($update->id);
expect($component->get('updateText'))->toBe('Original text here.');
});
// ===========================================
// HTML Sanitization Tests
// ===========================================
test('html is sanitized when adding update', function () {
Notification::fake();
$this->actingAs($this->admin);
$maliciousText = 'Valid update text here.';
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->set('updateText', $maliciousText)
->call('addUpdate')
->assertHasNoErrors();
$update = TimelineUpdate::where('timeline_id', $this->timeline->id)->first();
expect($update->update_text)->not->toContain('Updated safe text.';
Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
->call('editUpdate', $update->id)
->set('updateText', $maliciousText)
->call('saveEdit')
->assertHasNoErrors();
$update->refresh();
expect($update->update_text)->not->toContain('