diff --git a/Changelog.md b/Changelog.md index ca6298f2..470b97f1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ ## Unreleased ##### 2024-XX-YY +- Added the `includeVariants()` method to the ProductSearch class - Added the `priceGreaterThan`, `priceLessThan` and `priceBetween` methods to the ProductSearch class by [Matima](https://github.com/mahdirezaei-dev) in [#176](https://github.com/vanilophp/framework/pull/176) - Added the `Macroable` trait to the `ProductSearch` class - Added the unidirectional links feature diff --git a/src/Foundation/Search/ProductSearch.php b/src/Foundation/Search/ProductSearch.php index 1ac99430..8f50f860 100644 --- a/src/Foundation/Search/ProductSearch.php +++ b/src/Foundation/Search/ProductSearch.php @@ -89,8 +89,11 @@ public function withinTaxon(Taxon $taxon): self $query->where('id', $taxon->id); }); - if (null !== $this->variantQuery) { - } + $this->variantQuery?->whereHas('masterProduct', function ($query) use ($taxon) { + $query->whereHas('taxons', function ($query) use ($taxon) { + $query->where('id', $taxon->id); + }); + }); return $this; } @@ -103,6 +106,11 @@ public function orWithinTaxon(Taxon $taxon): self $this->masterProductQuery->orWhereHas('taxons', function ($query) use ($taxon) { $query->where('id', $taxon->id); }); + $this->variantQuery?->orWhereHas('masterProduct', function ($query) use ($taxon) { + $query->whereHas('taxons', function ($query) use ($taxon) { + $query->where('id', $taxon->id); + }); + }); return $this; } @@ -117,6 +125,11 @@ public function withinTaxons(array $taxons): self $this->masterProductQuery->whereHas('taxons', function ($query) use ($taxonIds) { $query->whereIn('id', $taxonIds); }); + $this->variantQuery?->whereHas('masterProduct', function ($query) use ($taxonIds) { + $query->whereHas('taxons', function ($query) use ($taxonIds) { + $query->whereIn('id', $taxonIds); + }); + }); return $this; } @@ -131,6 +144,11 @@ public function orWithinTaxons(array $taxons): self $this->masterProductQuery->orWhereHas('taxons', function ($query) use ($taxonIds) { $query->whereIn('id', $taxonIds); }); + $this->variantQuery?->orWhereHas('masterProduct', function ($query) use ($taxonIds) { + $query->whereHas('taxons', function ($query) use ($taxonIds) { + $query->whereIn('id', $taxonIds); + }); + }); return $this; } @@ -150,6 +168,11 @@ public function withinChannels(Channel ...$channels): self $this->masterProductQuery->whereHas('channels', function ($query) use ($channelIds) { $query->whereIn('channel_id', $channelIds); }); + $this->variantQuery?->whereHas('masterProduct', function ($query) use ($channelIds) { + $query->whereHas('channels', function ($query) use ($channelIds) { + $query->whereIn('channel_id', $channelIds); + }); + }); return $this; } @@ -167,6 +190,7 @@ public function priceBetween(float $min, float $max): self { $this->productQuery->whereBetween('price', [$min, $max]); $this->masterProductQuery->whereBetween('price', [$min, $max]); + $this->variantQuery?->whereBetween('price', [$min, $max]); return $this; } @@ -175,6 +199,7 @@ public function priceGreaterThan(float $min): self { $this->productQuery->where('price', '>', $min); $this->masterProductQuery->where('price', '>', $min); + $this->variantQuery?->where('price', '>', $min); return $this; } @@ -183,6 +208,7 @@ public function priceGreaterThanOrEqualTo(float $min): self { $this->productQuery->where('price', '>=', $min); $this->masterProductQuery->where('price', '>=', $min); + $this->variantQuery?->where('price', '>=', $min); return $this; } @@ -191,6 +217,7 @@ public function priceLessThan(float $max): self { $this->productQuery->where('price', '<', $max); $this->masterProductQuery->where('price', '<', $max); + $this->variantQuery?->where('price', '<', $max); return $this; } @@ -199,6 +226,7 @@ public function priceLessThanOrEqualTo(float $max): self { $this->productQuery->where('price', '<=', $max); $this->masterProductQuery->where('price', '<=', $max); + $this->variantQuery?->where('price', '<=', $max); return $this; } @@ -207,6 +235,7 @@ public function nameStartsWith(string $term): self { $this->productQuery->where('name', 'like', "$term%"); $this->masterProductQuery->where('name', 'like', "$term%"); + $this->variantQuery?->where('name', 'like', "$term%"); return $this; } @@ -215,6 +244,7 @@ public function orNameStartsWith(string $term): self { $this->productQuery->orWhere('name', 'like', "$term%"); $this->masterProductQuery->orWhere('name', 'like', "$term%"); + $this->variantQuery?->orWhere('name', 'like', "$term%"); return $this; } @@ -223,6 +253,7 @@ public function nameEndsWith(string $term): self { $this->productQuery->where('name', 'like', "%$term"); $this->masterProductQuery->where('name', 'like', "%$term"); + $this->variantQuery?->where('name', 'like', "%$term"); return $this; } @@ -235,6 +266,9 @@ public function havingPropertyValue(PropertyValue $propertyValue): self $this->masterProductQuery->whereHas('propertyValues', function ($query) use ($propertyValue) { $query->where('id', $propertyValue->id); }); + $this->variantQuery?->whereHas('propertyValues', function ($query) use ($propertyValue) { + $query->where('id', $propertyValue->id); + }); return $this; } @@ -247,6 +281,9 @@ public function orHavingPropertyValue(PropertyValue $propertyValue): self $this->masterProductQuery->orWhereHas('propertyValues', function ($query) use ($propertyValue) { $query->where('id', $propertyValue->id); }); + $this->variantQuery?->orWhereHas('propertyValues', function ($query) use ($propertyValue) { + $query->where('id', $propertyValue->id); + }); return $this; } @@ -261,6 +298,9 @@ public function havingPropertyValues(array $propertyValues): self $this->masterProductQuery->whereHas('propertyValues', function ($query) use ($propertyValueIds) { $query->whereIn('id', $propertyValueIds); }); + $this->variantQuery?->whereHas('propertyValues', function ($query) use ($propertyValueIds) { + $query->whereIn('id', $propertyValueIds); + }); return $this; } @@ -288,6 +328,9 @@ public function orHavingPropertyValues(array $propertyValues): self $this->masterProductQuery->orWhereHas('propertyValues', function ($query) use ($propertyValueIds) { $query->whereIn('id', $propertyValueIds); }); + $this->variantQuery?->orWhereHas('propertyValues', function ($query) use ($propertyValueIds) { + $query->whereIn('id', $propertyValueIds); + }); return $this; } @@ -319,6 +362,7 @@ public function withImages(): self { $this->productQuery->with('media'); $this->masterProductQuery->with(['media', 'variants.media']); + $this->variantQuery?->with('media'); return $this; } @@ -327,6 +371,7 @@ public function withChannels(): self { $this->productQuery->with('channels'); $this->masterProductQuery->with('channels'); + $this->variantQuery?->with('masterProduct.channels'); return $this; } diff --git a/src/Foundation/Tests/ProductSearchTest.php b/src/Foundation/Tests/ProductSearchTest.php index 3ff251da..9703b93b 100644 --- a/src/Foundation/Tests/ProductSearchTest.php +++ b/src/Foundation/Tests/ProductSearchTest.php @@ -713,4 +713,767 @@ public function it_can_optionally_include_variants() $finder->includeVariants(); $this->assertCount(11, $finder->getResults()); } + + /** @test */ + public function returns_variants_based_on_a_single_taxon() + { + $taxon = factory(Taxon::class)->create(); + factory(Product::class, 5)->create()->each(function (Product $product) use ($taxon) { + $product->addTaxon($taxon); + }); + + $master1 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + $master2 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + // Variants + factory(MasterProductVariant::class, 4)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master1->id, + ]); + + factory(MasterProductVariant::class, 5)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master2->id, + ]); + + $master1->addTaxon($taxon); + + $this->assertCount(6, (new ProductSearch())->withinTaxon($taxon)->getResults()); + $this->assertCount(10, (new ProductSearch())->includeVariants()->withinTaxon($taxon)->getResults()); + } + + /** @test */ + public function returns_variants_based_on_two_taxons_set_in_two_consecutive_calls() + { + // Taxons with products + $taxon1 = factory(Taxon::class)->create(); + factory(Product::class, 1)->create()->each(function (Product $product) use ($taxon1) { + $product->addTaxon($taxon1); + }); + + $taxon2 = factory(Taxon::class)->create(); + factory(Product::class, 2)->create()->each(function (Product $product) use ($taxon2) { + $product->addTaxon($taxon2); + }); + + // Masters + $master1 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + $master2 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + // Variants + factory(MasterProductVariant::class, 3)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master1->id, + ]); + + factory(MasterProductVariant::class, 4)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master2->id, + ]); + + $master1->addTaxon($taxon1); + $master2->addTaxon($taxon2); + + $this->assertCount(5, (new ProductSearch())->withinTaxon($taxon1)->orWithinTaxon($taxon2)->getResults()); + $this->assertCount(12, (new ProductSearch())->includeVariants()->withinTaxon($taxon1)->orWithinTaxon($taxon2)->getResults()); + } + + /** @test */ + public function returns_variants_based_on_several_taxons() + { + // Taxons with products + $taxon1 = factory(Taxon::class)->create(); + factory(Product::class, 1)->create()->each(function (Product $product) use ($taxon1) { + $product->addTaxons([$taxon1]); + }); + + $taxon2 = factory(Taxon::class)->create(); + factory(Product::class, 2)->create()->each(function (Product $product) use ($taxon2) { + $product->addTaxon($taxon2); + }); + + // Masters + $master1 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + $master2 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + // Variants + factory(MasterProductVariant::class, 3)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master1->id, + ]); + + factory(MasterProductVariant::class, 4)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master2->id, + ]); + + $master1->addTaxon($taxon1); + $master2->addTaxon($taxon2); + + $this->assertCount(5, (new ProductSearch())->withinTaxons([$taxon1, $taxon2])->getResults()); + $this->assertCount(12, (new ProductSearch())->includeVariants()->withinTaxons([$taxon1, $taxon2])->getResults()); + } + + /** @test */ + public function returns_variants_based_on_several_taxons_set_in_consecutive_calls() + { + // Taxons with products + $taxon1 = factory(Taxon::class)->create(); + factory(Product::class, 1)->create()->each(function (Product $product) use ($taxon1) { + $product->addTaxons([$taxon1]); + }); + + $taxon2 = factory(Taxon::class)->create(); + factory(Product::class, 2)->create()->each(function (Product $product) use ($taxon2) { + $product->addTaxon($taxon2); + }); + + // Masters + $master1 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + $master2 = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + // Variants + factory(MasterProductVariant::class, 3)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master1->id, + ]); + + factory(MasterProductVariant::class, 4)->create([ + 'state' => ProductState::ACTIVE, + 'master_product_id' => $master2->id, + ]); + + $master1->addTaxon($taxon1); + $master2->addTaxon($taxon2); + + $this->assertCount(5, (new ProductSearch())->withinTaxons([$taxon1])->orWithinTaxons([$taxon2])->getResults()); + $this->assertCount(12, (new ProductSearch())->includeVariants()->withinTaxons([$taxon1])->orWithinTaxons([$taxon2])->getResults()); + } + + /** @test */ + public function it_finds_a_variant_by_exact_name() + { + // Products + factory(Product::class, 10)->create(); + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Just A Master Product', + 'state' => ProductState::ACTIVE, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['master_product_id' => $master->id, 'name' => 'The Infinity Gauntlet'], + ['master_product_id' => $master->id, 'name' => 'Mjölnir'] + ]); + + $withoutVariants = (new ProductSearch())->nameContains('The Infinity Gauntlet')->getResults(); + $this->assertCount(0, $withoutVariants); + + $withVariants = (new ProductSearch())->includeVariants()->nameContains('The Infinity Gauntlet')->getResults(); + $this->assertCount(1, $withVariants); + + $firstWithVariants = $withVariants->first(); + $this->assertInstanceOf(MasterProductVariant::class, $firstWithVariants); + $this->assertEquals('The Infinity Gauntlet', $firstWithVariants->name); + } + + /** @test */ + public function it_finds_multiple_variant_results_where_name_contains_search_term() + { + factory(Product::class, 10)->create(); + factory(Product::class)->createMany([ + ['name' => 'Mandarin'], + ]); + factory(MasterProduct::class)->create(['name' => 'Crazy Mandarins']); + + $master = factory(MasterProduct::class)->create(['name' => 'Apple Fruits']); + factory(MasterProductVariant::class)->createMany([ + ['name' => 'Mandarin Paint', 'master_product_id' => $master->id], + ['name' => 'Mandarin As Language', 'master_product_id' => $master->id], + ['name' => 'Crazy Apples', 'master_product_id' => $master->id] + ]); + + $this->assertCount(2, (new ProductSearch())->nameContains('Mandarin')->getResults()); + $this->assertCount(4, (new ProductSearch())->includeVariants()->nameContains('Mandarin')->getResults()); + } + + /** @test */ + public function it_can_find_variants_by_price_range() + { + // Products + factory(Product::class)->createMany([ + ['price' => 55], + ['price' => 30], + ['price' => 40], + ]); + + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + 'price' => 10, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['price' => 35, 'master_product_id' => $master->id], + ['price' => 33, 'master_product_id' => $master->id], + ['price' => 1, 'master_product_id' => $master->id], + ]); + + $this->assertCount(2, (new ProductSearch())->priceBetween(30, 40)->getResults()); + $this->assertCount(4, (new ProductSearch())->includeVariants()->priceBetween(30, 40)->getResults()); + } + + /** @test */ + public function it_can_find_variants_above_a_given_price() + { + // Products + factory(Product::class)->createMany([ + ['price' => 8], + ['price' => 200], + ['price' => 1999884], + ]); + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + 'price' => 1, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['price' => 7, 'master_product_id' => $master->id], + ['price' => 8, 'master_product_id' => $master->id], + ['price' => 9, 'master_product_id' => $master->id], + ['price' => 10, 'master_product_id' => $master->id], + ]); + + $resultWithoutVariants = (new ProductSearch())->priceGreaterThan(8)->getResults(); + $this->assertCount(2, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch())->includeVariants()->priceGreaterThan(8)->getResults(); + $this->assertCount(4, $resultWithVariants); + + $resultWithVariants->each(fn ($product) => $this->assertGreaterThan(8, $product->price)); + } + + /** @test */ + public function it_can_find_variants_above_or_equal_to_a_certain_price() + { + // Product + factory(Product::class)->create([ + 'price' => 40, + ]); + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + 'price' => 10, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['price' => 35, 'master_product_id' => $master->id], + ['price' => 11, 'master_product_id' => $master->id], + ['price' => 10, 'master_product_id' => $master->id], + ['price' => 99, 'master_product_id' => $master->id], + ]); + + + $resultWithoutVariants = (new ProductSearch())->priceGreaterThanOrEqualTo(35)->getResults(); + $this->assertCount(1, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch())->includeVariants()->priceGreaterThanOrEqualTo(35)->getResults(); + $this->assertCount(3, $resultWithVariants); + + $prices = $resultWithVariants->pluck('price'); + foreach ($prices as $price) { + $this->assertGreaterThanOrEqual(35, $price); + } + } + + /** @test */ + public function it_can_find_variants_below_a_certain_price() + { + // Products + factory(Product::class)->createMany([ + ['price' => 12], + ['price' => 2], + ]); + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + 'price' => 9999, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['price' => 11, 'master_product_id' => $master->id], + ['price' => 10, 'master_product_id' => $master->id], + ['price' => 12, 'master_product_id' => $master->id], + ]); + + $resultWithoutVariants = (new ProductSearch())->priceLessThan(12)->getResults(); + $this->assertCount(1, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch())->includeVariants()->priceLessThan(12)->getResults(); + $this->assertCount(3, $resultWithVariants); + + $prices = $resultWithVariants->pluck('price'); + foreach ($prices as $price) { + $this->assertLessThan(12, $price); + } + } + + /** @test */ + public function it_can_find_variants_below_or_equal_to_a_given_price() + { + // Products + factory(Product::class)->createMany([ + ['price' => 300], + ['price' => 300.01], + ]); + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + 'price' => 9999, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['price' => 300.00, 'master_product_id' => $master->id], + ['price' => 300.01, 'master_product_id' => $master->id], + ['price' => 301.01, 'master_product_id' => $master->id], + ['price' => 301.01, 'master_product_id' => $master->id], + ['price' => 301.02, 'master_product_id' => $master->id], + ['price' => 301.03, 'master_product_id' => $master->id], + ['price' => 301.011, 'master_product_id' => $master->id], + ]); + + $resultWithoutVariants = (new ProductSearch())->priceLessThanOrEqualTo(301.01)->getResults(); + $this->assertCount(2, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch())->includeVariants()->priceLessThanOrEqualTo(301.01)->getResults(); + $this->assertCount(6, $resultWithVariants); + + $resultWithVariants->each(fn ($product) => $this->assertLessThanOrEqual(301.01, $product->price)); + } + + /** @test */ + public function it_finds_a_variant_where_name_begins_with() + { + factory(MasterProductVariant::class, 35)->create(); + factory(Product::class, 3)->create(); + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Cube', + 'state' => ProductState::ACTIVE, + ]); + + factory(MasterProductVariant::class)->createMany([ + ['name' => 'Straw Hat', 'master_product_id' => $master->id], + ['name' => 'Gomu Gomu No Mi', 'master_product_id' => $master->id], + ]); + + $resultWithoutVariants = (new ProductSearch())->nameStartsWith('Straw')->getResults(); + $this->assertCount(0, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch())->includeVariants()->nameStartsWith('Straw')->getResults(); + $this->assertCount(1, $resultWithVariants); + + $first = $resultWithVariants->first(); + $this->assertInstanceOf(MasterProductVariant::class, $first); + $this->assertEquals('Straw Hat', $first->name); + } + + /** @test */ + public function it_finds_products_master_products_and_variants_where_name_begins_with() + { + factory(Product::class, 9)->create(); + factory(Product::class)->create(['name' => 'Matured Cheese']); + factory(MasterProduct::class)->create(['name' => 'Mature People']); + + $master = factory(MasterProduct::class)->create([ + 'name' => 'A Regular Master Product', + 'state' => ProductState::ACTIVE, + ]); + + factory(MasterProductVariant::class)->createMany([ + ['name' => 'Matured Wine', 'master_product_id' => $master->id], + ['name' => 'Cube', 'master_product_id' => $master->id], + ]); + + $resultWithoutVariants = (new ProductSearch())->nameStartsWith('Mature')->getResults(); + $this->assertCount(2, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch())->includeVariants()->nameStartsWith('Mature')->getResults(); + $this->assertCount(3, $resultWithVariants); + + $first = $resultWithVariants->first(); + $this->assertEquals($first instanceof MasterProductVariant ? 'Matured Wine' : 'Mature People', $first->name); + + $second = $resultWithVariants->get(1); + $this->assertEquals($second instanceof MasterProduct ? 'Mature People' : 'Matured Cheese', $second->name); + + $third = $resultWithVariants->last(); + $this->assertEquals($third instanceof MasterProduct ? 'Mature People' : 'Matured Cheese', $third->name); + } + + /** @test */ + public function it_finds_multiple_results_with_variants_where_name_starts_with_search_term() + { + factory(Product::class, 18)->create(); + factory(Product::class)->createMany([ + ['name' => 'Orange Is Good'], + ['name' => 'Orange Is Orange'], + ['name' => 'This Should Not Be Found'], + ]); + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Master', + 'state' => ProductState::ACTIVE, + ]); + + factory(MasterProductVariant::class)->createMany([ + ['name' => 'Oranges From Morocco', 'master_product_id' => $master->id], + ['name' => 'This Orange Should Not Be Found As Well', 'master_product_id' => $master->id], + ]); + + $this->assertCount(2, (new ProductSearch())->nameStartsWith('Orange')->getResults()); + $this->assertCount(3, (new ProductSearch())->includeVariants()->nameStartsWith('Orange')->getResults()); + } + + /** @test */ + public function name_based_finders_can_be_combined_and_return_variants() + { + // Products + factory(Product::class, 21)->create(); + factory(Product::class)->createMany([ + ['name' => 'Waka Time'], + ['name' => 'Kaka Waka'], + ['name' => 'Tugo Waka Batagang'], + ]); + + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Master', + 'state' => ProductState::ACTIVE, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['name' => 'Waka Waka Eh Eh', 'master_product_id' => $master->id], + ['name' => 'Taka Waka', 'master_product_id' => $master->id], + ['name' => 'Aserejé', 'master_product_id' => $master->id], + ]); + + $resultWithoutVariants = (new ProductSearch()) + ->nameEndsWith('Waka') + ->orNameStartsWith('Waka') + ->getResults(); + + $this->assertCount(2, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch()) + ->includeVariants() + ->nameEndsWith('Waka') + ->orNameStartsWith('Waka') + ->getResults(); + + $this->assertCount(4, $resultWithVariants); + } + + /** @test */ + public function it_finds_a_variant_where_name_ends_with() + { + // Products + factory(Product::class, 27)->create(); + factory(Product::class)->create([ + 'name' => 'Bobinated Transformator' + ]); + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Master', + 'state' => ProductState::ACTIVE, + ]); + + // Variant + factory(MasterProductVariant::class)->createMany([ + ['name' => 'High Voltage Transformator', 'master_product_id' => $master->id], + ['name' => 'Something', 'master_product_id' => $master->id], + ]); + + $resultWithoutVariants = (new ProductSearch())->nameEndsWith('Transformator')->getResults(); + $this->assertCount(1, $resultWithoutVariants); + + $resultWithVariants = (new ProductSearch())->includeVariants()->nameEndsWith('Transformator')->getResults(); + $this->assertCount(2, $resultWithVariants); + + $first = $resultWithVariants->first(); + $this->assertInstanceOf(MasterProductVariant::class, $first); + $this->assertEquals('High Voltage Transformator', $first->name); + } + + /** @test */ + public function it_finds_multiple_variants_where_name_ends_with_search_term() + { + // Products + factory(Product::class, 7)->create(); + factory(Product::class)->createMany([ + ['name' => 'Awesome Blueberries'], + ['name' => 'Blueberries Not Here'], + ]); + + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Master', + 'state' => ProductState::ACTIVE, + ]); + + // Variants + factory(MasterProductVariant::class)->createMany([ + ['name' => 'Blueberries Are Blue Berries', 'master_product_id' => $master->id], + ['name' => 'Black Blueberries', 'master_product_id' => $master->id], + ['name' => 'Blueberries', 'master_product_id' => $master->id], + ]); + + $this->assertCount(1, (new ProductSearch())->nameEndsWith('Blueberries')->getResults()); + $this->assertCount(3, (new ProductSearch())->includeVariants()->nameEndsWith('Blueberries')->getResults()); + } + + /** @test */ + public function returns_variants_based_on_a_single_property_value() + { + // Background products without attributes + factory(Product::class, 10)->create(); + + $red = factory(PropertyValue::class)->create([ + 'value' => 'red', + 'title' => 'Red' + ]); + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Master', + 'state' => ProductState::ACTIVE, + ]); + + factory(MasterProductVariant::class, 5)->create([ + 'master_product_id' => $master->id + ])->each(function (MasterProductVariant $variant) use ($red) { + $variant->addPropertyValue($red); + }); + + $finder = new ProductSearch(); + $finder->havingPropertyValue($red); + $this->assertCount(0, $finder->getResults()); + $this->assertCount(5, $finder->includeVariants()->getResults()); + } + + /** @test */ + public function returns_variants_based_on_property_values_and_on_taxons_with_search_terms() + { + // Products without taxons + factory(Product::class, 37)->create(); + + $taxon = factory(Taxon::class)->create(); + factory(Product::class, 19)->create()->each(function (Product $product) use ($taxon) { + $product->addTaxon($taxon); + }); + factory(Product::class, 4)->create([ + 'name' => 'NER Posvany' + ])->each(function (Product $product) use ($taxon) { + $product->addTaxon($taxon); + }); + + $propertyValue = factory(PropertyValue::class)->create(); + factory(Product::class, 7)->create()->each(function (Product $product) use ($propertyValue) { + $product->addPropertyValue($propertyValue); + }); + factory(Product::class, 6)->create([ + 'name' => 'Phillip NER' + ])->each(function (Product $product) use ($propertyValue) { + $product->addPropertyValue($propertyValue); + }); + + factory(Product::class, 11)->create([ + 'name' => 'Phillip NER' + ])->each(function (Product $product) use ($propertyValue, $taxon) { + $product->addTaxon($taxon); + $product->addPropertyValue($propertyValue); + }); + + $master = factory(MasterProduct::class)->create([ + 'name' => 'Master', + 'state' => ProductState::ACTIVE, + ]); + + $master->addTaxon($taxon); + + factory(MasterProductVariant::class, 4)->create([ + 'name' => 'Phillip NER', + 'master_product_id' => $master->id, + ])->each(function (MasterProductVariant $variant) use ($propertyValue) { + $variant->addPropertyValue($propertyValue); + }); + + factory(MasterProductVariant::class, 9)->create([ + 'master_product_id' => $master->id, + ])->each(function (MasterProductVariant $variant) use ($propertyValue) { + $variant->addPropertyValue($propertyValue); + }); + + $this->assertCount(11, (new ProductSearch())->withinTaxon($taxon)->havingPropertyValue($propertyValue)->nameContains('NER')->getResults()); + $this->assertCount(15, (new ProductSearch())->includeVariants()->withinTaxon($taxon)->havingPropertyValue($propertyValue)->nameContains('NER')->getResults()); + } + + /** @test */ + public function returns_variants_based_on_property_values_and_on_taxons() + { + // Products without taxons + factory(Product::class, 90)->create(); + + $taxon = factory(Taxon::class)->create(); + factory(Product::class, 45)->create()->each(function (Product $product) use ($taxon) { + $product->addTaxon($taxon); + }); + + $propertyValue = factory(PropertyValue::class)->create(); + factory(Product::class, 19)->create()->each(function (Product $product) use ($propertyValue) { + $product->addPropertyValue($propertyValue); + }); + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + $master->addTaxon($taxon); + + factory(MasterProductVariant::class, 5)->create([ + 'master_product_id' => $master->id, + ]); + + factory(MasterProductVariant::class, 5)->create([ + 'master_product_id' => $master->id, + ])->each(function (MasterProductVariant $variant) use ($propertyValue) { + $variant->addPropertyValue($propertyValue); + }); + + $this->assertCount(65, (new ProductSearch())->withinTaxon($taxon)->orHavingPropertyValue($propertyValue)->getResults()); + $this->assertCount(75, (new ProductSearch())->includeVariants()->withinTaxon($taxon)->orHavingPropertyValue($propertyValue)->getResults()); + } + + /** @test */ + public function returns_variants_based_on_several_property_values() + { + // Background products without attributes + factory(Product::class, 25)->create(); + + $value1 = factory(PropertyValue::class)->create(); + $value2 = factory(PropertyValue::class)->create(); + + factory(Product::class, 13)->create()->each(function (Product $product) use ($value1) { + $product->addPropertyValue($value1); + }); + + factory(Product::class, 2)->create()->each(function (Product $product) use ($value2) { + $product->addPropertyValue($value2); + }); + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + factory(MasterProductVariant::class, 10)->create([ + 'master_product_id' => $master->id, + ]); + + factory(MasterProductVariant::class, 10)->create([ + 'master_product_id' => $master->id, + ])->each(function (MasterProductVariant $variant) use ($value1) { + $variant->addPropertyValue($value1); + }); + + factory(MasterProductVariant::class, 5)->create([ + 'master_product_id' => $master->id, + ])->each(function (MasterProductVariant $variant) use ($value2) { + $variant->addPropertyValue($value2); + }); + + $this->assertCount(15, (new ProductSearch())->havingPropertyValues([$value1, $value2])->getResults()); + $this->assertCount(30, (new ProductSearch())->includeVariants()->havingPropertyValues([$value1, $value2])->getResults()); + } + + /** @test */ + public function returns_variants_based_on_a_single_property_name_and_several_value_names() + { + // Background products without attributes + factory(Product::class, 10)->create(); + + $property = factory(Property::class)->create([ + 'name' => 'Wheel Size', + 'slug' => 'wheel' + ]); + + $twentyseven = factory(PropertyValue::class)->create([ + 'value' => '27', + 'title' => '27"', + 'property_id' => $property + ]); + + $twentynine = factory(PropertyValue::class)->create([ + 'value' => '29', + 'title' => '29"', + 'property_id' => $property + ]); + + factory(Product::class, 8)->create()->each(function (Product $product) use ($twentyseven) { + $product->addPropertyValue($twentyseven); + }); + + factory(Product::class, 19)->create()->each(function (Product $product) use ($twentynine) { + $product->addPropertyValue($twentynine); + }); + + $master = factory(MasterProduct::class)->create([ + 'state' => ProductState::ACTIVE, + ]); + + factory(MasterProductVariant::class, 10)->create([ + 'master_product_id' => $master->id, + ]); + + factory(MasterProductVariant::class, 5)->create([ + 'master_product_id' => $master->id, + ])->each(function (MasterProductVariant $variant) use ($twentyseven) { + $variant->addPropertyValue($twentyseven); + }); + + factory(MasterProductVariant::class, 10)->create([ + 'master_product_id' => $master->id, + ])->each(function (MasterProductVariant $variant) use ($twentynine) { + $variant->addPropertyValue($twentynine); + }); + + $this->assertCount(27, (new ProductSearch())->havingPropertyValuesByName('wheel', ['27','29'])->getResults()); + $this->assertCount(42, (new ProductSearch())->includeVariants()->havingPropertyValuesByName('wheel', ['27','29'])->getResults()); + } }