208 lines
8.3 KiB
PHP
208 lines
8.3 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace Tests\Feature\Prompts;
|
||
|
|
|
||
|
|
use App\Models\Prompt;
|
||
|
|
use App\Models\Tag;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
||
|
|
use Tests\TestCase;
|
||
|
|
|
||
|
|
class PromptIndexTest extends TestCase
|
||
|
|
{
|
||
|
|
use RefreshDatabase;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* GET the index page and return its Inertia props.
|
||
|
|
*/
|
||
|
|
private function pageProps(string $url = '/prompts'): array
|
||
|
|
{
|
||
|
|
$response = $this->get($url);
|
||
|
|
$response->assertOk()->assertInertia(fn (Assert $page) => $page->component('Prompts/Index'));
|
||
|
|
|
||
|
|
return $response->inertiaProps();
|
||
|
|
}
|
||
|
|
|
||
|
|
private function names(array $props): array
|
||
|
|
{
|
||
|
|
return array_column($props['prompts']['data'], 'name');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_renders_rows_tags_and_filter_state(): void
|
||
|
|
{
|
||
|
|
$alpha = Tag::query()->create(['name' => 'alpha']);
|
||
|
|
$zeta = Tag::query()->create(['name' => 'zeta']);
|
||
|
|
|
||
|
|
$alphaPrompt = Prompt::factory()->create(['name' => 'Alpha Prompt', 'body' => 'Body A {{x}}']);
|
||
|
|
$zetaPrompt = Prompt::factory()->create(['name' => 'Zeta Prompt', 'description' => null, 'body' => 'Body B']);
|
||
|
|
|
||
|
|
$alphaPrompt->tags()->attach([$alpha->id, $zeta->id]);
|
||
|
|
$zetaPrompt->tags()->attach([$zeta->id]);
|
||
|
|
|
||
|
|
$props = $this->pageProps();
|
||
|
|
|
||
|
|
// Newest first (created_at desc).
|
||
|
|
$this->assertSame(['Zeta Prompt', 'Alpha Prompt'], $this->names($props));
|
||
|
|
$this->assertSame(2, $props['prompts']['total']);
|
||
|
|
$this->assertSame(['alpha', 'zeta'], $props['tags']);
|
||
|
|
$this->assertSame(['search' => null, 'tag' => null, 'favorites' => false], $props['filters']);
|
||
|
|
$this->assertSame('created_at', $props['sort']);
|
||
|
|
$this->assertSame('desc', $props['direction']);
|
||
|
|
|
||
|
|
$row = $props['prompts']['data'][0];
|
||
|
|
$this->assertSame('Zeta Prompt', $row['name']);
|
||
|
|
$this->assertFalse($row['is_favorite']);
|
||
|
|
$this->assertSame(0, $row['usage_count']);
|
||
|
|
$this->assertNull($row['last_used_at']);
|
||
|
|
$this->assertSame(['zeta'], array_column($row['tags'], 'name'));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_search_matches_name_description_and_body_case_insensitively(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->create(['name' => 'Regex Explainer', 'description' => null, 'body' => 'Explain {{regex}}']);
|
||
|
|
Prompt::factory()->create(['name' => 'Unrelated', 'description' => 'notes about regex engines', 'body' => 'x']);
|
||
|
|
Prompt::factory()->create(['name' => 'Python Helper', 'description' => null, 'body' => 'write python code']);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?search=REGEX');
|
||
|
|
|
||
|
|
$this->assertSame(['Unrelated', 'Regex Explainer'], $this->names($props));
|
||
|
|
$this->assertSame('REGEX', $props['filters']['search']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_tag_filter_returns_only_matching_prompts(): void
|
||
|
|
{
|
||
|
|
$coding = Tag::query()->create(['name' => 'coding']);
|
||
|
|
$design = Tag::query()->create(['name' => 'design']);
|
||
|
|
|
||
|
|
$codeReviewer = Prompt::factory()->create(['name' => 'Code Reviewer']);
|
||
|
|
Prompt::factory()->create(['name' => 'No Tags']);
|
||
|
|
$moodboard = Prompt::factory()->create(['name' => 'Moodboard']);
|
||
|
|
|
||
|
|
$codeReviewer->tags()->attach($coding->id);
|
||
|
|
$moodboard->tags()->attach($design->id);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?tag=coding');
|
||
|
|
|
||
|
|
$this->assertSame(['Code Reviewer'], $this->names($props));
|
||
|
|
$this->assertSame('coding', $props['filters']['tag']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_unknown_tag_returns_empty_result_set(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->count(2)->create();
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?tag=does-not-exist');
|
||
|
|
|
||
|
|
$this->assertSame([], $props['prompts']['data']);
|
||
|
|
$this->assertSame(0, $props['prompts']['total']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_favorites_filter_returns_only_favorites(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->favorite()->create(['name' => 'Starred']);
|
||
|
|
Prompt::factory()->create(['name' => 'Plain']);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?favorites=1');
|
||
|
|
|
||
|
|
$this->assertSame(['Starred'], $this->names($props));
|
||
|
|
$this->assertTrue($props['filters']['favorites']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_sort_by_name_uses_asc_default_and_respects_direction(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->create(['name' => 'Charlie']);
|
||
|
|
Prompt::factory()->create(['name' => 'alpha']);
|
||
|
|
Prompt::factory()->create(['name' => 'Bravo']);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=name');
|
||
|
|
$this->assertSame(['Bravo', 'Charlie', 'alpha'], $this->names($props));
|
||
|
|
$this->assertSame('name', $props['sort']);
|
||
|
|
$this->assertSame('asc', $props['direction']);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=name&direction=desc');
|
||
|
|
$this->assertSame(['alpha', 'Charlie', 'Bravo'], $this->names($props));
|
||
|
|
$this->assertSame('desc', $props['direction']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_sort_by_usage_count(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->create(['name' => 'Rare', 'usage_count' => 1]);
|
||
|
|
Prompt::factory()->create(['name' => 'Common', 'usage_count' => 42]);
|
||
|
|
Prompt::factory()->create(['name' => 'Mid', 'usage_count' => 7]);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=usage_count');
|
||
|
|
$this->assertSame(['Common', 'Mid', 'Rare'], $this->names($props));
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=usage_count&direction=asc');
|
||
|
|
$this->assertSame(['Rare', 'Mid', 'Common'], $this->names($props));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_sort_by_last_used_at_keeps_nulls_last_in_both_directions(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->create(['name' => 'Older', 'last_used_at' => '2026-01-01 00:00:00']);
|
||
|
|
Prompt::factory()->create(['name' => 'Recent', 'last_used_at' => '2026-06-01 00:00:00']);
|
||
|
|
Prompt::factory()->create(['name' => 'Never', 'last_used_at' => null]);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=last_used_at&direction=asc');
|
||
|
|
$this->assertSame(['Older', 'Recent', 'Never'], $this->names($props));
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=last_used_at&direction=desc');
|
||
|
|
$this->assertSame(['Recent', 'Older', 'Never'], $this->names($props));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_sort_by_created_at_and_updated_at(): void
|
||
|
|
{
|
||
|
|
// created_at desc would list "Older" first; updated_at desc must not.
|
||
|
|
Prompt::factory()->create(['name' => 'Older', 'created_at' => '2026-09-01 00:00:00', 'updated_at' => '2026-01-01 00:00:00']);
|
||
|
|
Prompt::factory()->create(['name' => 'Newer', 'created_at' => '2026-08-01 00:00:00', 'updated_at' => '2026-05-01 00:00:00']);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts');
|
||
|
|
$this->assertSame(['Older', 'Newer'], $this->names($props));
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=created_at&direction=asc');
|
||
|
|
$this->assertSame(['Newer', 'Older'], $this->names($props));
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=updated_at');
|
||
|
|
$this->assertSame(['Newer', 'Older'], $this->names($props));
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?sort=updated_at&direction=asc');
|
||
|
|
$this->assertSame(['Older', 'Newer'], $this->names($props));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_invalid_sort_and_direction_fall_back_to_defaults(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->count(2)->create();
|
||
|
|
|
||
|
|
$response = $this->get('/prompts?sort=1%3BDROP&direction=upside');
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$props = $response->inertiaProps();
|
||
|
|
$this->assertSame('created_at', $props['sort']);
|
||
|
|
$this->assertSame('desc', $props['direction']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_paginates_ten_per_page(): void
|
||
|
|
{
|
||
|
|
Prompt::factory()->count(12)->create();
|
||
|
|
|
||
|
|
$props = $this->pageProps();
|
||
|
|
$this->assertSame(12, $props['prompts']['total']);
|
||
|
|
$this->assertSame(2, $props['prompts']['last_page']);
|
||
|
|
$this->assertCount(10, $props['prompts']['data']);
|
||
|
|
$this->assertSame(1, $props['prompts']['current_page']);
|
||
|
|
|
||
|
|
$props = $this->pageProps('/prompts?page=2');
|
||
|
|
$this->assertCount(2, $props['prompts']['data']);
|
||
|
|
$this->assertSame(2, $props['prompts']['current_page']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_index_with_no_prompts_shows_empty_state(): void
|
||
|
|
{
|
||
|
|
$props = $this->pageProps();
|
||
|
|
|
||
|
|
$this->assertSame([], $props['prompts']['data']);
|
||
|
|
$this->assertSame(0, $props['prompts']['total']);
|
||
|
|
$this->assertSame([], $props['tags']);
|
||
|
|
}
|
||
|
|
}
|