Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix checkbox facet duplication multi-select facet bug #4203

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1381,32 +1381,37 @@ protected function assertNoResetFiltersButton(Element $page): void
public static function multiSelectOnAdvancedSearchProvider(): array
{
return [
'with language switch' => [true],
'without language switch' => [false],
'with language switch / with checkbox' => [true, true],
'without language switch / with checkbox' => [false, true],
'with language switch / without checkbox' => [true, false],
'without language switch / without checkbox' => [false, false],
];
}

/**
* Test applying multi-facet selection to advanced search results, with or without changing the
* language setting first.
* language setting first and/or including a pre-existing checkbox filter.
*
* @param bool $changeLanguage Should we change the language before applying the facets?
* @param bool $changeLanguage Should we change the language before applying the facets?
* @param bool $includeCheckbox Should we apply a checkbox prior to multi-selection?
*
* @dataProvider multiSelectOnAdvancedSearchProvider
*
* @return void
*/
public function testMultiSelectOnAdvancedSearch(bool $changeLanguage): void
public function testMultiSelectOnAdvancedSearch(bool $changeLanguage, bool $includeCheckbox): void
{
$this->changeConfigs(
[
'facets' => [
'Results_Settings' => [
'multiFacetsSelection' => true,
],
],
]
);
$facets = [
'Results_Settings' => [
'multiFacetsSelection' => true,
],
];
if ($includeCheckbox) {
// Create a pointless checkbox filter that will not impact the result set size
// (we're just testing that it applies to the URL correctly):
$facets['CheckboxFacets']['title:*'] = 'Has Title';
}
$this->changeConfigs(compact('facets'));
$path = '/Search/Advanced';
$session = $this->getMinkSession();
$session->visit($this->getVuFindUrl() . $path);
Expand All @@ -1416,11 +1421,17 @@ public function testMultiSelectOnAdvancedSearch(bool $changeLanguage): void
$this->findCssAndSetValue($page, '#search_lookfor0_1', 'history');
$this->findCss($page, '[type=submit]')->press();

if ($includeCheckbox) {
$link = $this->findAndAssertLink($page, 'Has Title');
$link->click();
$this->waitForPageLoad($page);
}

if ($changeLanguage) {
$this->flipflopLanguage($page);
}

// Activate the first two facet values:
// Activate the first two facet values (and the checkbox filter, if requested):
$this->clickCss($page, '.js-user-selection-multi-filters');
$this->clickCss($page, '.facet__list__item a');
$this->clickCss($page, '.facet__list__item a', index: 1);
Expand All @@ -1433,7 +1444,13 @@ public function testMultiSelectOnAdvancedSearch(bool $changeLanguage): void
$this->findCssAndGetText($page, '.adv_search_terms strong')
);

// Make sure we have the expected number of filters applied on screen and in the URL query:
$this->assertCount(2, $page->findAll('css', '.facet.active'));
$this->assertCount($includeCheckbox ? 1 : 0, $page->findAll('css', '.checkbox-filter [data-checked="true"]'));
$query = parse_url($session->getCurrentUrl(), PHP_URL_QUERY);
parse_str($query, $queryArray);
$expectedFilterCount = $includeCheckbox ? 3 : 2;
$this->assertCount($expectedFilterCount, $queryArray['filter']);

