Skip to content
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
66 changes: 44 additions & 22 deletions .storybook/wcag/test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,49 @@ import { Page } from 'playwright-core';
import { runOnly } from '../wcagConfig';
import { TestContext, TestRunnerConfig } from '@storybook/test-runner';

const AXE_ALREADY_RUNNING_ERROR = 'Axe is already running';
const MAX_AXE_RETRIES = 3;
const AXE_RETRY_DELAY_MS = 250;

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

// Retry if errors from Axe already running to decrease flakiness
const runA11yCheck = async (page: Page, context: TestContext) => {
for (let attempt = 1; attempt <= MAX_AXE_RETRIES; attempt++) {
try {
await checkA11y(
page,
{
exclude: [
'#root .mapboxgl-canvas-container',
'.mapboxgl-marker',
'.mapboxgl-popup-close-button'
],
},
{
axeOptions: {
runOnly,
rules: {
'color-contrast': { enabled: context.name !== 'Loading' },
},
},
detailedReport: true,
detailedReportOptions: {
html: true,
},
}
);
return;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!message.includes(AXE_ALREADY_RUNNING_ERROR) || attempt === MAX_AXE_RETRIES) {
throw error;
}
await sleep(AXE_RETRY_DELAY_MS * attempt);
}
}
};

/**
* See https://storybook.js.org/docs/react/writing-tests/test-runner#test-hook-api-experimental
* to learn more about the test-runner hooks API.
Expand All @@ -12,28 +55,7 @@ const renderFunctions: TestRunnerConfig = {
await injectAxe(page);
},
async postVisit(page: Page, context: TestContext) {
await checkA11y(
page,
{
exclude: [
'#root .mapboxgl-canvas-container',
'.mapboxgl-marker',
'.mapboxgl-popup-close-button'
],
},
{
axeOptions: {
runOnly,
rules: {
'color-contrast': { enabled: context.name !== 'Loading' },
},
},
detailedReport: true,
detailedReportOptions: {
html: true,
},
}
);
await runA11yCheck(page, context);
},
};

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@yext/search-ui-react",
"version": "2.1.1",
"version": "2.1.2",
"description": "A library of React Components for powering Yext Search integrations",
"author": "watson@yext.com",
"license": "BSD-3-Clause",
Expand Down
6 changes: 2 additions & 4 deletions src/components/AppliedFiltersDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ export function AppliedFiltersDisplay(props: AppliedFiltersDisplayProps): React.
{removableFiltersWithHandlers.map(({ filter, handleRemove }, i) => {
return (
<RemovableFilter
key={`${filter.displayName ?? 'filter'}-${i}`}
displayName={filter.displayName}
handleRemove={handleRemove}
index={i}
cssClasses={cssClasses}
/>
);
Expand Down Expand Up @@ -119,17 +119,15 @@ function getDedupedRemovableFilters(filters: RemovableFilter[]) {
function RemovableFilter({
displayName,
handleRemove,
index,
cssClasses
}: {
displayName: string | undefined,
handleRemove: () => void,
index: number,
cssClasses: AppliedFiltersCssClasses
}): React.JSX.Element {
const { t } = useTranslation();
return (
<div className={cssClasses.removableFilter} key={`${displayName}-${index}`}>
<div className={cssClasses.removableFilter}>
<div className={cssClasses.filterLabel}>{displayName}</div>
<button
className='w-2 h-2 text-neutral m-1.5'
Expand Down
17 changes: 14 additions & 3 deletions src/components/SearchI18nextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,24 @@ export function SearchI18nextProvider(props: PropsWithChildren<SearchI18nextConf
translationOverrides && Object.entries(translationOverrides).forEach(([locale, translation]) => {
i18nInstance.addResourceBundle(locale, 'search-ui-react', translation, true, true);
});
i18nInstance.changeLanguage(searcher.state.meta.locale);
searcher.addListener<string | undefined>({
const initialLocale = searcher.state.meta.locale ?? 'en';
if (i18nInstance.language !== initialLocale) {
void i18nInstance.changeLanguage(initialLocale);
}

const unsubscribe = searcher.addListener<string | undefined>({
valueAccessor: state => state.meta.locale,
callback: locale => {
i18nInstance.changeLanguage(locale);
const normalizedLocale = locale ?? 'en';
if (i18nInstance.language !== normalizedLocale) {
void i18nInstance.changeLanguage(normalizedLocale);
}
}
});

return () => {
unsubscribe();
};
}, [searcher, translationOverrides]);

return (
Expand Down
2 changes: 1 addition & 1 deletion test-site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions tests/__setup__/setup-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,31 @@ globalWithMessageChannel.MessageChannel = globalWithMessageChannel.MessageChanne
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

const SUPPRESSED_TEST_WARNINGS = [
/An update to .* inside a test was not wrapped in act\(\.\.\.\)/,
/Error occured executing generative direct answer\./,
];

const SUPPRESSED_TEST_LOGS = [
/react-i18next:: useTranslation: You will need to pass in an i18next instance/
];

const originalConsoleError = console.error.bind(console);
const originalConsoleWarn = console.warn.bind(console);

console.error = (...args) => {
const firstArg = args[0];
if (typeof firstArg === 'string' && SUPPRESSED_TEST_WARNINGS.some(pattern => pattern.test(firstArg))) {
return;
}
originalConsoleError(...args);
};

console.warn = (...args) => {
const firstArg = args[0];
if (typeof firstArg === 'string' && SUPPRESSED_TEST_LOGS.some(pattern => pattern.test(firstArg))) {
return;
}
originalConsoleWarn(...args);
};
13 changes: 10 additions & 3 deletions tests/ssr/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { render } from '@testing-library/react';
import { renderToString } from 'react-dom/server';
import { ReactElement } from 'react';
import React, { ReactElement } from 'react';
import { I18nextProvider } from 'react-i18next';
import { i18nInstance } from '../../src/utils';

const USE_LAYOUT_EFFECT_ERROR = /useLayoutEffect does nothing on the server/;
const originalConsoleError = console.error.bind(console.error);

export function testSSR(App: ReactElement) {
const renderOnServer = () => renderToString(App);
const wrappedApp = (
<I18nextProvider i18n={i18nInstance}>
{App}
</I18nextProvider>
);
const renderOnServer = () => renderToString(wrappedApp);
const container = document.body.appendChild(document.createElement('div'));
let unexpectedErrorCount = 0;
jest.spyOn(global.console, 'error')
Expand All @@ -28,6 +35,6 @@ export function testSSR(App: ReactElement) {
container.innerHTML = renderOnServer();

// hydrate a container whose HTML contents were rendered by ReactDOMServer
render(App, { container, hydrate: true });
render(wrappedApp, { container, hydrate: true });
expect(unexpectedErrorCount).toEqual(0);
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"noImplicitAny": false,
"module": "es2020",
"moduleResolution": "node",
"outDir": "dist",
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
Expand Down
Loading