diff --git a/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js b/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js index 56ed30a..82ac646 100644 --- a/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js +++ b/src/js/components/AdvancedSearchForm/AdvancedSearchForm.stories.js @@ -1,9 +1,14 @@ import AdvancedSearchForm from './index.svelte'; -import { userEvent, within } from 'storybook/test'; +import AdvancedSearchDecorator from '../../decorators/AdvancedSearchDecorator.svelte' +import { fn, expect, waitFor } from 'storybook/test'; export default { title: 'AdvancedSearchForm', component: AdvancedSearchForm, + decorators: [() => AdvancedSearchDecorator], + args: { + mockSubmit: fn(), + }, }; export const MobileDefault = { @@ -22,4 +27,122 @@ export const DefaultDesktop = { isRotated: false, }, }, + play: async ({ args, canvas, userEvent }) => { + + await canvas.getByLabelText('Search Term 1').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); + await userEvent.click(canvas.getByTestId('advanced-search-submit')); + + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + + const calledUrl = args.mockSubmit.mock.calls[0][0]; + expect(calledUrl).toContain('q1=elephant') + + } }; + +export const TwoFieldsWithAnd = { + globals: { + viewport: { + value: 'bsXl', + isRotated: false, + }, + }, + play: async ({ args, canvas, userEvent }) => { + + await canvas.getByLabelText('Search Term 1').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); + const searchField2 = canvas.getByLabelText('Selected field 2'); + await userEvent.selectOptions(searchField2, 'Subject'); + await canvas.getByLabelText('Search Term 2').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 2'), 'conservation'); + await userEvent.click(canvas.getByTestId('advanced-search-submit')); + + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + + const calledUrl = args.mockSubmit.mock.calls[0][0]; + expect(calledUrl).toContain('lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=AND') + + } +} +export const FullTextFields1And4WithLastOr = { + globals: { + viewport: { + value: 'bsXl', + isRotated: false, + }, + }, + play: async ({ args, canvas, userEvent }) => { + + await canvas.getByLabelText('Search Term 1').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); + const searchField4 = canvas.getByLabelText('Selected field 4'); + await userEvent.selectOptions(searchField4, 'Subject'); + await canvas.getByLabelText('Search Term 4').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 4'), 'conservation'); + const thirdRadioGroupOr = canvas.getAllByRole('radio', { name: 'OR' })[2]; + await userEvent.click(thirdRadioGroupOr); + await userEvent.click(canvas.getByTestId('advanced-search-submit')); + + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + + const calledUrl = args.mockSubmit.mock.calls[0][0]; + expect(calledUrl).toContain('lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=OR') + + } +} + +export const FullTextFields1And4WithFirstOr = { + globals: { + viewport: { + value: 'bsXl', + isRotated: false, + }, + }, + play: async ({ args, canvas, userEvent }) => { + + await canvas.getByLabelText('Search Term 1').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 1'), 'elephant'); + const firstRadioGroupOr = canvas.getAllByRole('radio', { name: 'OR' })[0]; + await userEvent.click(firstRadioGroupOr); + const searchField4 = canvas.getByLabelText('Selected field 4'); + await userEvent.selectOptions(searchField4, 'Subject'); + await canvas.getByLabelText('Search Term 4').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 4'), 'conservation'); + await userEvent.click(canvas.getByTestId('advanced-search-submit')); + + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + + const calledUrl = args.mockSubmit.mock.calls[0][0]; + expect(calledUrl).toContain('lmt=ft&a=srchls&adv=1&q1=elephant&q2=conservation&field1=ocr&field2=subject&anyall1=all&anyall2=all&op2=OR') + + } +} +export const CatalogFields1And4WithLastOr = { + globals: { + viewport: { + value: 'bsXl', + isRotated: false, + }, + }, + play: async ({ args, canvas, userEvent }) => { + + const searchField1 = canvas.getByLabelText('Selected field 1'); + await userEvent.selectOptions(searchField1, 'Title'); + await canvas.getByLabelText('Search Term 1').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 1'), 'apple'); + const searchField4 = canvas.getByLabelText('Selected field 4'); + await userEvent.selectOptions(searchField4, 'Author'); + await canvas.getByLabelText('Search Term 4').focus(); + await userEvent.type(canvas.getByLabelText('Search Term 4'), 'orange'); + const thirdRadioGroupOr = canvas.getAllByRole('radio', { name: 'OR' })[2]; + await userEvent.click(thirdRadioGroupOr); + await userEvent.click(canvas.getByTestId('advanced-search-submit')); + + await waitFor(() => expect(args.mockSubmit).toHaveBeenCalled()) + + const calledUrl = args.mockSubmit.mock.calls[0][0]; + expect(calledUrl).toContain('adv=1&setft=true&ft=ft&lookfor%5B%5D=apple&lookfor%5B%5D=orange&type%5B%5D=title&type%5B%5D=author&bool%5B%5D=OR') + + } +} diff --git a/src/js/components/AdvancedSearchForm/index.svelte b/src/js/components/AdvancedSearchForm/index.svelte index b1a45b2..d0d10a1 100644 --- a/src/js/components/AdvancedSearchForm/index.svelte +++ b/src/js/components/AdvancedSearchForm/index.svelte @@ -18,7 +18,14 @@ */ /** @type {Props} */ - let { formatData = [], languageData = [], locationData = [], collid = null, collectionName = null } = $props(); + let { + formatData = [], + languageData = [], + locationData = [], + collid = null, + collectionName = null, + mockSubmit = null, + } = $props(); // export let useAnyAll = true; @@ -58,13 +65,12 @@ let lang = $state([]); let format = $state([]); let originalLocation = $state(''); - let modal; - // let types = new Array(4); types.fill('ocr'); let types = $state(['ocr', 'all', 'title', 'author']); let lookFors = $state(new Array(4)); lookFors.fill(''); let bools = $state(new Array(4)); bools.fill('AND'); + bools[0] = null; let anyalls = $state(new Array(4)); anyalls.fill('all'); let isFullView = $state(true); @@ -88,7 +94,7 @@ } } - function submitForm(event) { + function buildSearchUrl(event) { // which are we targeting? errors.lookFors = false; errors.yop = false; @@ -103,6 +109,36 @@ break; } } + + const filteredLookFors = []; + const filteredTypes = []; + const filteredAnyAlls = []; + const filteredBools = [null]; //first will be null + + let lastValidIndex = null; + + for (let i = 0; i < lookFors.length; i++) { + if (lookFors[i] !== null && lookFors[i] !== '') { + filteredLookFors.push(lookFors[i]); + filteredTypes.push(types[i]); + filteredAnyAlls.push(anyalls[i]); + + // use OR if it's the connecting bool between two lines, otherwise use AND + if (lastValidIndex !== null) { + let connectingBool = 'AND'; //default to AND + for (let j = lastValidIndex + 1; j <= i; j++) { + if (bools[j] === 'OR') { + connectingBool = 'OR'; + break; + } + } + filteredBools.push(connectingBool); + } + + lastValidIndex = i; + } + } + if (target == 'catalog') { url = new URL(`${protocol}//${HT.catalog_domain}/Search/Home`); let searchParams = new URLSearchParams(); @@ -166,11 +202,8 @@ } } - bools.forEach((value, idx) => { - if (idx === 0) { - return; - } - if (value && lookFors[idx] && lookFors[idx - 1]) { + filteredBools.forEach((value, idx) => { + if (value && filteredLookFors[idx]) { searchParams.append('bool[]', value); } }); @@ -201,27 +234,30 @@ } let hasSearchTerms = false; - lookFors.forEach((value, idx) => { + filteredLookFors.forEach((value, idx) => { if (value) { searchParams.set(`q${idx + 1}`, value); hasSearchTerms = true; } }); - types.forEach((value, idx) => { - if (value && lookFors[idx]) { + filteredTypes.forEach((value, idx) => { + if (value && filteredLookFors[idx]) { searchParams.set(`field${idx + 1}`, value == 'everything' ? 'ocr' : value); } }); - anyalls.forEach((value, idx) => { - if (value && lookFors[idx]) { + filteredAnyAlls.forEach((value, idx) => { + if (value && filteredLookFors[idx]) { searchParams.set(`anyall${idx + 1}`, value); } }); - bools.forEach((value, idx) => { - if (value && lookFors[idx]) { - searchParams.set(`op${idx + 1}`, value); + // if there's only one keyword, we don't need a boolean + if (filteredLookFors.length > 1) { + for (let idx = 1; idx < filteredBools.length; idx++) { + if (filteredBools[idx] && filteredLookFors[idx]) { + searchParams.set(`op${idx + 1}`, filteredBools[idx]); + } } - }); + } if (!hasSearchTerms) { errors.lookFors = true; @@ -275,7 +311,16 @@ return; } - location.assign(url.toString()); + return url.toString(); + } + + function submitForm(event) { + const url = buildSearchUrl(event); + if (mockSubmit) { + mockSubmit(url); + } else { + location.assign(url); + } } onMount(() => { @@ -341,6 +386,7 @@ lookFors[0] = params.get('q1'); types[0] = params.get('field1') || 'ocr'; anyalls[0] = params.get('anyall1') || 'all'; + bools[0] = null; lookFors[1] = params.get('q2'); types[1] = params.get('field2') || 'all'; anyalls[1] = params.get('anyall2') || 'all'; @@ -352,7 +398,7 @@ lookFors[3] = params.get('q4'); types[3] = params.get('field4') || 'author'; anyalls[3] = params.get('anyall4') || 'all'; - bools[4] = params.get('op3') || 'AND'; + bools[3] = params.get('op4') || 'AND'; if (params.get('facet_lang')) { params.getAll('facet_lang').forEach((value) => { @@ -579,7 +625,7 @@ {/each}