Skip to content

Commit

Permalink
feat(atomic): add skip link to grid results (#3045)
Browse files Browse the repository at this point in the history
* Handle click with JS rather than link

https://coveord.atlassian.net/browse/KIT-2597

* Support click target as prop on grid items

https://coveord.atlassian.net/browse/KIT-2597

* Add generated files

https://coveord.atlassian.net/browse/KIT-2597

* Add basic grid result list E2E tests, leverage noopener

https://coveord.atlassian.net/browse/KIT-2597

* Add grid result list quickview tests

https://coveord.atlassian.net/browse/KIT-2597

* Add skip link to each result

https://coveord.atlassian.net/browse/KIT-2597

* Rename prop, better docs, support cmd + click

https://coveord.atlassian.net/browse/KIT-2597

* Change default value to _self

https://coveord.atlassian.net/browse/KIT-2597

* Fix result-list-selectors

https://coveord.atlassian.net/browse/KIT-2597

* Leverage focus-visible pseudo-class

https://coveord.atlassian.net/browse/KIT-2597

* Remove ariaHidden from skip link

https://coveord.atlassian.net/browse/KIT-2597

* Add additional props to ResultLink

https://coveord.atlassian.net/browse/KIT-2597

---------

Co-authored-by: GitHub Actions Bot <>
  • Loading branch information
dmbrooke authored Aug 2, 2023
1 parent 9b422ce commit 049b420
Show file tree
Hide file tree
Showing 20 changed files with 222 additions and 27 deletions.
10 changes: 8 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
"cache": {
"useCache": true
},
"ignorePaths": ["CHANGELOG.md"],
"import": ["@cspell/dict-fr-fr/cspell-ext.json"],
"ignorePaths": [
"CHANGELOG.md"
],
"import": [
"@cspell/dict-fr-fr/cspell-ext.json"
],
"words": [
"adjustednumberoflikes",
"ARROWUP",
Expand Down Expand Up @@ -43,10 +47,12 @@
"exchangemdn",
"exchangerssfeed",
"Expandto",
"genqa",
"gmailmessage",
"gzipped",
"headless",
"IIFE",
"inat",
"initializable",
"inlink",
"isloading",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -738,14 +738,14 @@ export declare interface AtomicRecsList extends Components.AtomicRecsList {}

@ProxyCmp({
defineCustomElementFn: undefined,
inputs: ['density', 'display', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation'],
inputs: ['density', 'display', 'gridCellLinkTarget', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation'],
methods: ['setRenderFunction', 'previousPage', 'nextPage']
})
@Component({
selector: 'atomic-recs-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
inputs: ['density', 'display', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation']
inputs: ['density', 'display', 'gridCellLinkTarget', 'headingLevel', 'imageSize', 'label', 'numberOfRecommendations', 'numberOfRecommendationsPerPage', 'recommendation']
})
export class AtomicRecsList {
protected el: HTMLElement;
Expand Down Expand Up @@ -1080,14 +1080,14 @@ export declare interface AtomicResultList extends Components.AtomicResultList {}

@ProxyCmp({
defineCustomElementFn: undefined,
inputs: ['density', 'display', 'imageSize'],
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize'],
methods: ['setRenderFunction']
})
@Component({
selector: 'atomic-result-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
inputs: ['density', 'display', 'imageSize']
inputs: ['density', 'display', 'gridCellLinkTarget', 'imageSize']
})
export class AtomicResultList {
protected el: HTMLElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {generateComponentHTML} from '../../../fixtures/fixture-common';
import {
TagProps,
generateComponentHTML,
} from '../../../fixtures/fixture-common';
import {TestFixture} from '../../../fixtures/test-fixture';
import * as CommonAssertions from '../../common-assertions';
import {addFacet} from '../../facets/facet/facet-actions';
Expand All @@ -10,11 +13,14 @@ import {
QuickviewModalSelectors,
} from './quickview-selectors';

const addQuickviewInResultList = () =>
const addQuickviewInResultList = (props: TagProps = {}) =>
addResultList(
buildTemplateWithSections({
actions: generateComponentHTML(quickviewComponent),
})
buildTemplateWithSections(
{
actions: generateComponentHTML(quickviewComponent),
},
props
)
);

const openModal = () => {
Expand Down Expand Up @@ -107,6 +113,82 @@ describe('Quickview Component', () => {
});
});

describe('when used on grid display result list', () => {
beforeEach(() => {
new TestFixture()
.with(addFacet({field: 'filetype'}))
.withHash('f-filetype=pdf')
.with(
addQuickviewInResultList({
display: 'grid',
})
)
.init();
});

it('should be accessible', () => {
CommonAssertions.assertAccessibility(QuickviewSelectors.firstInResult);
});

it('should not log error to console', () => {
CommonAssertions.assertConsoleError(false);
});

it('should display a header title', () => {
openModal();

QuickviewModalSelectors.titleLink()
.should('exist')
.should('have.attr', 'href');
});

it('should display a close button', () => {
openModal();
QuickviewModalSelectors.closeButton()
.should('exist')
.should('have.attr', 'aria-label', 'Close')
.click();

cy.get('body').should('not.have.class', 'atomic-modal-opened');
});

it('should display an iframe', () => {
openModal();
QuickviewModalSelectors.iframe()
.should('exist')
.its('0.contentDocument.body')
.should('not.be.empty');
});

it('should display a pager with next navigation', () => {
openModal();
QuickviewModalSelectors.pagerSummary()
.should('exist')
.should('have.text', 'Result 1 of 10');

QuickviewModalSelectors.nextButton().click();
QuickviewModalSelectors.pagerSummary().should(
'have.text',
'Result 2 of 10'
);
cy.expectClickEvent('documentQuickview');
});

it('should display a pager with previous navigation', () => {
openModal();
QuickviewModalSelectors.pagerSummary()
.should('exist')
.should('have.text', 'Result 1 of 10');

QuickviewModalSelectors.previousButton().click();
QuickviewModalSelectors.pagerSummary().should(
'have.text',
'Result 10 of 10'
);
cy.expectClickEvent('documentQuickview');
});
});

describe('when used on pdf file inside a result list with keywords', () => {
beforeEach(() => {
new TestFixture()
Expand Down
12 changes: 12 additions & 0 deletions packages/atomic/cypress/e2e/result-list/result-list-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ export const addFoldedResultList =
fixture.withElement(foldedResultList);
};

export const addGridResultList =
(template?: HTMLElement, tags?: TagProps) => (fixture: TestFixture) => {
const gridResultList = generateComponentHTML(resultListComponent, {
...tags,
display: 'grid',
});
if (template) {
gridResultList.appendChild(template);
}
fixture.withElement(gridResultList);
};

export const removeResultChildrenFromResponse = (fixture: TestFixture) => {
fixture.withCustomResponse((response) =>
response.results.forEach((result) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export const ResultListSelectors = {
result: () => ResultListSelectors.shadow().find(resultComponent),
resultGridClickable: () =>
ResultListSelectors.shadow().find('[part="result-list-grid-clickable"]'),
resultGridClickableContainer: () =>
ResultListSelectors.shadow().find(
'[part="result-list-grid-clickable-container"]'
),
firstResult: () => ResultListSelectors.result().first().shadow(),
firstResultRoot: () => ResultListSelectors.firstResult().find(resultRoot),
sections: {
Expand Down
14 changes: 13 additions & 1 deletion packages/atomic/cypress/e2e/result-list/result-list.cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from './folded-result-list-selectors';
import {
addFoldedResultList,
addGridResultList,
addResultList,
buildTemplateWithSections,
} from './result-list-actions';
Expand All @@ -31,7 +32,18 @@ const resultListConfig = {
title: 'Result List Component',
};

const configs = [foldedResultListConfig, resultListConfig] as const;
const gridResultListConfig = {
componentSelectors: ResultListSelectors,
componentTag: resultListComponent,
addResultFn: addGridResultList,
title: 'Grid result List Component',
};

const configs = [
foldedResultListConfig,
resultListConfig,
gridResultListConfig,
] as const;

configs.forEach(({componentSelectors, componentTag, addResultFn, title}) => {
describe(title, () => {
Expand Down
22 changes: 21 additions & 1 deletion packages/atomic/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { AutomaticFacet, CategoryFacetSortCriterion, FacetSortCriterion, FoldedR
import { AnyBindings } from "./components/common/interface/bindings";
import { DateFilter, DateFilterState, NumericFilter, NumericFilterState, RelativeDateUnit } from "./components/common/types";
import { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type";
import { ResultDisplayBasicLayout, ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout } from "./components/common/layout/display-options";
import { ResultDisplayBasicLayout, ResultDisplayDensity, ResultDisplayImageSize, ResultDisplayLayout, ResultTarget } from "./components/common/layout/display-options";
import { ResultRenderingFunction } from "./components/common/result-list/result-list-common-interface";
import { InsightEngine, InsightFacetSortCriterion, InsightFoldedResult, InsightInteractiveResult, InsightLogLevel, InsightRangeFacetRangeAlgorithm, InsightRangeFacetSortCriterion, InsightResult, InsightResultTemplate, InsightResultTemplateCondition, PlatformEnvironmentInsight } from "./components/insight";
import { FacetDisplayValues } from "./components/common/facets/facet-common";
Expand Down Expand Up @@ -1213,6 +1213,11 @@ export namespace Components {
* The layout to apply when displaying results themselves. This does not affect the display of the surrounding list itself. To modify the number of recommendations per column, modify the --atomic-recs-number-of-columns CSS variable.
*/
"display": ResultDisplayBasicLayout;
/**
* The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`.
* @defaultValue `_self`
*/
"gridCellLinkTarget": ResultTarget;
/**
* The [heading level](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) to use for the heading label, from 1 to 6.
*/
Expand Down Expand Up @@ -1467,6 +1472,11 @@ export namespace Components {
* The desired layout to use when displaying results. Layouts affect how many results to display per row and how visually distinct they are from each other.
*/
"display": ResultDisplayLayout;
/**
* The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`.
* @defaultValue `_self`
*/
"gridCellLinkTarget": ResultTarget;
/**
* The expected size of the image displayed in the results.
*/
Expand Down Expand Up @@ -4139,6 +4149,11 @@ declare namespace LocalJSX {
* The layout to apply when displaying results themselves. This does not affect the display of the surrounding list itself. To modify the number of recommendations per column, modify the --atomic-recs-number-of-columns CSS variable.
*/
"display"?: ResultDisplayBasicLayout;
/**
* The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`.
* @defaultValue `_self`
*/
"gridCellLinkTarget"?: ResultTarget;
/**
* The [heading level](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) to use for the heading label, from 1 to 6.
*/
Expand Down Expand Up @@ -4373,6 +4388,11 @@ declare namespace LocalJSX {
* The desired layout to use when displaying results. Layouts affect how many results to display per row and how visually distinct they are from each other.
*/
"display"?: ResultDisplayLayout;
/**
* The target location to open the result link (see [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)). This property is only leveraged when `display` is `grid`.
* @defaultValue `_self`
*/
"gridCellLinkTarget"?: ResultTarget;
/**
* The expected size of the image displayed in the results.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/atomic/src/components/common/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {

export interface ButtonProps {
style: ButtonStyle;
onClick?(): void;
onClick?(event?: MouseEvent): void;
class?: string;
text?: string;
part?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type ResultDisplayBasicLayout = 'list' | 'grid';
export type ResultDisplayLayout = ResultDisplayBasicLayout | 'table';
export type ResultDisplayDensity = 'comfortable' | 'normal' | 'compact';
export type ResultDisplayImageSize = 'large' | 'small' | 'icon' | 'none';
export type ResultTarget = '_self' | '_blank' | '_parent' | '_top';

function getDisplayClass(display: ResultDisplayLayout) {
switch (display) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class ResultChildrenCommon {
);
}

private renderChildren(children: FoldedResult[]) {
private renderChildren(children: FoldedResult[]): VNode | VNode[] {
return this.renderChildrenWrapper(
children.map((child, i) =>
this.props.renderChild(child, i === children.length - 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {FunctionalComponent, h} from '@stencil/core';
import {filterProtocol} from '../../../utils/xss-utils';
import {ResultTarget} from '../layout/display-options';

export interface ResultLinkEventProps {
onSelect: () => void;
Expand All @@ -18,6 +19,8 @@ export interface ResultLinkProps extends ResultLinkEventProps {
attributes?: Attr[];
tabIndex?: number;
ariaHidden?: boolean;
target?: ResultTarget;
rel?: string;
}

export const bindAnalyticsToLink = (
Expand Down Expand Up @@ -64,7 +67,9 @@ export const LinkWithResultAnalytics: FunctionalComponent<ResultLinkProps> = (
attributes,
tabIndex,
ariaHidden,
rel,
stopPropagation = true,
target = '_self',
},
children
) => {
Expand All @@ -73,7 +78,9 @@ export const LinkWithResultAnalytics: FunctionalComponent<ResultLinkProps> = (
class={className}
part={part}
href={filterProtocol(href)}
target={target}
title={title}
rel={rel}
ref={(el) => {
if (ref) {
ref(el);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ export const GridDisplayResults: FunctionalComponent<ResultListDisplayProps> = (
<div
part="result-list-grid-clickable-container outline"
ref={(element) => props.setNewResultRef(element!, index)}
onClick={(event) => {
event.preventDefault();
interactiveResult.select();
window.open(
unfoldedResult.clickUri,
event.ctrlKey || event.metaKey
? '_blank'
: props.gridCellLinkTarget,
'noopener'
);
}}
>
<LinkWithResultAnalytics
part="result-list-grid-clickable"
Expand All @@ -22,9 +33,11 @@ export const GridDisplayResults: FunctionalComponent<ResultListDisplayProps> = (
onCancelPendingSelect={() => interactiveResult.cancelPendingSelect()}
href={unfoldedResult.clickUri}
title={unfoldedResult.title}
tabIndex={-1}
ariaHidden={true}
/>
target={props.gridCellLinkTarget}
rel="noopener"
>
{unfoldedResult.title}
</LinkWithResultAnalytics>
{props.renderResult({
key: props.getResultId(result),
result: result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ResultDisplayDensity,
ResultDisplayImageSize,
ResultDisplayLayout,
ResultTarget,
} from '../layout/display-options';
import {ResultTemplateProvider} from './result-template-provider';

Expand All @@ -25,6 +26,7 @@ export interface ResultListCommonProps<
loadingFlag: string;
resultTemplateProvider: ResultTemplateProvider;
nextNewResultTarget: FocusTargetController;
gridCellLinkTarget: ResultTarget;
getLayoutDisplay(): ResultDisplayLayout;
getResultDisplay(): ResultDisplayLayout;
getDensity(): ResultDisplayDensity;
Expand Down
Loading

0 comments on commit 049b420

Please sign in to comment.