// If configured, flip-flop language again to potentially modify filter params:
if ($changeLanguage) {
Expand Down
27 changes: 19 additions & 8 deletions themes/bootstrap3/js/facets.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,23 +178,34 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() {
}

/**
* Normalize key names in a set of search parameters
* Append a normalized value to a normalized key in a set of parameters
*
* @param {URLSearchParams} params Parameters to update
* @param {string} key Key name
* @param {string} value Value to set
*/
function appendNormalizedValue(params, key, value) {
const normalizedKey = normalizeSearchQueryKey(key);
params.append(normalizedKey, normalizeValue(normalizedKey, value));
}

/**
* Normalize keys and values in a set of search parameters
*
* @param {URLSearchParams} params Parameters to normalize
*
* @returns URLSearchParams
*/
function normalizeSearchQueryKeys(params) {
function normalizeSearchQueryKeysAndValues(params) {
const normalized = new URLSearchParams();
for (const [key, value] of params) {
normalized.append(normalizeSearchQueryKey(key), value);
appendNormalizedValue(normalized, key, value);
}
return normalized;
}


for (const [key, value] of (new URLSearchParams(window.location.search))) {
initialParams.append(normalizeSearchQueryKey(key), normalizeValue(key, value));
appendNormalizedValue(initialParams, key, value);
}

/**
Expand Down Expand Up @@ -249,19 +260,19 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() {
for (const elem of elems) {
const href = elem.getAttribute('href');
const p = href.indexOf('?');
const elemParams = normalizeSearchQueryKeys(new URLSearchParams(p >= 0 ? href.substring(p + 1) : ''));
const elemParams = normalizeSearchQueryKeysAndValues(new URLSearchParams(p >= 0 ? href.substring(p + 1) : ''));

// Add parameters that did not initially exist:
for (const [key, value] of elemParams) {
// URLSearchParams.has(key, value) seems to be broken on iOS 16, so check with our own method:
if (!VuFind.inURLSearchParams(initialParams, key, value)) {
globalAddedParams.append(normalizeSearchQueryKey(key), value);
appendNormalizedValue(globalAddedParams, key, value);
}
}
// Remove parameters that this URL no longer has:
for (const [key, value] of initialParams) {
if (!VuFind.inURLSearchParams(elemParams, key, value)) {
globalRemovedParams.append(normalizeSearchQueryKey(key), value);
appendNormalizedValue(globalRemovedParams, key, value);
}
}
}
Expand Down
27 changes: 19 additions & 8 deletions themes/bootstrap5/js/facets.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,23 +178,34 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() {
}

/**
* Normalize key names in a set of search parameters
* Append a normalized value to a normalized key in a set of parameters
*
* @param {URLSearchParams} params Parameters to update
* @param {string} key Key name
* @param {string} value Value to set
*/
function appendNormalizedValue(params, key, value) {
const normalizedKey = normalizeSearchQueryKey(key);
params.append(normalizedKey, normalizeValue(normalizedKey, value));
}

/**
* Normalize keys and values in a set of search parameters
*
* @param {URLSearchParams} params Parameters to normalize
*
* @returns URLSearchParams
*/
function normalizeSearchQueryKeys(params) {
function normalizeSearchQueryKeysAndValues(params) {
const normalized = new URLSearchParams();
for (const [key, value] of params) {
normalized.append(normalizeSearchQueryKey(key), value);
appendNormalizedValue(normalized, key, value);
}
return normalized;
}


for (const [key, value] of (new URLSearchParams(window.location.search))) {
initialParams.append(normalizeSearchQueryKey(key), normalizeValue(key, value));
appendNormalizedValue(initialParams, key, value);
}

/**
Expand Down Expand Up @@ -249,19 +260,19 @@ VuFind.register('multiFacetsSelection', function multiFacetsSelection() {
for (const elem of elems) {
const href = elem.getAttribute('href');
const p = href.indexOf('?');
const elemParams = normalizeSearchQueryKeys(new URLSearchParams(p >= 0 ? href.substring(p + 1) : ''));
const elemParams = normalizeSearchQueryKeysAndValues(new URLSearchParams(p >= 0 ? href.substring(p + 1) : ''));

// Add parameters that did not initially exist:
for (const [key, value] of elemParams) {
// URLSearchParams.has(key, value) seems to be broken on iOS 16, so check with our own method:
if (!VuFind.inURLSearchParams(initialParams, key, value)) {
globalAddedParams.append(normalizeSearchQueryKey(key), value);
appendNormalizedValue(globalAddedParams, key, value);
}
}
// Remove parameters that this URL no longer has:
for (const [key, value] of initialParams) {
if (!VuFind.inURLSearchParams(elemParams, key, value)) {
globalRemovedParams.append(normalizeSearchQueryKey(key), value);
appendNormalizedValue(globalRemovedParams, key, value);
}
}
}
Expand Down
Loading