-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(core): Ensure deterministic sorting in case of duplicates (#2632)
- Loading branch information
1 parent
0c645e3
commit 81b4607
Showing
9 changed files
with
35,883 additions
and
5,449 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
packages/core/e2e/default-search-plugin/default-search-plugin-sort-by.e2e-spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { DefaultJobQueuePlugin, DefaultSearchPlugin, mergeConfig } from '@vendure/core'; | ||
import { createTestEnvironment } from '@vendure/testing'; | ||
import path from 'path'; | ||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'; | ||
|
||
import { initialData } from '../../../../e2e-common/e2e-initial-data'; | ||
import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../../e2e-common/test-config'; | ||
import { SEARCH_PRODUCTS_ADMIN } from '../graphql/admin-definitions'; | ||
import { | ||
SearchResultSortParameter, | ||
SortOrder, | ||
SearchProductsAdminQuery, | ||
SearchProductsAdminQueryVariables, | ||
} from '../graphql/generated-e2e-admin-types'; | ||
import { | ||
SearchProductsShopQuery, | ||
SearchProductsShopQueryVariables, | ||
} from '../graphql/generated-e2e-shop-types'; | ||
import { SEARCH_PRODUCTS_SHOP } from '../graphql/shop-definitions'; | ||
import { awaitRunningJobs } from '../utils/await-running-jobs'; | ||
|
||
describe('Default search plugin', () => { | ||
const { server, adminClient, shopClient } = createTestEnvironment( | ||
mergeConfig(testConfig(), { | ||
plugins: [ | ||
DefaultSearchPlugin.init({ | ||
indexStockStatus: true, | ||
}), | ||
DefaultJobQueuePlugin, | ||
], | ||
}), | ||
); | ||
|
||
beforeAll(async () => { | ||
await server.init({ | ||
initialData, | ||
productsCsvPath: path.join(__dirname, 'fixtures', 'default-search-plugin-sort-by.csv'), | ||
customerCount: 1, | ||
}); | ||
await adminClient.asSuperAdmin(); | ||
await awaitRunningJobs(adminClient); | ||
}, TEST_SETUP_TIMEOUT_MS); | ||
|
||
afterAll(async () => { | ||
await awaitRunningJobs(adminClient); | ||
await server.destroy(); | ||
}); | ||
|
||
function searchProductsShop(queryVariables: SearchProductsShopQueryVariables) { | ||
return shopClient.query<SearchProductsShopQuery, SearchProductsShopQueryVariables>( | ||
SEARCH_PRODUCTS_SHOP, | ||
queryVariables, | ||
); | ||
} | ||
|
||
function searchProductsAdmin(queryVariables: SearchProductsAdminQueryVariables) { | ||
return adminClient.query<SearchProductsAdminQuery, SearchProductsAdminQueryVariables>( | ||
SEARCH_PRODUCTS_ADMIN, | ||
queryVariables, | ||
); | ||
} | ||
|
||
type SearchProducts = ( | ||
queryVariables: SearchProductsShopQueryVariables | SearchProductsAdminQueryVariables, | ||
) => Promise<SearchProductsShopQuery | SearchProductsAdminQuery>; | ||
|
||
async function testSearchProducts( | ||
searchProducts: SearchProducts, | ||
groupByProduct: boolean, | ||
sortBy: keyof SearchResultSortParameter, | ||
sortOrder: (typeof SortOrder)[keyof typeof SortOrder], | ||
skip: number, | ||
take: number, | ||
) { | ||
return searchProducts({ | ||
input: { | ||
groupByProduct, | ||
sort: { | ||
[sortBy]: sortOrder, | ||
}, | ||
skip, | ||
take, | ||
}, | ||
}); | ||
} | ||
|
||
async function testSortByPriceAsc(searchProducts: SearchProducts) { | ||
const resultPage1 = await testSearchProducts(searchProducts, false, 'price', SortOrder.ASC, 0, 3); | ||
const resultPage2 = await testSearchProducts(searchProducts, false, 'price', SortOrder.ASC, 3, 3); | ||
|
||
const pvIds1 = resultPage1.search.items.map(i => i.productVariantId); | ||
const pvIds2 = resultPage2.search.items.map(i => i.productVariantId); | ||
const pvIds3 = pvIds1.concat(pvIds2); | ||
|
||
expect(new Set(pvIds3).size).equals(6); | ||
expect(resultPage1.search.items.map(i => i.productVariantId)).toEqual(['T_4', 'T_5', 'T_6']); | ||
expect(resultPage2.search.items.map(i => i.productVariantId)).toEqual(['T_7', 'T_8', 'T_9']); | ||
} | ||
|
||
async function testSortByPriceDesc(searchProducts: SearchProducts) { | ||
const resultPage1 = await testSearchProducts(searchProducts, false, 'price', SortOrder.DESC, 0, 3); | ||
const resultPage2 = await testSearchProducts(searchProducts, false, 'price', SortOrder.DESC, 3, 3); | ||
|
||
const pvIds1 = resultPage1.search.items.map(i => i.productVariantId); | ||
const pvIds2 = resultPage2.search.items.map(i => i.productVariantId); | ||
const pvIds3 = pvIds1.concat(pvIds2); | ||
|
||
expect(new Set(pvIds3).size).equals(6); | ||
expect(resultPage1.search.items.map(i => i.productVariantId)).toEqual(['T_1', 'T_2', 'T_3']); | ||
expect(resultPage2.search.items.map(i => i.productVariantId)).toEqual(['T_4', 'T_5', 'T_6']); | ||
} | ||
|
||
async function testSortByPriceAscGroupByProduct(searchProducts: SearchProducts) { | ||
const resultPage1 = await testSearchProducts(searchProducts, true, 'price', SortOrder.ASC, 0, 3); | ||
const resultPage2 = await testSearchProducts(searchProducts, true, 'price', SortOrder.ASC, 3, 3); | ||
|
||
const pvIds1 = resultPage1.search.items.map(i => i.productVariantId); | ||
const pvIds2 = resultPage2.search.items.map(i => i.productVariantId); | ||
const pvIds3 = pvIds1.concat(pvIds2); | ||
|
||
expect(new Set(pvIds3).size).equals(6); | ||
expect(resultPage1.search.items.map(i => i.productId)).toEqual(['T_4', 'T_5', 'T_6']); | ||
expect(resultPage2.search.items.map(i => i.productId)).toEqual(['T_1', 'T_2', 'T_3']); | ||
} | ||
|
||
async function testSortByPriceDescGroupByProduct(searchProducts: SearchProducts) { | ||
const resultPage1 = await testSearchProducts(searchProducts, true, 'price', SortOrder.DESC, 0, 3); | ||
const resultPage2 = await testSearchProducts(searchProducts, true, 'price', SortOrder.DESC, 3, 3); | ||
|
||
const pvIds1 = resultPage1.search.items.map(i => i.productVariantId); | ||
const pvIds2 = resultPage2.search.items.map(i => i.productVariantId); | ||
const pvIds3 = pvIds1.concat(pvIds2); | ||
|
||
expect(new Set(pvIds3).size).equals(6); | ||
expect(resultPage1.search.items.map(i => i.productId)).toEqual(['T_1', 'T_2', 'T_3']); | ||
expect(resultPage2.search.items.map(i => i.productId)).toEqual(['T_4', 'T_5', 'T_6']); | ||
} | ||
|
||
describe('Search products shop', () => { | ||
const searchProducts = searchProductsShop; | ||
|
||
it('sort by price ASC', () => testSortByPriceAsc(searchProducts)); | ||
it('sort by price DESC', () => testSortByPriceDesc(searchProducts)); | ||
|
||
it('sort by price ASC group by product', () => testSortByPriceAscGroupByProduct(searchProducts)); | ||
it('sort by price DESC group by product', () => testSortByPriceDescGroupByProduct(searchProducts)); | ||
}); | ||
|
||
describe('Search products admin', () => { | ||
const searchProducts = searchProductsAdmin; | ||
|
||
it('sort by price ACS', () => testSortByPriceAsc(searchProducts)); | ||
it('sort by price DESC', () => testSortByPriceDesc(searchProducts)); | ||
|
||
it('sort by price ASC group by product', () => testSortByPriceAscGroupByProduct(searchProducts)); | ||
it('sort by price DESC group by product', () => testSortByPriceDescGroupByProduct(searchProducts)); | ||
}); | ||
}); |
16 changes: 16 additions & 0 deletions
16
packages/core/e2e/default-search-plugin/fixtures/default-search-plugin-sort-by.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
name,slug,description,assets,facets,optionGroups,optionValues,sku,price,taxCategory,stockOnHand,trackInventory,variantAssets,variantFacets | ||
Boot A,boot-a,Boot A Size 40,,category:sports equipment,shoe size,Size 40,BA40,100,standard,100,true,, | ||
Boot B,boot-b,Boot B Size 40,,category:sports equipment,shoe size,Size 40,BB40,100,standard,100,true,, | ||
Boot C,boot-c,Boot C Size 40,,category:sports equipment,shoe size,Size 40,BC40,100,standard,100,true,, | ||
Sneaker A,sneaker-a,Sneaker A Size 40,,category:sports equipment,shoe size,Size 40,SA40,99.99,standard,100,true,, | ||
,,Sneaker A Size 41,,,,Size 41,SA41,99.99,standard,100,true,, | ||
,,Sneaker A Size 42,,,,Size 42,SA42,99.99,standard,100,true,, | ||
,,Sneaker A Size 43,,,,Size 43,SA43,99.99,standard,100,true,, | ||
Sneaker B,sneaker-b,Sneaker B Size 40,,category:sports equipment,shoe size,Size 40,SB40,99.99,standard,100,true,, | ||
,,Sneaker B Size 41,,,,Size 41,SB41,99.99,standard,100,true,, | ||
,,Sneaker B Size 42,,,,Size 42,SB42,99.99,standard,100,true,, | ||
,,Sneaker B Size 43,,,,Size 43,SB43,99.99,standard,100,true,, | ||
Sneaker C,sneaker-c,Sneaker C Size 40,,category:sports equipment,shoe size,Size 40,SC40,99.99,standard,100,true,, | ||
,,Sneaker C Size 41,,,,Size 41,SC41,99.99,standard,100,true,, | ||
,,Sneaker C Size 42,,,,Size 42,SC42,99.99,standard,100,true,, | ||
,,Sneaker C Size 43,,,,Size 43,SC43,99.99,standard,100,true,, |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import gql from 'graphql-tag'; | ||
|
||
export const SEARCH_PRODUCTS_ADMIN = gql` | ||
query SearchProductsAdmin($input: SearchInput!) { | ||
search(input: $input) { | ||
totalItems | ||
items { | ||
enabled | ||
productId | ||
productName | ||
slug | ||
description | ||
productVariantId | ||
productVariantName | ||
sku | ||
} | ||
} | ||
} | ||
`; |
Oops, something went wrong.