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 / Read title while searching (accessibility) #487

Merged
merged 4 commits into from
Apr 18, 2024
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
4 changes: 3 additions & 1 deletion packages/ui-react/src/components/ErrorPage/ErrorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export const ErrorPageWithoutTranslation = ({ title, children, message, learnMor
<div className={styles.errorPage}>
<div className={styles.box}>
<img className={styles.image} src={logo || '/images/logo.png'} alt={alt} />
<h1 className={styles.title}>{title || 'An error occurred'}</h1>
<h1 className={styles.title} aria-live="polite">
{title || 'An error occurred'}
</h1>
<div className={styles.main}>
<p className={styles.message}>{message || 'Try refreshing this page or come back later.'}</p>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ exports[`<ErrorPage> > renders and matches snapshot 1`] = `
src="/images/logo.png"
/>
<h1
aria-live="polite"
class="_title_8c5621"
>
This is the title
Expand Down
47 changes: 34 additions & 13 deletions packages/ui-react/src/pages/Search/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
import { useParams } from 'react-router';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -34,7 +34,19 @@ const Search = () => {
// User
const { user, subscription } = useAccountStore(({ user, subscription }) => ({ user, subscription }), shallow);

const getURL = (playlistItem: PlaylistItem) => mediaURL({ media: playlistItem, playlistId: features?.searchPlaylist });
const getURL = (playlistItem: PlaylistItem) =>
mediaURL({
media: playlistItem,
playlistId: features?.searchPlaylist,
});

const title = useMemo(() => {
if (isFetching) return t('heading');
if (!query) return t('start_typing');
if (!playlist?.playlist.length) return t('no_results_heading', { query });

return t('title', { count: playlist.playlist.length, query });
}, [isFetching, playlist?.playlist.length, query, t]);

// Update the search bar query to match the route param on mount
useEffect(() => {
Expand Down Expand Up @@ -66,31 +78,40 @@ const Search = () => {
}

if (!query) {
return <ErrorPage title={t('start_typing')} />;
return <ErrorPage title={title} />;
}

if (!playlist?.playlist.length) {
return (
<ErrorPage title={t('no_results_heading', { query })}>
<h2 className={styles.subHeading}>{t('suggestions')}</h2>
<ul>
<li>{t('tip_one')}</li>
<li>{t('tip_two')}</li>
<li>{t('tip_three')}</li>
</ul>
</ErrorPage>
<>
<Helmet>
<title>
{title} - {siteName}
</title>
</Helmet>
<ErrorPage title={title}>
<h2 className={styles.subHeading}>{t('suggestions')}</h2>
<ul>
<li>{t('tip_one')}</li>
<li>{t('tip_two')}</li>
<li>{t('tip_three')}</li>
</ul>
</ErrorPage>
</>
);
}

return (
<div className={styles.search}>
<Helmet>
<title>
{t('title', { count: playlist.playlist.length, query })} - {siteName}
{title} - {siteName}
</title>
</Helmet>
<header className={styles.header}>
<h2 id={headingId}>{t('heading')}</h2>
<h2 id={headingId} aria-live={isFetching ? undefined : 'polite'}>
{title}
</h2>
</header>
<CardGrid
aria-labelledby={headingId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ exports[`<DemoConfigDialog> > renders and matches snapshot error dialog 1`] = `
src="/images/logo.png"
/>
<h1
aria-live="polite"
class="_title_d73633"
>
app_config_not_found
Expand Down
27 changes: 15 additions & 12 deletions platforms/web/test-e2e/tests/search_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,33 +54,36 @@ Scenario('Closing search return to original page (@mobile-only)', async ({ I })
});

Scenario('I can type a search phrase in the search bar', async ({ I }) => {
const searchTerm = 'Caminandes';
await openSearch(I);
I.fillField(searchBarLocator, 'Caminandes');
I.fillField(searchBarLocator, searchTerm);
I.seeElement(clearSearchLocator);

checkSearchResults(I, ['Caminandes 1', 'Caminandes 2', 'Caminandes 3']);
checkSearchResults(I, searchTerm, 3, ['Caminandes 1', 'Caminandes 2', 'Caminandes 3']);

I.click(clearSearchLocator);
assert.strictEqual('', await I.grabValueFrom(searchBarLocator));

checkSearchResults(I, []);
I.dontSee('Search results');
I.see(emptySearchPrompt);
});

Scenario('I can search by partial match', async ({ I }) => {
const searchTerm = 'ani';
await openSearch(I);
I.fillField(searchBarLocator, 'ani');
I.fillField(searchBarLocator, searchTerm);
I.seeElement(clearSearchLocator);

checkSearchResults(I, ['Minecraft Animation Workshop', 'Animating the Throw', 'Primitive Animals']);
checkSearchResults(I, searchTerm, 5, ['Minecraft Animation Workshop', 'Animating the Throw', 'Primitive Animals']);
});

Scenario('I get empty search results when no videos match', async ({ I }) => {
const searchTerm = 'Axdfsdfgfgfd';
await openSearch(I);
I.fillField(searchBarLocator, 'Axdfsdfgfgfd');
I.fillField(searchBarLocator, searchTerm);
I.seeElement(clearSearchLocator);

checkSearchResults(I, []);
checkSearchResults(I, searchTerm, 0, []);

I.see('No results found for "Axdfsdfgfgfd"');
I.see('Suggestions:');
Expand Down Expand Up @@ -124,17 +127,17 @@ Scenario('I can clear the search phrase manually', async ({ I }) => {
I.dontSee('Suggestions:');
});

function checkSearchResults(I: CodeceptJS.I, expectedResults: string[]) {
function checkSearchResults(I: CodeceptJS.I, searchTerm: string, expectedResults: number, searchMatches: string[]) {
I.dontSee('Blender Channel');
I.dontSee('All Films');

if (expectedResults.length > 0) {
I.see('Search results');
if (expectedResults > 0) {
I.see(`${expectedResults} results for "${searchTerm}"`, 'h2');
I.dontSee(emptySearchPrompt);
I.dontSee('No results found');
expectedResults.forEach((result) => I.see(result));
searchMatches.forEach((result) => I.see(result));
} else {
I.dontSee('Search results');
I.see(`No results found for "${searchTerm}"`, 'h1');
I.dontSeeElement('div[class*="cell"]');
I.dontSeeElement('div[class*="card"]');
I.dontSeeElement('div[class*="poster"]');
Expand Down
Loading