Skip to content

Commit

Permalink
chore: Add loading prop to Text- and PropertyFilter to improve countT…
Browse files Browse the repository at this point in the history
…ext announcement (#3122)

Co-authored-by: Johannes Weber <[email protected]>
  • Loading branch information
johannes-weber and Johannes Weber authored Dec 13, 2024
1 parent 85cb377 commit c3fcf07
Show file tree
Hide file tree
Showing 13 changed files with 645 additions and 19 deletions.
153 changes: 153 additions & 0 deletions pages/property-filter/count-text.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';

import { I18nProvider } from '~components/i18n';
import messages from '~components/i18n/messages/all.en';
import PropertyFilter, { PropertyFilterProps } from '~components/property-filter';
import SpaceBetween from '~components/space-between';

export default function () {
const [query, setQuery] = React.useState<PropertyFilterProps['query']>({
tokens: [
{
operator: '=',
propertyKey: 'instanceid',
value: 'i-2dc5ce28a0328391',
},
],
operation: 'and',
});
const [countText, setCountText] = React.useState('');
const [loading, setLoading] = React.useState(false);
const propertyFilterRef = React.useRef<PropertyFilterProps.Ref>(null);

return (
<I18nProvider messages={[messages]} locale="en">
<h1>Demo page for countText live announcement testing</h1>
<SpaceBetween size="l">
<SpaceBetween direction="horizontal" size="s" key="settings">
<span>Count Text:</span>
{['18 matches', '36 matches', ''].map((countText, index) => (
<label key={index}>
<input type="radio" name="count-text" value={countText} onChange={() => setCountText(countText)} />{' '}
{countText !== '' ? countText : '[Empty]'}
</label>
))}
</SpaceBetween>
<label htmlFor="loading-state-toggle" key="loadingStateToggle">
<input id="loading-state-toggle" type="checkbox" onChange={event => setLoading(event.target.checked)} />{' '}
Loading
</label>
<PropertyFilter
key="property-filter"
query={query}
onChange={({ detail }) => setQuery(detail)}
countText={countText}
ref={propertyFilterRef}
expandToViewport={true}
filteringAriaLabel="Find distributions"
loading={loading}
filteringOptions={[
{
propertyKey: 'instanceid',
value: 'i-2dc5ce28a0328391',
},
{
propertyKey: 'instanceid',
value: 'i-d0312e022392efa0',
},
{
propertyKey: 'instanceid',
value: 'i-070eef935c1301e6',
},
{
propertyKey: 'instanceid',
value: 'i-3b44795b1fea36ac',
},
{ propertyKey: 'state', value: 'Stopped' },
{ propertyKey: 'state', value: 'Stopping' },
{ propertyKey: 'state', value: 'Pending' },
{ propertyKey: 'state', value: 'Running' },
{
propertyKey: 'instancetype',
value: 't3.small',
},
{
propertyKey: 'instancetype',
value: 't2.small',
},
{ propertyKey: 'instancetype', value: 't3.nano' },
{
propertyKey: 'instancetype',
value: 't2.medium',
},
{
propertyKey: 'instancetype',
value: 't3.medium',
},
{
propertyKey: 'instancetype',
value: 't2.large',
},
{ propertyKey: 'instancetype', value: 't2.nano' },
{
propertyKey: 'instancetype',
value: 't2.micro',
},
{
propertyKey: 'instancetype',
value: 't3.large',
},
{
propertyKey: 'instancetype',
value: 't3.micro',
},
{ propertyKey: 'averagelatency', value: '17' },
{ propertyKey: 'averagelatency', value: '53' },
{ propertyKey: 'averagelatency', value: '73' },
{ propertyKey: 'averagelatency', value: '74' },
{ propertyKey: 'averagelatency', value: '107' },
{ propertyKey: 'averagelatency', value: '236' },
{ propertyKey: 'averagelatency', value: '242' },
{ propertyKey: 'averagelatency', value: '375' },
{ propertyKey: 'averagelatency', value: '402' },
{ propertyKey: 'averagelatency', value: '636' },
{ propertyKey: 'averagelatency', value: '639' },
{ propertyKey: 'averagelatency', value: '743' },
{ propertyKey: 'averagelatency', value: '835' },
{ propertyKey: 'averagelatency', value: '981' },
{ propertyKey: 'averagelatency', value: '995' },
]}
filteringPlaceholder="Find distributions"
filteringProperties={[
{
key: 'instanceid',
operators: ['=', '!=', ':', '!:', '^', '!^'],
propertyLabel: 'Instance ID',
groupValuesLabel: 'Instance ID values',
},
{
key: 'state',
operators: ['=', '!=', ':', '!:', '^', '!^'],
propertyLabel: 'State',
groupValuesLabel: 'State values',
},
{
key: 'instancetype',
operators: ['=', '!=', ':', '!:', '^', '!^'],
propertyLabel: 'Instance type',
groupValuesLabel: 'Instance type values',
},
{
key: 'averagelatency',
operators: ['=', '!=', '>', '<', '<=', '>='],
propertyLabel: 'Average latency',
groupValuesLabel: 'Average latency values',
},
]}
/>
</SpaceBetween>
</I18nProvider>
);
}
45 changes: 45 additions & 0 deletions pages/text-filter/count-text.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';

import { I18nProvider } from '~components/i18n';
import messages from '~components/i18n/messages/all.en';
import SpaceBetween from '~components/space-between';
import TextFilter, { TextFilterProps } from '~components/text-filter';

export default function () {
const [filteringText, setFilteringText] = React.useState('Hello World');
const [countText, setCountText] = React.useState('');
const [loading, setLoading] = React.useState(false);
const textFilterRef = React.useRef<TextFilterProps.Ref>(null);

return (
<I18nProvider messages={[messages]} locale="en">
<h1>Demo page for countText live announcement testing</h1>
<SpaceBetween size="l">
<SpaceBetween direction="horizontal" size="s" key="settings">
<span>Count Text:</span>
{['18 matches', '36 matches', ''].map((countText, index) => (
<label key={index}>
<input type="radio" name="count-text" value={countText} onChange={() => setCountText(countText)} />{' '}
{countText !== '' ? countText : '[Empty]'}
</label>
))}
</SpaceBetween>
<label htmlFor="loading-state-toggle" key="loadingStateToggle">
<input id="loading-state-toggle" type="checkbox" onChange={event => setLoading(event.target.checked)} />{' '}
Loading
</label>
<TextFilter
filteringText={filteringText}
filteringPlaceholder="Find instances"
filteringAriaLabel="Filter instances"
loading={loading}
countText={countText}
onChange={({ detail }) => setFilteringText(detail.filteringText)}
ref={textFilterRef}
/>
</SpaceBetween>
</I18nProvider>
);
}
16 changes: 16 additions & 0 deletions src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12921,6 +12921,7 @@ is provided by its parent form field component.
If the total number of results is unknown, also include an indication that there may be more results than
the number listed. For example, "25+ matches."
The count text is only displayed when \`query.tokens\` isn't empty.
When the \`countText\` or \`query\` changes, it will be announced to assistive technologies.
",
"name": "countText",
"optional": true,
Expand Down Expand Up @@ -13329,6 +13330,13 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
"optional": true,
"type": "string",
},
{
"description": "Set to \`true\` while the related collection is loading (e.g. during an async filtering action).
If set to \`true\`, the live announcement of countText by assistive technologies will be paused until it changes back to \`false\`.",
"name": "loading",
"optional": true,
"type": "boolean",
},
{
"description": "An object representing the current query displayed in the property filter, which has three properties: \`operation\`, \`tokens\`, and \`tokenGroups\`.
The \`operation\` property has two valid values: "and", "or", and controls the join operation to be applied between tokens when filtering the items.
Expand Down Expand Up @@ -16854,6 +16862,7 @@ is provided by its parent form field component.
If the total number of results is unknown, also include an indication that there may be more results than
the number listed. For example, "25+ matches."
The count text is only displayed when \`filteringText\` isn't empty.
When the \`countText\` or \`filteringText\` changes, it will be announced to assistive technologies.
",
"name": "countText",
"optional": true,
Expand Down Expand Up @@ -16910,6 +16919,13 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
"optional": true,
"type": "string",
},
{
"description": "Set to \`true\` while the related collection is loading (e.g. during an async filtering action).
If set to \`true\`, the live announcement of countText by assistive technologies will be paused until it changes back to \`false\`.",
"name": "loading",
"optional": true,
"type": "boolean",
},
],
"regions": [],
"releaseStatus": "stable",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { act, render } from '@testing-library/react';

Expand Down Expand Up @@ -82,9 +81,10 @@ const filteringOptions: readonly FilteringOption[] = [

const defaultProps = createDefaultProps(filteringProperties, filteringOptions);

const renderComponent = (props?: Partial<PropertyFilterProps & { ref: React.Ref<Ref> }>) => {
const { container } = render(<PropertyFilter {...defaultProps} {...props} />);
return { container, propertyFilterWrapper: createWrapper(container).findPropertyFilter()! };
type RenderComponentProps = Partial<PropertyFilterProps & { ref: React.Ref<Ref> }>;
const renderComponent = (props?: RenderComponentProps) => {
const { container, rerender } = render(<PropertyFilter {...defaultProps} {...props} />);
return { container, propertyFilterWrapper: createWrapper(container).findPropertyFilter()!, rerender };
};

describe('filtering input', () => {
Expand Down Expand Up @@ -418,6 +418,66 @@ describe('count text', () => {
});
expect(wrapper.findResultsCount()!.getElement()).toHaveTextContent('5 matches');
});

describe('live announcement', () => {
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

function getPoliteRegion() {
return document.querySelector('[aria-live=polite]')!;
}

const testProps: RenderComponentProps = {
enableTokenGroups: true,
countText: '5 matches',
query: { operation: 'or', tokens: [], tokenGroups: [{ propertyKey: 'string', value: 'first', operator: ':' }] },
};

test('includes the live announcement when all conditions met', () => {
const { propertyFilterWrapper: wrapper } = renderComponent(testProps);
jest.runAllTimers();
expect(wrapper.findResultsCount().getElement()).toHaveTextContent('5 matches');
expect(getPoliteRegion()).toHaveTextContent('5 matches');
});

test('does not include the live announcement when loading = true', () => {
const { propertyFilterWrapper: wrapper } = renderComponent({
...testProps,
loading: true,
});
jest.runAllTimers();
expect(wrapper.findResultsCount().getElement()).toHaveTextContent('5 matches');
expect(getPoliteRegion()).toBeNull();
});

test('includes the live announcement when loading switches from true to false', () => {
const { rerender } = renderComponent({
...testProps,
loading: true,
});
jest.runAllTimers();
expect(getPoliteRegion()).toBeNull();

rerender(<PropertyFilter {...defaultProps} {...testProps} loading={false} />);
jest.runAllTimers();
expect(getPoliteRegion()).toHaveTextContent('5 matches');
});

test('re-announce the live announcement when countText changes', () => {
const { rerender } = renderComponent(testProps);
jest.runAllTimers();
expect(getPoliteRegion()).toHaveTextContent('5 matches');

rerender(<PropertyFilter {...defaultProps} {...testProps} countText="123 matches" loading={false} />);
jest.runAllTimers();
expect(getPoliteRegion()).toHaveTextContent('123 matches');
});
});
});

describe('constraint text', () => {
Expand Down
8 changes: 8 additions & 0 deletions src/property-filter/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,16 @@ export interface PropertyFilterProps extends BaseComponentProps, ExpandToViewpor
* the number listed. For example, "25+ matches."
*
* The count text is only displayed when `query.tokens` isn't empty.
* When the `countText` or `query` changes, it will be announced to assistive technologies.
*/
countText?: string;

/**
* Set to `true` while the related collection is loading (e.g. during an async filtering action).
* If set to `true`, the live announcement of countText by assistive technologies will be paused until it changes back to `false`.
*/
loading?: boolean;

/**
* An object representing the current query displayed in the property filter, which has three properties: `operation`, `tokens`, and `tokenGroups`.
* The `operation` property has two valid values: "and", "or", and controls the join operation to be applied between tokens when filtering the items.
Expand Down
Loading

0 comments on commit c3fcf07

Please sign in to comment.