search and filtering
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
use App\Models\Upvote;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProductSearchAndFilterTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected User $user;
|
||||
protected Category $category1;
|
||||
protected Category $category2;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
$this->category1 = Category::factory()->create(['name' => 'Technology']);
|
||||
$this->category2 = Category::factory()->create(['name' => 'Design']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_search_products_by_title()
|
||||
{
|
||||
$product1 = Product::factory()->create([
|
||||
'title' => 'Laravel Framework',
|
||||
'description' => 'A web application framework',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$product2 = Product::factory()->create([
|
||||
'title' => 'React Library',
|
||||
'description' => 'A JavaScript library',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['search' => 'Laravel']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($product1->title);
|
||||
$response->assertDontSee($product2->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_search_products_by_description()
|
||||
{
|
||||
$product1 = Product::factory()->create([
|
||||
'title' => 'Product One',
|
||||
'description' => 'This is about Laravel framework',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$product2 = Product::factory()->create([
|
||||
'title' => 'Product Two',
|
||||
'description' => 'This is about React library',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['search' => 'Laravel']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($product1->title);
|
||||
$response->assertDontSee($product2->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_filter_products_by_category()
|
||||
{
|
||||
$techProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$designProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category2->id,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['category' => $this->category1->id]));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($techProduct->title);
|
||||
$response->assertDontSee($designProduct->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_filter_products_by_date_range_week()
|
||||
{
|
||||
$oldProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subWeeks(2),
|
||||
]);
|
||||
|
||||
$recentProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subDays(3),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['date_range' => 'week']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($recentProduct->title);
|
||||
$response->assertDontSee($oldProduct->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_filter_products_by_date_range_month()
|
||||
{
|
||||
$oldProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subMonths(2),
|
||||
]);
|
||||
|
||||
$recentProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subDays(15),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['date_range' => 'month']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($recentProduct->title);
|
||||
$response->assertDontSee($oldProduct->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_filter_products_by_date_range_today()
|
||||
{
|
||||
$yesterdayProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subDay(),
|
||||
]);
|
||||
|
||||
$todayProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['date_range' => 'today']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($todayProduct->title);
|
||||
$response->assertDontSee($yesterdayProduct->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_sort_products_by_popularity()
|
||||
{
|
||||
$product1 = Product::factory()->create([
|
||||
'title' => 'Less Popular Product',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$product2 = Product::factory()->create([
|
||||
'title' => 'More Popular Product',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
// Add upvotes to product2
|
||||
Upvote::factory()->count(5)->create(['product_id' => $product2->id]);
|
||||
Upvote::factory()->count(2)->create(['product_id' => $product1->id]);
|
||||
|
||||
$response = $this->get(route('products.index', ['sort' => 'popular']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
// Get the response content and check if product2 appears before product1
|
||||
$content = $response->getContent();
|
||||
$pos1 = strpos($content, $product2->title);
|
||||
$pos2 = strpos($content, $product1->title);
|
||||
|
||||
$this->assertNotFalse($pos1);
|
||||
$this->assertNotFalse($pos2);
|
||||
$this->assertLessThan($pos2, $pos1, 'More popular product should appear first');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_sort_products_by_newest()
|
||||
{
|
||||
$oldProduct = Product::factory()->create([
|
||||
'title' => 'Old Product',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subDays(5),
|
||||
]);
|
||||
|
||||
$newProduct = Product::factory()->create([
|
||||
'title' => 'New Product',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['sort' => 'newest']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
// Get the response content and check if newProduct appears before oldProduct
|
||||
$content = $response->getContent();
|
||||
$pos1 = strpos($content, $newProduct->title);
|
||||
$pos2 = strpos($content, $oldProduct->title);
|
||||
|
||||
$this->assertNotFalse($pos1);
|
||||
$this->assertNotFalse($pos2);
|
||||
$this->assertLessThan($pos2, $pos1, 'Newer product should appear first');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_sort_products_by_oldest()
|
||||
{
|
||||
$oldProduct = Product::factory()->create([
|
||||
'title' => 'Old Product',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subDays(5),
|
||||
]);
|
||||
|
||||
$newProduct = Product::factory()->create([
|
||||
'title' => 'New Product',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['sort' => 'oldest']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
// Get the response content and check if oldProduct appears before newProduct
|
||||
$content = $response->getContent();
|
||||
$pos1 = strpos($content, $oldProduct->title);
|
||||
$pos2 = strpos($content, $newProduct->title);
|
||||
|
||||
$this->assertNotFalse($pos1);
|
||||
$this->assertNotFalse($pos2);
|
||||
$this->assertLessThan($pos2, $pos1, 'Older product should appear first');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_combine_search_and_filters()
|
||||
{
|
||||
$matchingProduct = Product::factory()->create([
|
||||
'title' => 'Laravel Tool',
|
||||
'description' => 'A great tool',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subDays(2),
|
||||
]);
|
||||
|
||||
$nonMatchingProduct1 = Product::factory()->create([
|
||||
'title' => 'Laravel Design',
|
||||
'description' => 'A design tool',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category2->id, // Different category
|
||||
'created_at' => now()->subDays(2),
|
||||
]);
|
||||
|
||||
$nonMatchingProduct2 = Product::factory()->create([
|
||||
'title' => 'React Tool',
|
||||
'description' => 'A different tool',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
'created_at' => now()->subDays(2),
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', [
|
||||
'search' => 'Laravel',
|
||||
'category' => $this->category1->id,
|
||||
'date_range' => 'week',
|
||||
]));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($matchingProduct->title);
|
||||
$response->assertDontSee($nonMatchingProduct1->title);
|
||||
$response->assertDontSee($nonMatchingProduct2->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_only_shows_approved_products_on_public_index()
|
||||
{
|
||||
$approvedProduct = Product::factory()->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$pendingProduct = Product::factory()->create([
|
||||
'status' => 'pending',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$rejectedProduct = Product::factory()->create([
|
||||
'status' => 'rejected',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index'));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee($approvedProduct->title);
|
||||
$response->assertDontSee($pendingProduct->title);
|
||||
$response->assertDontSee($rejectedProduct->title);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_preserves_query_parameters_in_pagination()
|
||||
{
|
||||
// Create enough products to trigger pagination
|
||||
Product::factory()->count(15)->create([
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', [
|
||||
'search' => 'test',
|
||||
'category' => $this->category1->id,
|
||||
'sort' => 'popular',
|
||||
]));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
// Check that pagination links include the query parameters
|
||||
$response->assertSee('search=test');
|
||||
$response->assertSee('category=' . $this->category1->id);
|
||||
$response->assertSee('sort=popular');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_validates_search_and_filter_parameters()
|
||||
{
|
||||
$response = $this->get(route('products.index', [
|
||||
'category' => 99999, // Non-existent category
|
||||
]));
|
||||
|
||||
$response->assertSessionHasErrors('category');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_validates_invalid_sort_parameter()
|
||||
{
|
||||
$response = $this->get(route('products.index', [
|
||||
'sort' => 'invalid_sort',
|
||||
]));
|
||||
|
||||
$response->assertSessionHasErrors('sort');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_validates_invalid_date_range_parameter()
|
||||
{
|
||||
$response = $this->get(route('products.index', [
|
||||
'date_range' => 'invalid_range',
|
||||
]));
|
||||
|
||||
$response->assertSessionHasErrors('date_range');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_shows_appropriate_message_when_no_results_found()
|
||||
{
|
||||
Product::factory()->create([
|
||||
'title' => 'Laravel Tool',
|
||||
'status' => 'approved',
|
||||
'category_id' => $this->category1->id,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('products.index', ['search' => 'nonexistentproduct']));
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertSee('No products found matching your filters');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user