diff --git a/core/navigation/components/header/SearchButton.tsx b/core/navigation/components/header/SearchButton.tsx index 6458e3caa966539188e740c4b69251c964f396a2..6299e9c0d320566fba7af35ecda2de559cc2be6c 100644 --- a/core/navigation/components/header/SearchButton.tsx +++ b/core/navigation/components/header/SearchButton.tsx @@ -22,6 +22,7 @@ export const SearchButton = () => { navigate('/search') }} size="lg" + testID="btn-to-search" /> ) } diff --git a/core/screens/search/components/SearchContent.tsx b/core/screens/search/components/SearchContent.tsx index db3db75e1067a5399dccca98eae9504b612aa2fc..4f1321aa99689f79e688be6f0236fdc5a74a5be7 100644 --- a/core/screens/search/components/SearchContent.tsx +++ b/core/screens/search/components/SearchContent.tsx @@ -114,7 +114,7 @@ const SearchContent = () => { </> } > - <View style={[mixins.container, styles.inner]}> + <View style={[mixins.container, styles.inner]} testID="search-content"> {/* Results */} <SearchResultsList query={query} diff --git a/core/screens/search/typesense/SearchFacetsChips.tsx b/core/screens/search/typesense/SearchFacetsChips.tsx index 9752920033edb359c87ffa1c1954ffd38152ae8e..386e697a3eec1c3e602611bae25d34fefeb978f4 100644 --- a/core/screens/search/typesense/SearchFacetsChips.tsx +++ b/core/screens/search/typesense/SearchFacetsChips.tsx @@ -61,7 +61,14 @@ export const SearchFacetsChips = ({ initialFacet, onFacetChange }: SearchFacetsC {orderedItems.map(({ item }) => { const label = t(`search.typesense.facets.${item.label}`) return ( - <SelectableChip id={item.value} key={item.value} color="white" size="md" aria-label={label}> + <SelectableChip + id={item.value} + key={item.value} + color="white" + size="md" + aria-label={label} + testID={`facet-${item.value}`} + > {label} </SelectableChip> ) diff --git a/core/screens/search/typesense/SearchHit.tsx b/core/screens/search/typesense/SearchHit.tsx index f3605b2fba599214dacf13a737446fb9a9fcedd8..75bd8e5dcf0b38400cac973a46ce816a28217a32 100644 --- a/core/screens/search/typesense/SearchHit.tsx +++ b/core/screens/search/typesense/SearchHit.tsx @@ -36,13 +36,12 @@ export const SearchHit = Trackable<SearchHitProps>(({ hit, onPress }: SearchHitP return ( <SearchHitLink hit={hit} label={title} onPress={onPress}> - <View style={styles.hitItem}> + <View style={styles.hitItem} testID={`search-hit-${hit.type}`}> <Avatar initials={getInitials(title)} label={title} size={'md'} shape={hit.type === 'profile' ? 'circle' : 'square'} - role={'img'} imgSrc={hit.image_url} /> <View style={styles.hitItemContent}> diff --git a/core/screens/search/typesense/SearchTextInput.tsx b/core/screens/search/typesense/SearchTextInput.tsx index d26c9dd6b4c41cd39f499f714d03526345e1fe07..587a4b9ee840fb7c6a80f528a431e53f6ec2acfd 100644 --- a/core/screens/search/typesense/SearchTextInput.tsx +++ b/core/screens/search/typesense/SearchTextInput.tsx @@ -59,13 +59,18 @@ export function SearchTextInput({ initialQuery, onQueryChange, props }: SearchBo }} trailingComponent={ inputValue ? ( - <Pressable onPress={() => setQuery('')} accessibilityLabel={t('search.typesense.input.clear')}> + <Pressable + onPress={() => setQuery('')} + accessibilityLabel={t('search.typesense.input.clear')} + testID="search-input-clear" + > <HoliIcon icon={CloseCircleFilled} size={22} /> </Pressable> ) : ( <HoliIcon icon={Search} size={22} /> ) } + testID="search" /> ) } diff --git a/e2e/mobile/tests/journey/search.test.ts b/e2e/mobile/tests/journey/search.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..73eaa87abd8fd2d76feac862c9f0da9e4a5b40c9 --- /dev/null +++ b/e2e/mobile/tests/journey/search.test.ts @@ -0,0 +1,69 @@ +import { continueAsGuest } from '@holi/e2e-mobile/helpers/auth' +import { getDeviceScreenSize } from '@holi/e2e-mobile/helpers/gestures' +import { + checkIfElementWithTestIdExists, + fillFieldByTestId, + pressEnter, + tapOnElementWithTestId, + waitForElementWithTestId, +} from '@holi/e2e-mobile/helpers/selectors' + +describe('@Search', () => { + afterEach(async () => { + // Speeds up local execution + if (target === 'local') { + // navigate back to home screen + await tapOnElementWithTestId('nav-button-back') + } + }) + + it('should show and filter results', async () => { + await continueAsGuest() + + // Navigate to search + try { + await tapOnElementWithTestId('btn-to-search') + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + // On android in browserstack the above does not work + // Instead we touch via coordinates starting from the status bar + const size = await getDeviceScreenSize() + // @ts-ignore + const { pixelRatio, statBarHeight } = Appium.capabilities + const gap = 24 // Top + right gap to the search button + + await Appium.touchAction({ action: 'tap', x: size.width - gap * pixelRatio, y: statBarHeight + gap * pixelRatio }) + } + + const oldSearch = await checkIfElementWithTestIdExists('search-content') + if (oldSearch) { + // Abort as test only handles new search variant + return + } + + // Volunteering is selected by default + await waitForElementWithTestId('search-hit-volunteering') + + // Test search by query + await fillFieldByTestId('search-input', 'Test') + await waitForElementWithTestId('search-hit-volunteering') + + // Clear query to prepare for facet filtering + await tapOnElementWithTestId('search-input-clear') + await waitForElementWithTestId('search-hit-volunteering') + + // Close keyboard by submitting (empty) input + await pressEnter('search-input') + await waitForElementWithTestId('search-hit-volunteering') + + // Test facet filters + await tapOnElementWithTestId('facet-profile') + await waitForElementWithTestId('search-hit-profile') + + await tapOnElementWithTestId('facet-space') + await waitForElementWithTestId('search-hit-space') + + await tapOnElementWithTestId('facet-volunteering') + await waitForElementWithTestId('search-hit-volunteering') + }) +}) diff --git a/e2e/web/tests/search.spec.ts b/e2e/web/tests/search.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..412ae79c6dde6f06be4541b65794b8d9baf040ae --- /dev/null +++ b/e2e/web/tests/search.spec.ts @@ -0,0 +1,42 @@ +import { byTestId, checkAccessibility, clickElementByTestId, denyConsent, locateElementByTestId } from './helpers' +import { test } from './testmaster' + +test.describe('@Search', () => { + test('user journey', async ({ page }) => { + await page.goto('/search') + + await denyConsent(page) + + const oldSearch = await locateElementByTestId(page, 'search-content') + const oldSearchVisible = await oldSearch.isVisible() + if (oldSearchVisible) { + // Abort as test only handles new search variant + return + } + + // Volunteering is selected by default + await page.waitForSelector(byTestId('search-hit-volunteering')) + + // Test search by query + const emailInput = await locateElementByTestId(page, 'search-input') + await emailInput.fill('Test') + await page.waitForSelector(byTestId('search-hit-volunteering')) + + // Clear query to prepare for facet filtering + await clickElementByTestId(page, 'search-input-clear') + await page.waitForSelector(byTestId('search-hit-volunteering')) + + // Check accessibility + await checkAccessibility(page) + + // Test facet filters + await clickElementByTestId(page, 'facet-profile') + await page.waitForSelector(byTestId('search-hit-profile')) + + await clickElementByTestId(page, 'facet-space') + await page.waitForSelector(byTestId('search-hit-space')) + + await clickElementByTestId(page, 'facet-volunteering') + await page.waitForSelector(byTestId('search-hit-volunteering')) + }) +}) diff --git a/holi-bricks/components/chips/ChipAction.tsx b/holi-bricks/components/chips/ChipAction.tsx index 0d0501969bfe38e3926c08772aa006d1e1ab93e1..c83fb125d474c1bbf4632f0dbedb46760a5024b9 100644 --- a/holi-bricks/components/chips/ChipAction.tsx +++ b/holi-bricks/components/chips/ChipAction.tsx @@ -26,6 +26,7 @@ export type ChipActionProps = AccessibilityProps & { iconLeading?: HoliIconType iconTrailing?: HoliIconType 'aria-label'?: string + testID?: string } export const ChipAction = ({ @@ -38,6 +39,7 @@ export const ChipAction = ({ iconLeading, iconTrailing, role = 'switch', + testID, ...rest }: ChipActionProps) => { const { styles } = useStyles(stylesheet, { size, selected, disabled }) @@ -51,6 +53,7 @@ export const ChipAction = ({ aria-checked={selected} role={role} style={({ hovered, focused }) => [styles.chip(!!hovered, !disabled && !!focused, color)]} + testID={testID} > {iconLeading && <HoliIcon icon={iconLeading} size={iconSize} color={styles.text.color} />} {children && (