Initial commit

This commit is contained in:
2020-10-07 10:37:15 +02:00
commit ce5f440392
28157 changed files with 4429172 additions and 0 deletions

View File

@@ -0,0 +1,547 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\Module\FacetedSearch\Tests\Adapter;
use Configuration;
use Context;
use Db;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PrestaShop\Module\FacetedSearch\Adapter\MySQL;
use PrestaShop\Module\FacetedSearch\Product\Search;
use Product;
use stdClass;
use StockAvailable;
class MySQLTest extends MockeryTestCase
{
private $adapter;
protected function setUp()
{
$this->adapter = new MySQL();
$mock = Mockery::mock(StockAvailable::class);
$mock->shouldReceive('addSqlShopRestriction')
->with(null, null, 'sa')
->andReturn('');
StockAvailable::setStaticExpectations($mock);
$stdClass = new stdClass();
$stdClass->shop = new stdClass();
$stdClass->shop->id = 1;
$stdClass->language = new stdClass();
$stdClass->language->id = 2;
$stdClass->country = new stdClass();
$stdClass->country->id = 3;
$stdClass->currency = new stdClass();
$stdClass->currency->id = 4;
$contextMock = Mockery::mock(Context::class);
$contextMock->shouldReceive('getContext')
->andReturn($stdClass);
Context::setStaticExpectations($contextMock);
$configurationMock = Mockery::mock(Configuration::class);
$configurationMock->shouldReceive('get')
->with('PS_LAYERED_FILTER_SHOW_OUT_OF_STOCK_LAST')
->andReturn(0);
Configuration::setStaticExpectations($configurationMock);
}
public function testGetEmptyQuery()
{
$this->assertEquals(
'SELECT FROM ps_product p ORDER BY p.id_product DESC LIMIT 0, 20',
$this->adapter->getQuery()
);
}
/**
* @dataProvider oneSelectFieldDataProvider
*/
public function testGetQueryWithOneSelectField($type, $expected)
{
$this->adapter->addSelectField($type);
$this->assertEquals(
$expected,
$this->adapter->getQuery()
);
}
public function testGetMinMaxPriceValue()
{
$dbInstanceMock = Mockery::mock(Db::class)->makePartial();
$dbInstanceMock->shouldReceive('executeS')
->once()
->with('SELECT psi.price_min, MIN(price_min) as min, MAX(price_max) as max FROM ps_product p INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3)')
->andReturn(
[
[
'price_min' => '11',
'min' => '11',
'max' => '35',
],
]
);
$dbMock = Mockery::mock(Db::class)->makePartial();
$dbMock->shouldReceive('getInstance')
->andReturn($dbInstanceMock);
Db::setStaticExpectations($dbMock);
$this->assertEquals(
[11.0, 35.0],
$this->adapter->getMinMaxPriceValue()
);
}
public function testGetMinMaxValueForWeight()
{
$dbInstanceMock = Mockery::mock(Db::class);
$dbInstanceMock->shouldReceive('executeS')
->once()
->with('SELECT MIN(weight) as min, MAX(weight) as max FROM ps_product p')
->andReturn(
[
[
'min' => '10',
'max' => '42',
],
]
);
$dbMock = Mockery::mock(Db::class);
$dbMock->shouldReceive('getInstance')
->andReturn($dbInstanceMock);
Db::setStaticExpectations($dbMock);
$this->assertEquals(
[10.0, 42.0],
$this->adapter->getMinMaxValue('weight')
);
}
public function testCount()
{
$dbInstanceMock = Mockery::mock(Db::class);
$dbInstanceMock->shouldReceive('executeS')
->once()
->with('SELECT COUNT(DISTINCT p.id_product) c FROM ps_product p')
->andReturn(
[
[
'c' => '100',
],
]
);
$dbMock = Mockery::mock(Db::class);
$dbMock->shouldReceive('getInstance')
->andReturn($dbInstanceMock);
Db::setStaticExpectations($dbMock);
$this->assertEquals(
100,
$this->adapter->count()
);
}
public function testValueCount()
{
$dbInstanceMock = Mockery::mock(Db::class);
$dbInstanceMock->shouldReceive('executeS')
->once()
->with('SELECT p.weight, COUNT(DISTINCT p.id_product) c FROM ps_product p GROUP BY p.weight')
->andReturn(
[
[
'weight' => '10',
'c' => '100',
],
]
);
$dbMock = Mockery::mock(Db::class);
$dbMock->shouldReceive('getInstance')
->andReturn($dbInstanceMock);
Db::setStaticExpectations($dbMock);
$this->assertEquals(
[
0 => [
'weight' => '10',
'c' => '100',
],
],
$this->adapter->valueCount('weight')
);
}
public function testValueCountWithInitialPopulation()
{
$this->adapter->useFiltersAsInitialPopulation();
$dbInstanceMock = Mockery::mock(Db::class);
$dbInstanceMock->shouldReceive('executeS')
->once()
->with('SELECT p.id_product, p.weight, COUNT(DISTINCT p.id_product) c FROM (SELECT p.id_product, p.id_manufacturer, SUM(sa.quantity) as quantity, p.condition, p.weight, p.price, psales.quantity as sales FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_sale psales ON (psales.id_product = p.id_product)) p GROUP BY p.weight')
->andReturn(
[
[
'weight' => '10',
'c' => '1000',
],
]
);
$dbMock = Mockery::mock(Db::class);
$dbMock->shouldReceive('getInstance')
->andReturn($dbInstanceMock);
Db::setStaticExpectations($dbMock);
$this->assertEquals(
[
0 => [
'weight' => '10',
'c' => '1000',
],
],
$this->adapter->valueCount('weight')
);
}
public function testValueCountWithInitialPopulationAndStockManagement()
{
$this->adapter->useFiltersAsInitialPopulation();
$this->adapter->getInitialPopulation()->addOperationsFilter(
Search::STOCK_MANAGEMENT_FILTER,
[[['quantity', [0], '>=']]]
);
$dbInstanceMock = Mockery::mock(Db::class);
$dbInstanceMock->shouldReceive('executeS')
->once()
->with('SELECT p.id_product, p.weight, COUNT(DISTINCT p.id_product) c FROM (SELECT p.id_product, p.id_manufacturer, SUM(sa.quantity) as quantity, p.condition, p.weight, p.price, psales.quantity as sales FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_sale psales ON (psales.id_product = p.id_product) WHERE ((sa.quantity>=0))) p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) WHERE ((sa.quantity>=0)) GROUP BY p.weight')
->andReturn(
[
[
'weight' => '10',
'c' => '1000',
],
]
);
$dbMock = Mockery::mock(Db::class);
$dbMock->shouldReceive('getInstance')
->andReturn($dbInstanceMock);
Db::setStaticExpectations($dbMock);
$this->assertEquals(
[
0 => [
'weight' => '10',
'c' => '1000',
],
],
$this->adapter->valueCount('weight')
);
}
public function testGetQueryWithAllSelectField()
{
$this->adapter->setSelectFields(
[
'id_product',
'id_product_attribute',
'id_attribute',
'id_attribute_group',
'id_feature',
'id_shop',
'id_feature_çvalue',
'id_category',
'name',
'nleft',
'nright',
'level_depth',
'out_of_stock',
'quantity',
'price_min',
'price_max',
'range_start',
'range_end',
'id_group',
'manufacturer_name',
]
);
$this->assertEquals(
'SELECT p.id_product, pa.id_product_attribute, pac.id_attribute, a.id_attribute_group, fp.id_feature, ps.id_shop, p.id_feature_çvalue, cp.id_category, pl.name, c.nleft, c.nright, c.level_depth, sa.out_of_stock, SUM(sa.quantity) as quantity, psi.price_min, psi.price_max, psi.range_start, psi.range_end, cg.id_group, m.name FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) INNER JOIN ps_attribute a ON (a.id_attribute = pac.id_attribute) INNER JOIN ps_feature_product fp ON (p.id_product = fp.id_product) INNER JOIN ps_product_shop ps ON (p.id_product = ps.id_product AND ps.id_shop = 1 AND ps.active = TRUE) INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) INNER JOIN ps_product_lang pl ON (p.id_product = pl.id_product AND pl.id_shop = 1 AND pl.id_lang = 2) INNER JOIN ps_category c ON (cp.id_category = c.id_category AND c.active=1) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3) LEFT JOIN ps_category_group cg ON (cg.id_category = c.id_category) INNER JOIN ps_manufacturer m ON (p.id_manufacturer = m.id_manufacturer) ORDER BY p.id_product DESC LIMIT 0, 20',
$this->adapter->getQuery()
);
}
public function testGetQueryWithManyFilters()
{
$this->adapter->setSelectFields(
[
'id_product',
'out_of_stock',
'quantity',
'price_min',
'price_max',
'range_start',
'range_end',
]
);
$this->adapter->addFilter('condition', ['new', 'used'], '=');
$this->adapter->addFilter('weight', [10], '=');
$this->adapter->addFilter('price_min', [10], '>=');
$this->adapter->addFilter('price_min', [100], '<=');
$this->adapter->addFilter('id_product', [2, 20, 200], '=');
$this->assertEquals(
'SELECT p.id_product, sa.out_of_stock, SUM(sa.quantity) as quantity, psi.price_min, psi.price_max, psi.range_start, psi.range_end FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3) WHERE p.condition IN (\'new\', \'used\') AND p.weight=\'10\' AND psi.price_min>=10 AND psi.price_min<=100 AND p.id_product IN (2, 20, 200) ORDER BY p.id_product DESC LIMIT 0, 20',
$this->adapter->getQuery()
);
}
/**
* @dataProvider getManyOperationsFilters
*/
public function testGetQueryWithManyOperationsFilters($fields, $operationsFilter, $expected)
{
$this->adapter->setSelectFields($fields);
$this->adapter->addOperationsFilter(
'out_of_stock_filter',
$operationsFilter
);
$this->assertEquals(
$expected,
$this->adapter->getQuery()
);
}
public function testGetQueryWithGroup()
{
$this->adapter->addSelectField('id_product');
$this->adapter->addGroupBy('id_product');
$this->adapter->addGroupBy('id_feature_value');
$this->adapter->addGroupBy('p.something_defined_by_me');
$this->assertEquals(
'SELECT p.id_product FROM ps_product p GROUP BY p.id_product, fp.id_feature_value, p.something_defined_by_me ORDER BY p.id_product DESC LIMIT 0, 20',
$this->adapter->getQuery()
);
}
public function testGetQueryWithPriceOrderFieldInDesc()
{
$this->adapter->addSelectField('id_product');
$this->adapter->setOrderField('price');
$this->assertEquals(
'SELECT p.id_product FROM ps_product p ORDER BY psi.price_max DESC LIMIT 0, 20',
$this->adapter->getQuery()
);
}
public function testGetQueryWithPriceOrderFieldInAsc()
{
$this->adapter->addSelectField('id_product');
$this->adapter->setOrderField('price');
$this->adapter->setOrderDirection('asc');
$this->assertEquals(
'SELECT p.id_product FROM ps_product p ORDER BY psi.price_min ASC LIMIT 0, 20',
$this->adapter->getQuery()
);
}
public function testGetQueryWithPriceOrderFieldInAscWithInitialPopulation()
{
$this->adapter->addSelectField('manufacturer_name');
$this->adapter->useFiltersAsInitialPopulation();
$this->adapter->setOrderField('manufacturer_name');
$this->adapter->setOrderDirection('asc');
$this->assertEquals(
'SELECT p.id_product FROM (SELECT p.id_product, p.id_manufacturer, SUM(sa.quantity) as quantity, p.condition, p.weight, p.price, psales.quantity as sales, m.name FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_sale psales ON (psales.id_product = p.id_product) INNER JOIN ps_manufacturer m ON (p.id_manufacturer = m.id_manufacturer)) p INNER JOIN ps_manufacturer m ON (p.id_manufacturer = m.id_manufacturer) ORDER BY m.name ASC',
$this->adapter->getQuery()
);
}
public function testGetQueryWithPositionOrderFieldInAscWithInitialPopulation()
{
$this->adapter->addSelectField('id_product');
$this->adapter->useFiltersAsInitialPopulation();
$this->adapter->setOrderField('position');
$this->adapter->setOrderDirection('desc');
$this->assertEquals(
'SELECT p.id_product FROM (SELECT p.id_product, p.id_manufacturer, SUM(sa.quantity) as quantity, p.condition, p.weight, p.price, psales.quantity as sales, cp.position FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_sale psales ON (psales.id_product = p.id_product) INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product)) p INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) ORDER BY p.position DESC',
$this->adapter->getQuery()
);
}
public function testGetQueryWithComputeShowLastEnabled()
{
$configurationMock = Mockery::mock(Configuration::class);
$configurationMock->shouldReceive('get')
->with('PS_LAYERED_FILTER_SHOW_OUT_OF_STOCK_LAST')
->andReturn(true);
Configuration::setStaticExpectations($configurationMock);
$productMock = Mockery::namedMock(Product::class);
$productMock->shouldReceive('isAvailableWhenOutOfStock')
->with(2)
->andReturn(true);
$this->adapter->addSelectField('id_product');
$this->adapter->useFiltersAsInitialPopulation();
$this->adapter->setOrderField('position');
$this->adapter->setOrderDirection('desc');
$this->assertEquals(
'SELECT p.id_product, sa.out_of_stock FROM (SELECT p.id_product, p.id_manufacturer, SUM(sa.quantity) as quantity, p.condition, p.weight, p.price, psales.quantity as sales, cp.position FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_sale psales ON (psales.id_product = p.id_product) INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product)) p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) ORDER BY IFNULL(p.quantity, 0) <= 0, IFNULL(p.quantity, 0) <= 0 AND FIELD(sa.out_of_stock, 0) ASC, p.position DESC',
$this->adapter->getQuery()
);
}
public function testGetQueryWithComputeShowLastEnabledAndDenyOrderOutOfStockProducts()
{
$configurationMock = Mockery::mock(Configuration::class);
$configurationMock->shouldReceive('get')
->with('PS_LAYERED_FILTER_SHOW_OUT_OF_STOCK_LAST')
->andReturn(true);
Configuration::setStaticExpectations($configurationMock);
$productMock = Mockery::namedMock(Product::class);
$productMock->shouldReceive('isAvailableWhenOutOfStock')
->with(2)
->andReturn(false);
$this->adapter->addSelectField('id_product');
$this->adapter->useFiltersAsInitialPopulation();
$this->adapter->setOrderField('position');
$this->adapter->setOrderDirection('desc');
$this->assertEquals(
'SELECT p.id_product, sa.out_of_stock FROM (SELECT p.id_product, p.id_manufacturer, SUM(sa.quantity) as quantity, p.condition, p.weight, p.price, psales.quantity as sales, cp.position FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_sale psales ON (psales.id_product = p.id_product) INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product)) p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) ORDER BY IFNULL(p.quantity, 0) <= 0, IFNULL(p.quantity, 0) <= 0 AND FIELD(sa.out_of_stock, 1) DESC, p.position DESC',
$this->adapter->getQuery()
);
}
public function getManyOperationsFilters()
{
return [
[
'fields' => [
'id_product',
'out_of_stock',
'quantity',
'price_min',
'price_max',
'range_start',
'range_end',
],
'operationsFilter' => [
[
['quantity', [0], '>='],
['out_of_stock', [1, 3, 4], '='],
],
[
['quantity', [0], '>'],
['out_of_stock', [1], '='],
],
],
'expected' => 'SELECT p.id_product, sa.out_of_stock, SUM(sa.quantity) as quantity, psi.price_min, psi.price_max, psi.range_start, psi.range_end FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3) LEFT JOIN ps_stock_available sa_1 ON (p.id_product = sa_1.id_product AND IFNULL(pac.id_product_attribute, 0) = sa_1.id_product_attribute) WHERE ((sa.quantity>=0 AND sa_1.out_of_stock IN (1, 3, 4)) OR (sa.quantity>0 AND sa_1.out_of_stock=1)) ORDER BY p.id_product DESC LIMIT 0, 20',
],
[
'fields' => [
'id_product',
'quantity',
],
'operationsFilter' => [
[
['id_attribute', [2]],
['id_attribute', [4]],
],
[
['quantity', [0], '>'],
['out_of_stock', [1], '='],
],
],
'expected' => 'SELECT p.id_product, SUM(sa.quantity) as quantity FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_attribute_combination pac_1 ON (pa.id_product_attribute = pac_1.id_product_attribute) LEFT JOIN ps_stock_available sa_1 ON (p.id_product = sa_1.id_product AND IFNULL(pac.id_product_attribute, 0) = sa_1.id_product_attribute) WHERE ((pac.id_attribute=2 AND pac_1.id_attribute=4) OR (sa.quantity>0 AND sa_1.out_of_stock=1)) ORDER BY p.id_product DESC LIMIT 0, 20',
],
[
'fields' => [
'id_product',
'quantity',
],
'operationsFilter' => [
[
['id_attribute', [2]],
['id_attribute', [4, 5, 6]],
['id_attribute', [7, 8, 9]],
],
[
['quantity', [0], '>'],
['out_of_stock', [0], '='],
],
],
'expected' => 'SELECT p.id_product, SUM(sa.quantity) as quantity FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) LEFT JOIN ps_product_attribute_combination pac_1 ON (pa.id_product_attribute = pac_1.id_product_attribute) LEFT JOIN ps_product_attribute_combination pac_2 ON (pa.id_product_attribute = pac_2.id_product_attribute) LEFT JOIN ps_stock_available sa_1 ON (p.id_product = sa_1.id_product AND IFNULL(pac.id_product_attribute, 0) = sa_1.id_product_attribute) WHERE ((pac.id_attribute=2 AND pac_1.id_attribute IN (4, 5, 6) AND pac_2.id_attribute IN (7, 8, 9)) OR (sa.quantity>0 AND sa_1.out_of_stock=0)) ORDER BY p.id_product DESC LIMIT 0, 20',
],
];
}
public function oneSelectFieldDataProvider()
{
return [
['id_product', 'SELECT p.id_product FROM ps_product p ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_product_attribute', 'SELECT pa.id_product_attribute FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_attribute', 'SELECT pac.id_attribute FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_attribute_group', 'SELECT a.id_attribute_group FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) INNER JOIN ps_attribute a ON (a.id_attribute = pac.id_attribute) ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_feature', 'SELECT fp.id_feature FROM ps_product p INNER JOIN ps_feature_product fp ON (p.id_product = fp.id_product) ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_shop', 'SELECT ps.id_shop FROM ps_product p INNER JOIN ps_product_shop ps ON (p.id_product = ps.id_product AND ps.id_shop = 1 AND ps.active = TRUE) ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_feature_value', 'SELECT fp.id_feature_value FROM ps_product p LEFT JOIN ps_feature_product fp ON (p.id_product = fp.id_product) ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_category', 'SELECT cp.id_category FROM ps_product p INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) ORDER BY p.id_product DESC LIMIT 0, 20'],
['position', 'SELECT cp.position FROM ps_product p INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) ORDER BY p.id_product DESC LIMIT 0, 20'],
['name', 'SELECT pl.name FROM ps_product p INNER JOIN ps_product_lang pl ON (p.id_product = pl.id_product AND pl.id_shop = 1 AND pl.id_lang = 2) ORDER BY p.id_product DESC LIMIT 0, 20'],
['nleft', 'SELECT c.nleft FROM ps_product p INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) INNER JOIN ps_category c ON (cp.id_category = c.id_category AND c.active=1) ORDER BY p.id_product DESC LIMIT 0, 20'],
['nright', 'SELECT c.nright FROM ps_product p INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) INNER JOIN ps_category c ON (cp.id_category = c.id_category AND c.active=1) ORDER BY p.id_product DESC LIMIT 0, 20'],
['level_depth', 'SELECT c.level_depth FROM ps_product p INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) INNER JOIN ps_category c ON (cp.id_category = c.id_category AND c.active=1) ORDER BY p.id_product DESC LIMIT 0, 20'],
['out_of_stock', 'SELECT sa.out_of_stock FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) ORDER BY p.id_product DESC LIMIT 0, 20'],
['quantity', 'SELECT SUM(sa.quantity) as quantity FROM ps_product p LEFT JOIN ps_product_attribute pa ON (p.id_product = pa.id_product) LEFT JOIN ps_product_attribute_combination pac ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN ps_stock_available sa ON (p.id_product = sa.id_product AND IFNULL(pac.id_product_attribute, 0) = sa.id_product_attribute) ORDER BY p.id_product DESC LIMIT 0, 20'],
['price_min', 'SELECT psi.price_min FROM ps_product p INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3) ORDER BY p.id_product DESC LIMIT 0, 20'],
['price_max', 'SELECT psi.price_max FROM ps_product p INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3) ORDER BY p.id_product DESC LIMIT 0, 20'],
['range_start', 'SELECT psi.range_start FROM ps_product p INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3) ORDER BY p.id_product DESC LIMIT 0, 20'],
['range_end', 'SELECT psi.range_end FROM ps_product p INNER JOIN ps_layered_price_index psi ON (psi.id_product = p.id_product AND psi.id_shop = 1 AND psi.id_currency = 4 AND psi.id_country = 3) ORDER BY p.id_product DESC LIMIT 0, 20'],
['id_group', 'SELECT cg.id_group FROM ps_product p INNER JOIN ps_category_product cp ON (p.id_product = cp.id_product) INNER JOIN ps_category c ON (cp.id_category = c.id_category AND c.active=1) LEFT JOIN ps_category_group cg ON (cg.id_category = c.id_category) ORDER BY p.id_product DESC LIMIT 0, 20'],
['manufacturer_name', 'SELECT m.name FROM ps_product p INNER JOIN ps_manufacturer m ON (p.id_manufacturer = m.id_manufacturer) ORDER BY p.id_product DESC LIMIT 0, 20'],
];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,719 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\Module\FacetedSearch\Tests\Filters;
use Configuration;
use Context;
use Db;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PrestaShop\Module\FacetedSearch\Filters\Converter;
use PrestaShop\PrestaShop\Core\Product\Search\Facet;
use PrestaShop\PrestaShop\Core\Product\Search\Filter;
use Shop;
use stdClass;
use Tools;
class ConverterTest extends MockeryTestCase
{
/** @var Context */
private $contextMock;
/** @var Db */
private $dbMock;
/** @var Block */
private $converter;
protected function setUp()
{
$mock = Mockery::mock(Configuration::class);
$mock->shouldReceive('get')
->andReturnUsing(function ($arg) {
$valueMap = [
'PS_HOME_CATEGORY' => 1,
'PS_WEIGHT_UNIT' => 'kg',
'PS_STOCK_MANAGEMENT' => '1',
'PS_ORDER_OUT_OF_STOCK' => '0',
'PS_UNIDENTIFIED_GROUP' => '1',
'PS_LAYERED_FILTER_CATEGORY_DEPTH' => 3,
];
return $valueMap[$arg];
});
Configuration::setStaticExpectations($mock);
$this->contextMock = Mockery::mock(Context::class);
$this->contextMock->shop = new stdClass();
$this->contextMock->shop->id = 1;
$this->contextMock->language = new stdClass();
$this->contextMock->language->id = 2;
$this->dbMock = Mockery::mock(Db::class);
$toolsMock = Mockery::mock(Tools::class);
$toolsMock->shouldReceive('getValue')
->andReturnUsing(function ($arg) {
$valueMap = [
'id_category' => 12,
'id_category_layered' => 11,
];
return $valueMap[$arg];
});
Tools::setStaticExpectations($toolsMock);
$this->shopMock = Mockery::mock(Shop::class);
Shop::setStaticExpectations($this->shopMock);
$this->converter = new Converter($this->contextMock, $this->dbMock);
}
public function testGetFacetsFromFilterBlocksWithEmptyArray()
{
$this->assertEquals(
[],
$this->converter->getFacetsFromFilterBlocks(
[]
)
);
}
/**
* Test different scenario for facets filter
*
* @dataProvider facetsProvider
*/
public function testGetFacetsFromFilterBlocks($filterBlocks, $expected)
{
$this->assertEquals(
$expected,
$this->converter->getFacetsFromFilterBlocks(
[$filterBlocks]
)
);
}
public function facetsProvider()
{
return [
// Empty
[
[],
[],
],
// Categories
[
[
'type_lite' => 'category',
'type' => 'category',
'id_key' => 0,
'name' => 'Categories',
'values' => [
7 => [
'name' => 'Stationery',
'nbr' => '3',
],
8 => [
'name' => 'Home Accessories',
'nbr' => '8',
'checked' => true,
],
],
'filter_show_limit' => '0',
'filter_type' => Converter::WIDGET_TYPE_RADIO,
],
[
Facet::__set_state(
[
'label' => 'Categories',
'type' => 'category',
'displayed' => true,
'properties' => [
'filter_show_limit' => 0,
],
'filters' => [
Filter::__set_state(
[
'label' => 'Home Accessories',
'type' => 'category',
'active' => true,
'displayed' => true,
'properties' => [],
'magnitude' => 8,
'value' => 8,
'nextEncodedFacets' => [],
]
),
1 => Filter::__set_state(
[
'label' => 'Stationery',
'type' => 'category',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 3,
'value' => 7,
'nextEncodedFacets' => [],
]
),
],
'multipleSelectionAllowed' => false,
'widgetType' => 'radio',
]
),
],
],
// Attribute group
[
[
'type_lite' => 'id_attribute_group',
'type' => 'id_attribute_group',
'id_key' => '2',
'name' => 'Color',
'is_color_group' => true,
'values' => [
8 => [
'name' => 'White',
'nbr' => '3',
'url_name' => null,
'meta_title' => null,
'color' => '#ffffff',
],
11 => [
'name' => 'Black',
'nbr' => '3',
'url_name' => null,
'meta_title' => null,
'color' => '#434A54',
],
12 => [
'name' => 'Weird',
'nbr' => '3',
'url_name' => null,
'meta_title' => null,
'color' => '',
],
],
'url_name' => null,
'meta_title' => null,
'filter_show_limit' => '0',
'filter_type' => Converter::WIDGET_TYPE_DROPDOWN,
],
[
Facet::__set_state(
[
'label' => 'Color',
'type' => 'attribute_group',
'displayed' => true,
'properties' => [
'filter_show_limit' => 0,
'id_attribute_group' => '2',
],
'filters' => [
Filter::__set_state(
[
'label' => 'White',
'type' => 'attribute_group',
'active' => false,
'displayed' => true,
'properties' => [
'color' => '#ffffff',
],
'magnitude' => 3,
'value' => 8,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => 'Black',
'type' => 'attribute_group',
'active' => false,
'displayed' => true,
'properties' => [
'color' => '#434A54',
],
'magnitude' => 3,
'value' => 11,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => 'Weird',
'type' => 'attribute_group',
'active' => false,
'displayed' => true,
'properties' => [
'texture' => '/theme/12.jpg',
],
'magnitude' => 3,
'value' => 12,
'nextEncodedFacets' => [],
]
),
],
'multipleSelectionAllowed' => false,
'widgetType' => 'dropdown',
]
),
],
],
// Feature values
[
[
'type_lite' => 'id_feature',
'type' => 'id_feature',
'id_key' => '2',
'values' => [
5 => [
'nbr' => '2',
'name' => '2',
'url_name' => null,
'meta_title' => null,
],
6 => [
'nbr' => '2',
'name' => '1',
'url_name' => null,
'meta_title' => null,
],
7 => [
'nbr' => '2',
'name' => '2.2',
'url_name' => null,
'meta_title' => null,
],
8 => [
'nbr' => '2',
'name' => '2.1',
'url_name' => null,
'meta_title' => null,
],
9 => [
'nbr' => '3',
'name' => 'Removable cover',
'url_name' => null,
'meta_title' => null,
],
10 => [
'nbr' => '3',
'name' => '120 pages',
'url_name' => null,
'meta_title' => null,
],
],
'name' => 'Property',
'url_name' => null,
'meta_title' => null,
'filter_show_limit' => '0',
'filter_type' => '0',
],
[
Facet::__set_state(
[
'label' => 'Property',
'type' => 'feature',
'displayed' => true,
'properties' => [
'filter_show_limit' => 0,
'id_feature' => '2',
],
'filters' => [
Filter::__set_state(
[
'label' => '1',
'type' => 'feature',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 2,
'value' => 6,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => '2',
'type' => 'feature',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 2,
'value' => 5,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => '2.1',
'type' => 'feature',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 2,
'value' => 8,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => '2.2',
'type' => 'feature',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 2,
'value' => 7,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => '120 pages',
'type' => 'feature',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 3,
'value' => 10,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => 'Removable cover',
'type' => 'feature',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 3,
'value' => 9,
'nextEncodedFacets' => [],
]
),
],
'multipleSelectionAllowed' => true,
'widgetType' => 'checkbox',
]
),
],
],
// Quantity
[
[
'type_lite' => 'quantity',
'type' => 'quantity',
'id_key' => 0, 'name' => 'Availability',
'values' => [
0 => [
'name' => 'Not available',
'nbr' => 0,
],
1 => [
'name' => 'In stock',
'nbr' => 11,
],
],
'filter_show_limit' => '0',
'filter_type' => '0',
],
[
Facet::__set_state(
[
'label' => 'Availability',
'type' => 'availability',
'displayed' => true,
'properties' => [
'filter_show_limit' => 0,
],
'filters' => [
Filter::__set_state(
[
'label' => 'In stock',
'type' => 'availability',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 11,
'value' => 1,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => 'Not available',
'type' => 'availability',
'active' => false,
'displayed' => false,
'properties' => [],
'magnitude' => 0,
'value' => 0,
'nextEncodedFacets' => [],
]
),
],
'multipleSelectionAllowed' => true,
'widgetType' => 'checkbox',
]
),
],
],
// Manufacturer
[
[
'type_lite' => 'manufacturer',
'type' => 'manufacturer',
'id_key' => 0, 'name' => 'Brand',
'values' => [
1 => [
'name' => 'Studio Design',
'nbr' => '7',
],
2 => [
'name' => 'Graphic Corner',
'nbr' => '3',
],
],
'filter_show_limit' => '0',
'filter_type' => '0',
],
[
Facet::__set_state(
[
'label' => 'Brand',
'type' => 'manufacturer',
'displayed' => true,
'properties' => [
'filter_show_limit' => 0,
],
'filters' => [
Filter::__set_state(
[
'label' => 'Graphic Corner',
'type' => 'manufacturer',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 3,
'value' => 2,
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => 'Studio Design',
'type' => 'manufacturer',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 7,
'value' => 1,
'nextEncodedFacets' => [],
]
),
],
'multipleSelectionAllowed' => true,
'widgetType' => 'checkbox',
]
),
],
],
// Condition
[
[
'type_lite' => 'condition',
'type' => 'condition',
'id_key' => 0, 'name' => 'Condition',
'values' => [
'new' => [
'name' => 'New',
'nbr' => '11',
],
'used' => [
'name' => 'Used',
'nbr' => 0,
],
'refurbished' => [
'name' => 'Refurbished',
'nbr' => 0,
],
],
'filter_show_limit' => '0',
'filter_type' => '0',
],
[
Facet::__set_state(
[
'label' => 'Condition',
'type' => 'condition',
'displayed' => true,
'properties' => [
'filter_show_limit' => 0,
],
'filters' => [
Filter::__set_state(
[
'label' => 'New',
'type' => 'condition',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 11,
'value' => 'new',
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => 'Refurbished',
'type' => 'condition',
'active' => false,
'displayed' => false,
'properties' => [],
'magnitude' => 0,
'value' => 'refurbished',
'nextEncodedFacets' => [],
]
),
Filter::__set_state(
[
'label' => 'Used',
'type' => 'condition',
'active' => false,
'displayed' => false,
'properties' => [],
'magnitude' => 0,
'value' => 'used',
'nextEncodedFacets' => [],
]
),
],
'multipleSelectionAllowed' => true,
'widgetType' => 'checkbox',
]
),
],
],
// Price
[
[
'type_lite' => 'price',
'type' => 'price',
'id_key' => 0, 'name' => 'Price',
'max' => 35.0, 'min' => 11.0, 'unit' => '$',
'specifications' => [
'symbol' => [
0 => '.',
1 => ',',
2 => ';',
3 => '%',
4 => '-',
5 => '+',
6 => 'E',
7 => '×',
8 => '‰',
9 => '∞',
10 => 'NaN',
],
'currencyCode' => 'USD',
'currencySymbol' => '$',
'positivePattern' => '¤#,##0.00',
'negativePattern' => '-¤#,##0.00',
'maxFractionDigits' => 2,
'minFractionDigits' => 2,
'groupingUsed' => true,
'primaryGroupSize' => 3,
'secondaryGroupSize' => 3,
],
'filter_show_limit' => 0,
'filter_type' => 3, 'nbr' => '11',
'value' => null,
],
[
Facet::__set_state(
[
'label' => 'Price',
'type' => 'price',
'displayed' => true,
'properties' => [
'filter_show_limit' => 0,
'min' => 11.0,
'max' => 35.0,
'unit' => '$',
'specifications' => [
'symbol' => [
0 => '.',
1 => ',',
2 => ';',
3 => '%',
4 => '-',
5 => '+',
6 => 'E',
7 => '×',
8 => '‰',
9 => '∞',
10 => 'NaN',
],
'currencyCode' => 'USD',
'currencySymbol' => '$',
'positivePattern' => '¤#,##0.00',
'negativePattern' => '-¤#,##0.00',
'maxFractionDigits' => 2,
'minFractionDigits' => 2,
'groupingUsed' => true,
'primaryGroupSize' => 3,
'secondaryGroupSize' => 3,
],
'range' => true,
],
'filters' => [
Filter::__set_state(
[
'label' => '',
'type' => 'price',
'active' => false,
'displayed' => true,
'properties' => [
'symbol' => '$',
],
'magnitude' => 11,
'value' => null,
'nextEncodedFacets' => [],
]
),
],
'multipleSelectionAllowed' => false,
'widgetType' => 'slider',
]
),
],
],
];
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\Module\FacetedSearch\Tests;
use Context;
use Db;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PrestaShop\Module\FacetedSearch\HookDispatcher;
use Ps_Facetedsearch;
class HookDispatcherTest extends MockeryTestCase
{
private $module;
private $dispatcher;
protected function setUp()
{
$this->module = Mockery::mock(Ps_Facetedsearch::class);
$contextMock = Mockery::mock(Context::class);
$dbMock = Mockery::mock(Db::class);
$this->module->shouldReceive('getDatabase')
->andReturn($dbMock);
$this->module->shouldReceive('getContext')
->andReturn($contextMock);
$this->dispatcher = new HookDispatcher($this->module);
}
public function testGetAvailableHooks()
{
$this->assertCount(27, $this->dispatcher->getAvailableHooks());
$this->assertEquals(
[
'actionAttributeGroupDelete',
'actionAttributeSave',
'displayAttributeForm',
'actionAttributePostProcess',
'actionAttributeGroupDelete',
'actionAttributeGroupSave',
'displayAttributeGroupForm',
'displayAttributeGroupPostProcess',
'actionCategoryAdd',
'actionCategoryDelete',
'actionCategoryUpdate',
'displayLeftColumn',
'actionFeatureSave',
'actionFeatureDelete',
'displayFeatureForm',
'displayFeaturePostProcess',
'actionFeatureFormBuilderModifier',
'actionAfterCreateFeatureFormHandler',
'actionAfterUpdateFeatureFormHandler',
'actionFeatureValueSave',
'actionFeatureValueDelete',
'displayFeatureValueForm',
'displayFeatureValuePostProcess',
'actionProductSave',
'productSearchProvider',
'actionObjectSpecificPriceRuleUpdateBefore',
'actionAdminSpecificPriceRuleControllerSaveAfter',
],
$this->dispatcher->getAvailableHooks()
);
}
public function testDispatchUnfoundHook()
{
$this->module->shouldReceive('renderWidget')
->once()
->with('ThisHookDoesNotExists', [])
->andReturn('');
$this->assertEquals('', $this->dispatcher->dispatch('ThisHookDoesNotExists'));
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\PrestaShop\Core\Product\Search;
interface FacetsRendererInterface
{
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\PrestaShop\Core\Product\Search;
interface ProductSearchProviderInterface
{
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\PrestaShop\Core\Module;
interface WidgetInterface
{
}

View File

@@ -0,0 +1,254 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\PrestaShop\Core\Product\Search;
/**
* We call a facet a set of filters combined with logical operators.
*/
class Facet
{
/**
* @var string the facet label
*/
private $label = '';
/**
* @var string the facet type
*/
private $type = '';
/**
* @var bool if true, the facet is displayed
*/
private $displayed = true;
/**
* @var array the facet properties
*/
private $properties = [];
/**
* @var array the facet filters
*/
private $filters = [];
/**
* @var bool if true, allows the multiple selection
*/
private $multipleSelectionAllowed = true;
/**
* @var string the widget type
*/
private $widgetType = 'radio';
/**
* @return array an array representation of the facet
*/
public function toArray()
{
return [
'label' => $this->label,
'displayed' => $this->displayed,
'type' => $this->type,
'properties' => $this->properties,
'filters' => array_map(function (Filter $filter) {
return $filter->toArray();
}, $this->filters),
'multipleSelectionAllowed' => $this->multipleSelectionAllowed,
'widgetType' => $this->widgetType,
];
}
/**
* @param string $label the facet label
*
* @return $this
*/
public function setLabel($label)
{
$this->label = $label;
return $this;
}
/**
* @return string the facet label
*/
public function getLabel()
{
return $this->label;
}
/**
* @param string $type the facet type
*
* @return $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* @return string the facet type
*/
public function getType()
{
return $this->type;
}
/**
* @param string $name the facet property name
* @param mixed $value the facet property value
*
* @return $this
*/
public function setProperty($name, $value)
{
$this->properties[$name] = $value;
return $this;
}
/**
* @param string $name the facet property name
*
* @return mixed|null
*/
public function getProperty($name)
{
if (!array_key_exists($name, $this->properties)) {
return null;
}
return $this->properties[$name];
}
/**
* @param Filter $filter the facet filter
*
* @return $this
*/
public function addFilter(Filter $filter)
{
$this->filters[] = $filter;
return $this;
}
/**
* @return array the list of facet filters
*/
public function getFilters()
{
return $this->filters;
}
/**
* @param bool $isAllowed allows/disallows the multiple selection
*
* @return $this
*/
public function setMultipleSelectionAllowed($isAllowed = true)
{
$this->multipleSelectionAllowed = $isAllowed;
return $this;
}
/**
* @return bool returns true if multiple selection is allowed
*/
public function isMultipleSelectionAllowed()
{
return $this->multipleSelectionAllowed;
}
/**
* @param bool $displayed sets the display of the facet
*
* @return $this
*/
public function setDisplayed($displayed = true)
{
$this->displayed = $displayed;
return $this;
}
/**
* @return bool returns true if the facet is displayed
*/
public function isDisplayed()
{
return $this->displayed;
}
/**
* @param string $widgetType sets the widget type of the facet
*
* @return $this
*/
public function setWidgetType($widgetType)
{
$this->widgetType = $widgetType;
return $this;
}
/**
* @return string returns the facet widget type
*/
public function getWidgetType()
{
return $this->widgetType;
}
/**
* Functions created for testing
*/
public function setProperties(array $data)
{
$this->properties = $data;
}
public function setFilters(array $data)
{
$this->filters = $data;
}
public static function __set_state($data)
{
$obj = new self();
$obj->setLabel($data['label']);
$obj->setDisplayed($data['displayed']);
$obj->setType($data['type']);
$obj->setProperties($data['properties']);
$obj->setFilters($data['filters']);
$obj->setMultipleSelectionAllowed($data['multipleSelectionAllowed']);
$obj->setWidgetType($data['widgetType']);
return $obj;
}
}

View File

@@ -0,0 +1,271 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\PrestaShop\Core\Product\Search;
class Filter
{
/**
* @var string the filter label
*/
private $label = '';
/**
* @var string internal type, used by query logic
*/
private $type = '';
/**
* @var bool whether or not the filter is used in the query
*/
private $active = false;
/**
* @var bool whether or not the filter is displayed
*/
private $displayed = true;
/**
* @var array the filter properties
*/
private $properties = [];
/**
* @var int the filter magnitude
*/
private $magnitude = 0;
/**
* @var mixed the filter value
*/
private $value;
/**
* @var array the filter next encoded facets
*/
private $nextEncodedFacets = [];
/**
* @return array an array representation of the filter
*/
public function toArray()
{
return [
'label' => $this->label,
'type' => $this->type,
'active' => $this->active,
'displayed' => $this->displayed,
'properties' => $this->properties,
'magnitude' => $this->magnitude,
'value' => $this->value,
'nextEncodedFacets' => $this->nextEncodedFacets,
];
}
/**
* @param string $label the filter label
*
* @return $this
*/
public function setLabel($label)
{
$this->label = $label;
return $this;
}
/**
* @return string the filter label
*/
public function getLabel()
{
return $this->label;
}
/**
* @param string $type the filter type
*
* @return $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* @return string the filter type
*/
public function getType()
{
return $this->type;
}
/**
* @param string $name the filter property name
* @param mixed $value the filter property value
*
* @return $this
*/
public function setProperty($name, $value)
{
$this->properties[$name] = $value;
return $this;
}
/**
* @param string $name the filter property name
*
* @return mixed|null
*/
public function getProperty($name)
{
if (!array_key_exists($name, $this->properties)) {
return null;
}
return $this->properties[$name];
}
/**
* @param $value
*
* @return $this
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* @param int $magnitude the filter magnitude
*
* @return $this
*/
public function setMagnitude($magnitude)
{
$this->magnitude = (int) $magnitude;
return $this;
}
/**
* @return int the filter magnitude
*/
public function getMagnitude()
{
return $this->magnitude;
}
/**
* @param bool $active sets the activation of the filter
*
* @return $this
*/
public function setActive($active = true)
{
$this->active = $active;
return $this;
}
/**
* @return bool returns true if the filter is active
*/
public function isActive()
{
return $this->active;
}
/**
* @param bool $displayed sets the display of the filter
*
* @return $this
*/
public function setDisplayed($displayed = true)
{
$this->displayed = $displayed;
return $this;
}
/**
* @return bool returns true if the filter is displayed
*/
public function isDisplayed()
{
return $this->displayed;
}
/**
* @param $nextEncodedFacets
*
* @return $this
*/
public function setNextEncodedFacets($nextEncodedFacets)
{
$this->nextEncodedFacets = $nextEncodedFacets;
return $this;
}
/**
* @return array
*/
public function getNextEncodedFacets()
{
return $this->nextEncodedFacets;
}
/**
* Functions created for testing
*/
public function setProperties(array $data)
{
$this->properties = $data;
}
public static function __set_state($data)
{
$obj = new self();
$obj->setLabel($data['label']);
$obj->setDisplayed($data['displayed']);
$obj->setType($data['type']);
$obj->setProperties($data['properties']);
$obj->setActive($data['active']);
$obj->setMagnitude($data['magnitude']);
$obj->setValue($data['value']);
$obj->setNextEncodedFacets($data['nextEncodedFacets']);
return $obj;
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
class MockProxy
{
protected static $mock;
/**
* Set static expectations
*
* @param mixed $mock
*/
public static function setStaticExpectations($mock)
{
static::$mock = $mock;
}
/**
* Any static calls we get are passed along to self::$mock. public static
*
* @param string $name
* @param mixed $args
*
* @return mixed
*/
public static function __callStatic($name, $args)
{
return call_user_func_array(
[static::$mock, $name],
$args
);
}
}
class StockAvailable extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Context extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Db extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Configuration extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Tools extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Category extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
public $id = null;
}
class Group extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Manufacturer extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Combination extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Shop extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Feature extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class FeatureValue extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}
class Module extends MockProxy
{
// Redeclare to use this instead MockProxy::mock
protected static $mock;
}

View File

@@ -0,0 +1,408 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\Module\FacetedSearch\Tests\Product;
use Configuration;
use Context;
use Db;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PrestaShop\Module\FacetedSearch\Filters\Converter;
use PrestaShop\Module\FacetedSearch\Product\SearchProvider;
use PrestaShop\Module\FacetedSearch\URLSerializer;
use PrestaShop\PrestaShop\Core\Product\Search\Facet;
use PrestaShop\PrestaShop\Core\Product\Search\FacetCollection;
use PrestaShop\PrestaShop\Core\Product\Search\Filter;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchContext;
use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchResult;
use PrestaShop\PrestaShop\Core\Product\Search\SortOrder;
use Ps_Facetedsearch;
use Smarty;
use Tools;
class SearchProviderTest extends MockeryTestCase
{
/**
* @var Search
*/
private $provider;
/**
* @var Db
*/
private $database;
/**
* @var Context
*/
private $context;
/**
* @var URLSerializer
*/
private $serializer;
/**
* @var FacetCollection
*/
private $facetCollection;
/**
* @var Ps_Facetedsearch
*/
private $module;
private function mockFacet($label, $data = ['filters' => []], $widgetType = 'checkbox')
{
$facet = Mockery::mock(Facet::class);
$facet->shouldReceive('getLabel')
->andReturn($label);
$facet->shouldReceive('toArray')
->andReturn($data);
$facet->shouldReceive('getWidgetType')
->andReturn($widgetType);
return $facet;
}
private function mockFilter($label, $active = false, $value = null, $properties = [])
{
$filter = Mockery::mock(Filter::class);
$filter->shouldReceive('getLabel')
->andReturn($label);
$filter->shouldReceive('isActive')
->andReturn($active);
if ($value !== null) {
$filter->shouldReceive('getValue')
->andReturn($value);
}
$filter->shouldReceive('getProperty')
->andReturnUsing(
function ($arg) use ($properties) {
return $properties[$arg];
}
);
return $filter;
}
protected function setUp()
{
$this->database = Mockery::mock(Db::class);
$this->context = Mockery::mock(Context::class);
$this->converter = Mockery::mock(Converter::class);
$this->serializer = Mockery::mock(URLSerializer::class);
$this->facetCollection = Mockery::mock(FacetCollection::class);
$this->module = Mockery::mock(Ps_Facetedsearch::class);
$this->module->shouldReceive('getDatabase')
->andReturn($this->database);
$this->module->shouldReceive('getContext')
->andReturn($this->context);
$this->module->shouldReceive('isAjax')
->andReturn(true);
$mock = Mockery::mock(Configuration::class);
$mock->shouldReceive('get')
->andReturnUsing(function ($arg) {
$valueMap = [
'PS_LAYERED_SHOW_QTIES' => true,
];
return $valueMap[$arg];
});
Configuration::setStaticExpectations($mock);
$toolsMock = Mockery::mock(Tools::class);
$toolsMock->shouldReceive('getCurrentUrlProtocolPrefix')
->andReturn('http://');
Tools::setStaticExpectations($toolsMock);
$this->provider = new SearchProvider(
$this->module,
$this->converter,
$this->serializer
);
}
public function testRenderFacetsWithoutFacetsCollection()
{
$productContext = Mockery::mock(ProductSearchContext::class);
$productSearchResult = Mockery::mock(ProductSearchResult::class);
$productSearchResult->shouldReceive('getFacetCollection')
->once()
->andReturn(null);
$this->assertEquals(
'',
$this->provider->renderFacets(
$productContext,
$productSearchResult
)
);
}
public function testRenderFacetsWithFacetsCollection()
{
$productContext = Mockery::mock(ProductSearchContext::class);
$smarty = Mockery::mock(Smarty::class);
$smarty->shouldReceive('assign')
->once()
->with(
[
'show_quantities' => true,
'facets' => [
[
'filters' => [],
],
],
'js_enabled' => true,
'displayedFacets' => [],
'activeFilters' => [],
'sort_order' => 'product.position.asc',
'clear_all_link' => 'http://shop.prestashop.com/catalog?from=scratch',
]
);
$this->context->smarty = $smarty;
$sortOrder = Mockery::mock(SortOrder::class);
$sortOrder->shouldReceive('toString')
->once()
->andReturn('product.position.asc');
$productSearchResult = Mockery::mock(ProductSearchResult::class);
$productSearchResult->shouldReceive('getFacetCollection')
->once()
->andReturn($this->facetCollection);
$productSearchResult->shouldReceive('getCurrentSortOrder')
->once()
->andReturn($sortOrder);
$facet = $this->mockFacet('Test');
$this->facetCollection->shouldReceive('getFacets')
->once()
->andReturn(
[
$facet,
]
);
$this->module->shouldReceive('fetch')
->once()
->with(
'module:ps_facetedsearch/views/templates/front/catalog/facets.tpl'
)
->andReturn('');
$this->assertEquals(
'',
$this->provider->renderFacets(
$productContext,
$productSearchResult
)
);
}
public function testRenderFacetsWithFacetsCollectionAndFilters()
{
$productContext = Mockery::mock(ProductSearchContext::class);
$smarty = Mockery::mock(Smarty::class);
$smarty->shouldReceive('assign')
->once()
->with(
[
'show_quantities' => true,
'facets' => [
[
'displayed' => true,
'filters' => [
[
'label' => 'Men',
'type' => 'category',
'nextEncodedFacets' => 'Categories-Men',
'active' => false,
'facetLabel' => 'Test',
'nextEncodedFacetsURL' => 'http://shop.prestashop.com/catalog?from=scratch&q=Categories-Men',
],
[
'label' => 'Women',
'type' => 'category',
'nextEncodedFacets' => '',
'active' => true,
'facetLabel' => 'Test',
'nextEncodedFacetsURL' => 'http://shop.prestashop.com/catalog?from=scratch&page=1',
],
],
],
[
'displayed' => true,
'filters' => [
[
'label' => '£22.00 - £35.00',
'type' => 'price',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 2,
'value' => 0,
'nextEncodedFacets' => '',
'facetLabel' => 'Price',
'nextEncodedFacetsURL' => 'http://shop.prestashop.com/catalog?from=scratch',
],
],
],
],
'js_enabled' => true,
'displayedFacets' => [
[
'displayed' => true,
'filters' => [
[
'label' => 'Men',
'type' => 'category',
'nextEncodedFacets' => 'Categories-Men',
'active' => false,
'facetLabel' => 'Test',
'nextEncodedFacetsURL' => 'http://shop.prestashop.com/catalog?from=scratch&q=Categories-Men',
],
[
'label' => 'Women',
'type' => 'category',
'nextEncodedFacets' => '',
'active' => true,
'facetLabel' => 'Test',
'nextEncodedFacetsURL' => 'http://shop.prestashop.com/catalog?from=scratch&page=1',
],
],
],
[
'displayed' => true,
'filters' => [
[
'label' => '£22.00 - £35.00',
'type' => 'price',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 2,
'value' => 0,
'nextEncodedFacets' => '',
'facetLabel' => 'Price',
'nextEncodedFacetsURL' => 'http://shop.prestashop.com/catalog?from=scratch',
],
],
],
],
'activeFilters' => [
[
'label' => 'Women',
'type' => 'category',
'nextEncodedFacets' => '',
'active' => true,
'facetLabel' => 'Test',
'nextEncodedFacetsURL' => 'http://shop.prestashop.com/catalog?from=scratch&page=1',
],
],
'sort_order' => 'product.position.asc',
'clear_all_link' => 'http://shop.prestashop.com/catalog?from=scratch',
]
);
$this->context->smarty = $smarty;
$sortOrder = Mockery::mock(SortOrder::class);
$sortOrder->shouldReceive('toString')
->once()
->andReturn('product.position.asc');
$productSearchResult = Mockery::mock(ProductSearchResult::class);
$productSearchResult->shouldReceive('getFacetCollection')
->once()
->andReturn($this->facetCollection);
$productSearchResult->shouldReceive('getCurrentSortOrder')
->once()
->andReturn($sortOrder);
$facet = $this->mockFacet(
'Test',
[
'displayed' => true,
'filters' => [
[
'label' => 'Men',
'type' => 'category',
'nextEncodedFacets' => 'Categories-Men',
'active' => false,
],
[
'label' => 'Women',
'type' => 'category',
'nextEncodedFacets' => '',
'active' => true,
],
],
]
);
$facetSlider = $this->mockFacet(
'Price',
[
'displayed' => true,
'filters' => [
[
'label' => '£22.00 - £35.00',
'type' => 'price',
'active' => false,
'displayed' => true,
'properties' => [],
'magnitude' => 2,
'value' => 0,
'nextEncodedFacets' => '',
],
],
],
'slider'
);
$this->facetCollection->shouldReceive('getFacets')
->once()
->andReturn(
[
$facet,
$facetSlider,
]
);
$this->module->shouldReceive('fetch')
->once()
->with(
'module:ps_facetedsearch/views/templates/front/catalog/facets.tpl'
)
->andReturn('');
$this->assertEquals(
'',
$this->provider->renderFacets(
$productContext,
$productSearchResult
)
);
}
}

View File

@@ -0,0 +1,481 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\Module\FacetedSearch\Tests\Product;
use Configuration;
use Context;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PrestaShop\Module\FacetedSearch\Adapter\MySQL;
use PrestaShop\Module\FacetedSearch\Product\Search;
use stdClass;
use Tools;
class SearchTest extends MockeryTestCase
{
/**
* @var Search
*/
private $search;
protected function setUp()
{
$mock = Mockery::mock(Configuration::class);
$mock->shouldReceive('get')
->andReturnUsing(function ($arg) {
$valueMap = [
'PS_STOCK_MANAGEMENT' => true,
'PS_ORDER_OUT_OF_STOCK' => true,
'PS_HOME_CATEGORY' => true,
'PS_LAYERED_FULL_TREE' => false,
'PS_LAYERED_FILTER_BY_DEFAULT_CATEGORY' => true,
];
return $valueMap[$arg];
});
Configuration::setStaticExpectations($mock);
$contextMock = Mockery::mock(Context::class);
$contextMock->shop = new stdClass();
$contextMock->shop->id = 1;
Context::setStaticExpectations($contextMock);
$this->search = new Search($contextMock);
}
public function testGetFacetedSearchTypeAdapter()
{
$this->assertInstanceOf(
MySQL::class,
$this->search->getSearchAdapter()
);
}
public function testInitSearchWithEmptyFilters()
{
$toolsMock = Mockery::mock(Tools::class);
$toolsMock->shouldReceive('getValue')
->andReturnUsing(function ($arg) {
$valueMap = [
'id_category' => 12,
'id_category_layered' => 11,
];
return $valueMap[$arg];
});
Tools::setStaticExpectations($toolsMock);
$this->search->initSearch([]);
$this->assertEquals([], $this->search->getSearchAdapter()->getFilters()->toArray());
$this->assertEquals([], $this->search->getSearchAdapter()->getOperationsFilters()->toArray());
$this->assertEquals(
[
'id_category_default' => [
'=' => [
[
null,
],
],
],
'id_category' => [
'=' => [
[
null,
],
],
],
'id_shop' => [
'=' => [
[
1,
],
],
],
'visibility' => [
'=' => [
[
'both',
'catalog',
],
],
],
],
$this->search->getSearchAdapter()->getInitialPopulation()->getFilters()->toArray()
);
$this->assertEquals([], $this->search->getSearchAdapter()->getInitialPopulation()->getOperationsFilters()->toArray());
}
public function testInitSearchWithAllFilters()
{
$toolsMock = Mockery::mock(Tools::class);
$toolsMock->shouldReceive('getValue')
->andReturnUsing(function ($arg) {
$valueMap = [
'id_category' => 12,
'id_category_layered' => 11,
];
return $valueMap[$arg];
});
Tools::setStaticExpectations($toolsMock);
$this->search->initSearch(
[
'id_feature' => [
[1, 2],
],
'id_attribute_group' => [
[4, 5],
],
'category' => [
[6],
],
'quantity' => [
0,
],
'weight' => [
'10',
'40',
],
'price' => [
'50',
'200',
],
'manufacturer' => [
'10',
],
'condition' => [
'1',
],
]
);
$this->assertEquals([], $this->search->getSearchAdapter()->getFilters()->toArray());
$this->assertEquals([], $this->search->getSearchAdapter()->getOperationsFilters()->toArray());
$this->assertEquals(
[
'weight' => [
'>=' => [
[
10.0,
],
],
'<=' => [
[
40.0,
],
],
],
'price_min' => [
'>=' => [
[
50.0,
],
],
],
'price_max' => [
'<=' => [
[
200.0,
],
],
],
'id_manufacturer' => [
'=' => [
[
'10',
],
],
],
'condition' => [
'=' => [
[
'1',
],
],
],
'id_shop' => [
'=' => [
[
1,
],
],
],
'visibility' => [
'=' => [
[
'both',
'catalog',
],
],
],
'id_category' => [
'=' => [
[
null,
],
[
6,
],
],
],
],
$this->search->getSearchAdapter()->getInitialPopulation()->getFilters()->toArray()
);
$this->assertEquals(
[
'with_stock_management' => [
[
[
'quantity',
[
0,
],
'<=',
],
[
'out_of_stock',
[
0,
],
'=',
],
],
],
'with_attributes_0' => [
[
[
'id_attribute',
[
4,
5,
],
],
],
],
'with_features_0' => [
[
[
'id_feature_value',
[
1,
2,
],
],
],
],
],
$this->search->getSearchAdapter()->getInitialPopulation()->getOperationsFilters()->toArray()
);
}
public function testInitSearchWithManyFeatures()
{
$toolsMock = Mockery::mock(Tools::class);
$toolsMock->shouldReceive('getValue')
->andReturnUsing(function ($arg) {
$valueMap = [
'id_category' => 12,
'id_category_layered' => 11,
];
return $valueMap[$arg];
});
Tools::setStaticExpectations($toolsMock);
$this->search->initSearch(
[
'id_feature' => [
[1],
[2, 3, 4],
],
]
);
$this->assertEquals([], $this->search->getSearchAdapter()->getFilters()->toArray());
$this->assertEquals([], $this->search->getSearchAdapter()->getOperationsFilters()->toArray());
$this->assertEquals(
[
'id_shop' => [
'=' => [
[
1,
],
],
],
'visibility' => [
'=' => [
[
'both',
'catalog',
],
],
],
'id_category_default' => [
'=' => [
[
null,
],
],
],
'id_category' => [
'=' => [
[
null,
],
],
],
],
$this->search->getSearchAdapter()->getInitialPopulation()->getFilters()->toArray()
);
$this->assertEquals(
[
'with_features_0' => [
[
[
'id_feature_value',
[
1,
],
],
],
],
'with_features_1' => [
[
[
'id_feature_value',
[
2,
3,
4,
],
],
],
],
],
$this->search->getSearchAdapter()->getInitialPopulation()->getOperationsFilters()->toArray()
);
}
public function testInitSearchWithManyAttributes()
{
$toolsMock = Mockery::mock(Tools::class);
$toolsMock->shouldReceive('getValue')
->andReturnUsing(function ($arg) {
$valueMap = [
'id_category' => 12,
'id_category_layered' => 11,
];
return $valueMap[$arg];
});
Tools::setStaticExpectations($toolsMock);
$this->search->initSearch(
[
'id_attribute_group' => [
[1],
[2, 3, 4],
],
]
);
$this->assertEquals([], $this->search->getSearchAdapter()->getFilters()->toArray());
$this->assertEquals([], $this->search->getSearchAdapter()->getOperationsFilters()->toArray());
$this->assertEquals(
[
'id_shop' => [
'=' => [
[
1,
],
],
],
'visibility' => [
'=' => [
[
'both',
'catalog',
],
],
],
'id_category_default' => [
'=' => [
[
null,
],
],
],
'id_category' => [
'=' => [
[
null,
],
],
],
],
$this->search->getSearchAdapter()->getInitialPopulation()->getFilters()->toArray()
);
$this->assertEquals(
[
'with_attributes_0' => [
[
[
'id_attribute',
[
1,
],
],
],
],
'with_attributes_1' => [
[
[
'id_attribute',
[
2,
3,
4,
],
],
],
],
],
$this->search->getSearchAdapter()->getInitialPopulation()->getOperationsFilters()->toArray()
);
}
public function testAddFilter()
{
$this->search->addFilter('weight', [10, 20]);
$this->search->addFilter('id_feature', [[10, 20]]);
}
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
namespace PrestaShop\Module\FacetedSearch\Tests;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use PrestaShop\Module\FacetedSearch\URLSerializer;
use PrestaShop\PrestaShop\Core\Product\Search\Facet;
use PrestaShop\PrestaShop\Core\Product\Search\Filter;
class URLSerializerTest extends MockeryTestCase
{
private $serializer;
protected function setUp()
{
$this->serializer = new URLSerializer();
}
private function mockFacet($label, $properties = [])
{
$facet = Mockery::mock(Facet::class);
$facet->shouldReceive('getLabel')
->andReturn($label);
$facet->shouldReceive('getProperty')
->andReturnUsing(
function ($arg) use ($properties) {
return $properties[$arg];
}
);
return $facet;
}
private function mockFilter($label, $active = false, $value = null, $properties = [])
{
$filter = Mockery::mock(Filter::class);
$filter->shouldReceive('getLabel')
->andReturn($label);
$filter->shouldReceive('isActive')
->andReturn($active);
if ($value !== null) {
$filter->shouldReceive('getValue')
->andReturn($value);
}
$filter->shouldReceive('getProperty')
->andReturnUsing(
function ($arg) use ($properties) {
return $properties[$arg];
}
);
return $filter;
}
public function testGetActiveFilters()
{
$first = $this->mockFilter('Tops', true);
$second = $this->mockFilter('Robes', false);
$facet = $this->mockFacet('Categories', ['range' => false]);
$facet->shouldReceive('getFilters')
->andReturn([$first, $second]);
$this->assertEquals(
['Categories' => ['Tops' => 'Tops']],
$this->serializer->getActiveFacetFiltersFromFacets([$facet])
);
}
public function testGetActiveFiltersWithRange()
{
$filter = $this->mockFilter('filter', true, [0, 100], ['symbol' => '$']);
$facet = $this->mockFacet('Price', ['range' => true]);
$facet->shouldReceive('getFilters')
->andReturn([$filter]);
$this->assertEquals(
['Price' => ['$', 0, 100]],
$this->serializer->getActiveFacetFiltersFromFacets([$facet])
);
}
public function testAddAndRemoveFiltersWithoutRange()
{
$filter = $this->mockFilter('Tops');
$facet = $this->mockFacet('Categories', ['range' => false]);
$facetsFilters = $this->serializer->addFilterToFacetFilters(
[],
$filter,
$facet
);
$this->assertEquals(
['Categories' => ['Tops' => 'Tops']],
$facetsFilters
);
$facetsFilters = $this->serializer->removeFilterFromFacetFilters(
$facetsFilters,
$filter,
$facet
);
$this->assertEquals(
[],
$facetsFilters
);
}
public function testAddAndRemoveFiltersWithRangeAndMinMax()
{
$filter = $this->mockFilter(
'filter',
true,
[0, 100],
['symbol' => '$']
);
$facet = $this->mockFacet(
'Price',
[
'range' => true,
'values' => [],
'min' => 0,
'max' => 200,
]
);
$facetsFilters = $this->serializer->addFilterToFacetFilters(
[],
$filter,
$facet
);
$this->assertEquals(
['Price' => ['$', 0, 200]],
$facetsFilters
);
$facetsFilters = $this->serializer->removeFilterFromFacetFilters(
$facetsFilters,
$filter,
$facet
);
$this->assertEquals(
[],
$facetsFilters
);
}
public function testAddAndRemoveFiltersWithRange()
{
$filter = $this->mockFilter(
'filter',
true,
[0, 100],
['symbol' => '$']
);
$facet = $this->mockFacet(
'Price',
[
'range' => true,
'values' => [10, 100],
]
);
$facetsFilters = $this->serializer->addFilterToFacetFilters(
[],
$filter,
$facet
);
$this->assertEquals(
['Price' => ['$', 10, 100]],
$facetsFilters
);
$facetsFilters = $this->serializer->removeFilterFromFacetFilters(
$facetsFilters,
$filter,
$facet
);
$this->assertEquals(
[],
$facetsFilters
);
}
}

View File

@@ -0,0 +1,19 @@
<?php
// FacetedSearch autoloader
require __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/FacetedSearch/MockProxy.php';
require_once __DIR__ . '/FacetedSearch/Mock/Filter.php';
require_once __DIR__ . '/FacetedSearch/Mock/Facet.php';
require_once __DIR__ . '/FacetedSearch/Interface/WidgetInterface.php';
require_once __DIR__ . '/FacetedSearch/Interface/FacetsRendererInterface.php';
require_once __DIR__ . '/FacetedSearch/Interface/ProductSearchProviderInterface.php';
define('_PS_COL_IMG_DIR_', __DIR__ . '/files/');
// Fake pSQL function
function pSQL($string, $htmlOK = false)
{
return $string;
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
$rootDir = getenv('_PS_ROOT_DIR_');
if (!$rootDir) {
echo '[ERROR] Define _PS_ROOT_DIR_ with the path to PrestaShop folder' . PHP_EOL;
exit(1);
}
// Add module composer autoloader
require_once dirname(__DIR__) . '/../../vendor/autoload.php';
// Add PrestaShop composer autoload
define('_PS_ADMIN_DIR_', $rootDir . '/admin-dev/');
define('PS_ADMIN_DIR', _PS_ADMIN_DIR_);
require_once $rootDir . '/config/defines.inc.php';
require_once $rootDir . '/config/autoload.php';
require_once $rootDir . '/config/bootstrap.php';
// Make sure loader php-parser is coming from php stan composer
$loader = new \Composer\Autoload\ClassLoader();
$loader->setPsr4('PhpParser\\', ['/composer/vendor/nikic/php-parser/lib/PhpParser']);
$loader->register(true);
// We must declare these constant in this boostrap script.
// Ignoring the error partern with this value will throw another error if not found
// during the checks.
$constantsToDefine = [
'_DB_PREFIX_',
'_PS_SSL_PORT_',
'_THEME_NAME_',
'_PARENT_THEME_NAME_',
'__PS_BASE_URI__',
'_PS_PRICE_DISPLAY_PRECISION_',
'_PS_PRICE_COMPUTE_PRECISION_',
'_PS_OS_CHEQUE_',
'_PS_OS_PAYMENT_',
'_PS_OS_PREPARATION_',
'_PS_OS_SHIPPING_',
'_PS_OS_DELIVERED_',
'_PS_OS_CANCELED_',
'_PS_OS_REFUND_',
'_PS_OS_ERROR_',
'_PS_OS_OUTOFSTOCK_',
'_PS_OS_OUTOFSTOCK_PAID_',
'_PS_OS_OUTOFSTOCK_UNPAID_',
'_PS_OS_BANKWIRE_',
'_PS_OS_PAYPAL_',
'_PS_OS_WS_PAYMENT_',
'_PS_OS_COD_VALIDATION_',
];
foreach ($constantsToDefine as $constant) {
if (!defined($constant)) {
define($constant, 'DUMMY_VALUE');
}
}

View File

@@ -0,0 +1,18 @@
parameters:
reportUnmatchedIgnoredErrors: false
bootstrap: /var/www/html/modules/ps_facetedsearch/tests/php/phpstan/bootstrap.php
paths:
- /var/www/html/modules/ps_facetedsearch/src
ignoreErrors:
# module specific
- '~Constant _THEME_COL_DIR_ not found.~'
- '~Iterating over an object of an unknown class mysqli_result\.~'
- '~Access to offset mixed on an unknown class mysqli_result\.~'
- '~Parameter #1 \$master of static method DbCore::getInstance\(\) expects bool, int given\.~'
- '~Parameter #1 \$string of method PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer::unserialize\(\) expects string, array given\.~'
- '~Parameter #\d+ \$(.+?) of class Category constructor expects null, int given\.~'
- '~constant NUMBERING_SYSTEM_LATIN on an unknown class~'
- '~PrestaShopBundle\\Form\\Admin\\Type\\(TranslatableType|SwitchType) not found~'
- '~Call to an undefined method PrestaShop\\PrestaShop\\Adapter\\Tools::linkRewrite\(\).~'
level: 5

View File

@@ -0,0 +1,18 @@
parameters:
reportUnmatchedIgnoredErrors: false
bootstrap: /var/www/html/modules/ps_facetedsearch/tests/php/phpstan/bootstrap.php
paths:
- /var/www/html/modules/ps_facetedsearch/src
ignoreErrors:
# module specific
- '~Constant _THEME_COL_DIR_ not found.~'
- '~Iterating over an object of an unknown class mysqli_result\.~'
- '~Access to offset mixed on an unknown class mysqli_result\.~'
- '~Parameter #1 \$master of static method DbCore::getInstance\(\) expects bool, int given\.~'
- '~Parameter #1 \$string of method PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer::unserialize\(\) expects string, array given\.~'
- '~Parameter #\d+ \$(.+?) of class Category constructor expects null, int given\.~'
- '~constant NUMBERING_SYSTEM_LATIN on an unknown class~'
- '~PrestaShopBundle\\Form\\Admin\\Type\\(TranslatableType|SwitchType) not found~'
- '~Call to an undefined method PrestaShop\\PrestaShop\\Adapter\\Tools::linkRewrite\(\).~'
level: 5

View File

@@ -0,0 +1,17 @@
parameters:
reportUnmatchedIgnoredErrors: false
bootstrap: /var/www/html/modules/ps_facetedsearch/tests/php/phpstan/bootstrap.php
paths:
- /var/www/html/modules/ps_facetedsearch/src
ignoreErrors:
# module specific
- '~Constant _THEME_COL_DIR_ not found.~'
- '~Iterating over an object of an unknown class mysqli_result\.~'
- '~Access to offset mixed on an unknown class mysqli_result\.~'
- '~Parameter #1 \$master of static method DbCore::getInstance\(\) expects bool, int given\.~'
- '~Parameter #1 \$string of method PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer::unserialize\(\) expects string, array given\.~'
- '~Parameter #\d+ \$(.+?) of class Category constructor expects null, int given\.~'
- '~constant NUMBERING_SYSTEM_LATIN on an unknown class~'
- '~PrestaShopBundle\\Form\\Admin\\Type\\(TranslatableType|SwitchType) not found~'
level: 5

View File

@@ -0,0 +1,16 @@
parameters:
reportUnmatchedIgnoredErrors: false
bootstrap: /var/www/html/modules/ps_facetedsearch/tests/php/phpstan/bootstrap.php
paths:
- /var/www/html/modules/ps_facetedsearch/src
ignoreErrors:
# module specific
- '~Constant _THEME_COL_DIR_ not found.~'
- '~Iterating over an object of an unknown class mysqli_result\.~'
- '~Access to offset mixed on an unknown class mysqli_result\.~'
- '~Parameter #1 \$master of static method DbCore::getInstance\(\) expects bool, int given\.~'
- '~Parameter #1 \$string of method PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer::unserialize\(\) expects string, array given\.~'
- '~Parameter #\d+ \$(.+?) of class Category constructor expects null, int given\.~'
- '~PrestaShopBundle\\Form\\Admin\\Type\\(TranslatableType|SwitchType) not found~'
level: 5

View File

@@ -0,0 +1,16 @@
parameters:
reportUnmatchedIgnoredErrors: false
bootstrap: /var/www/html/modules/ps_facetedsearch/tests/php/phpstan/bootstrap.php
paths:
- /var/www/html/modules/ps_facetedsearch/src
ignoreErrors:
# module specific
- '~Constant _THEME_COL_DIR_ not found.~'
- '~Iterating over an object of an unknown class mysqli_result\.~'
- '~Access to offset mixed on an unknown class mysqli_result\.~'
- '~Parameter #1 \$master of static method DbCore::getInstance\(\) expects bool, int given\.~'
- '~Parameter #1 \$string of method PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer::unserialize\(\) expects string, array given\.~'
- '~Parameter #\d+ \$(.+?) of class Category constructor expects null, int given\.~'
- '~PrestaShopBundle\\Form\\Admin\\Type\\(TranslatableType|SwitchType) not found~'
level: 5

View File

@@ -0,0 +1,17 @@
parameters:
reportUnmatchedIgnoredErrors: false
bootstrap: /var/www/html/modules/ps_facetedsearch/tests/php/phpstan/bootstrap.php
paths:
- /var/www/html/modules/ps_facetedsearch/src
ignoreErrors:
# module specific
- '~Constant _THEME_COL_DIR_ not found.~'
- '~Iterating over an object of an unknown class mysqli_result\.~'
- '~Access to offset mixed on an unknown class mysqli_result\.~'
- '~Parameter #1 \$master of static method DbCore::getInstance\(\) expects bool, int given\.~'
- '~Parameter #1 \$string of method PrestaShop\\PrestaShop\\Core\\Product\\Search\\URLFragmentSerializer::unserialize\(\) expects string, array given\.~'
- '~Parameter #\d+ \$(.+?) of class Category constructor expects null, int given\.~'
- '~constant NUMBERING_SYSTEM_LATIN on an unknown class~'
- '~PrestaShopBundle\\Form\\Admin\\Type\\(TranslatableType|SwitchType) not found~'
level: 5

View File

@@ -0,0 +1,23 @@
<phpunit
bootstrap="bootstrap.php"
>
<php>
<const name="_DB_PREFIX_" value="ps_"/>
<const name="_PS_VERSION_" value="FacetedSearchVersion"/>
<const name="_THEME_COL_DIR_" value="/theme/"/>
<server name="REQUEST_URI" value="/catalog?from=scratch&amp;page=1&amp;something"/>
<server name="HTTP_HOST" value="shop.prestashop.com"/>
</php>
<testsuites>
<testsuite name="FacetedSearch">
<directory>.</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">../../src</directory>
</whitelist>
</filter>
</phpunit>