Skip to content

Commit

Permalink
feat: adds test id to the cards
Browse files Browse the repository at this point in the history
  • Loading branch information
orangevolon committed Nov 11, 2024
1 parent 35ee8c7 commit da09f0e
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 8 deletions.
49 changes: 43 additions & 6 deletions src/cards/__tests__/cards.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function findFooterPagination(wrapper: CardsWrapper): PaginationWrapper | null {

const cardDefinition: CardsProps.CardDefinition<Item> = {
header: item => `Header ${item.name}`,
testId: item => `card-${item.id}`,
sections: [
{
id: 'description',
Expand Down Expand Up @@ -59,14 +60,19 @@ describe('Cards', () => {
// index is 0-based
const getCard = (index: number) => wrapper.findItems()[index];

const getCardHeader = (index: number) => getCard(index)?.findCardHeader()?.getElement();
const getCardByTestId = (testId: string) => wrapper.findItemByTestId(testId)!;

const getCardHeader = (index: number) => wrapper.findItems()[index]?.findCardHeader()?.getElement();

// index is 0-based
const getCardsSections = (index: number) => getCard(index).findSections();
const getCardsSections = (index: number) => wrapper.findItems()[index].findSections();

// cardIndex and sectionIndex are 0-based
const getCardSection = (cardIndex: number, sectionIndex: number) => getCardsSections(cardIndex)[sectionIndex];

const getCardSectionByTestId = (cardIndex: number, sectionTestId: string) =>
getCard(cardIndex)!.findSectionByTestId(sectionTestId)!;

const getCardSectionHeader = (cardIndex: number, sectionIndex: number) =>
getCardSection(cardIndex, sectionIndex)?.findSectionHeader()?.getElement();

Expand All @@ -85,8 +91,7 @@ describe('Cards', () => {
wrapper = renderCards(
<Cards<Item> cardDefinition={cardDefinition} items={defaultItems} selectionType="single" />
).wrapper;

const cardsOrderedList = getCard(0).getElement().parentElement;
const cardsOrderedList = getCardByTestId('card-1').getElement().parentElement;
expect(cardsOrderedList).toHaveAttribute('role', 'group');
});

Expand Down Expand Up @@ -142,6 +147,7 @@ describe('Cards', () => {
sections: [
{
content: item => item.name,
testId: item => `card-${item.id}-content-section`,
},
],
}}
Expand All @@ -151,7 +157,8 @@ describe('Cards', () => {
defaultItems.forEach((item, idx) => {
expect(getCardsSections(idx)).toHaveLength(1);

expect(getCardSection(idx, 0).findSectionHeader()).toBe(null);
const sectionTestId = `card-${idx + 1}-content-section`;
expect(getCardSectionByTestId(idx, sectionTestId).findSectionHeader()).toBe(null);
expect(getCardSectionContent(idx, 0)).toHaveTextContent(item.name);
});
});
Expand All @@ -176,6 +183,36 @@ describe('Cards', () => {
expect(getCardSection(idx, 0)?.findContent()).toBe(null);
});
});

it('assigns test id attributes to the cards and sections', () => {
const { wrapper } = renderCards(
<Cards<Item>
cardDefinition={{
testId: item => `${item.name}-${item.id}`,
sections: [
{
testId: item => `${item.name}-${item.id}-id-section`,
header: 'id',
},
{
testId: item => `${item.name}-${item.id}-name-section`,
header: 'name',
},
],
}}
items={defaultItems}
/>
);

const itemTestIds = wrapper.findItems().map(item => item.getElement()!.getAttribute('data-testid'));
expect(itemTestIds).toEqual(['Apples-1', 'Oranges-2', 'Bananas-3']);

const secondItemSectionTestIds = wrapper
.findItems()[1]
.findSections()
.map(section => section.getElement()!.getAttribute('data-testid'));
expect(secondItemSectionTestIds).toEqual(['Oranges-2-id-section', 'Oranges-2-name-section']);
});
});

describe('header region', () => {
Expand Down Expand Up @@ -308,7 +345,7 @@ describe('Cards', () => {
<Cards<Item> cardDefinition={cardDefinition} selectionType="multi" items={defaultItems} />
</TestI18nProvider>
));
expect(getCard(0).findSelectionArea()!.getElement()).toHaveAttribute(
expect(getCardByTestId('card-1').findSelectionArea()!.getElement()).toHaveAttribute(
'aria-label',
expect.stringContaining('Custom label')
);
Expand Down
10 changes: 8 additions & 2 deletions src/cards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ const CardsList = <T,>({
[styles['card-selected']]: selectable && isItemSelected(item),
})}
key={getItemKey(trackBy, item, index)}
data-testid={cardDefinition.testId?.(item)}
onFocus={onFocus}
{...(focusMarkers && focusMarkers.item)}
role={listItemRole}
Expand Down Expand Up @@ -301,8 +302,13 @@ const CardsList = <T,>({
</div>
)}
</div>
{visibleSectionsDefinition.map(({ width = 100, header, content, id }, index) => (
<div key={id || index} className={styles.section} style={{ width: `${width}%` }}>
{visibleSectionsDefinition.map(({ width = 100, header, content, id, testId }, index) => (
<div
key={id || index}
className={styles.section}
style={{ width: `${width}%` }}
data-testid={testId?.(item)}
>
{header ? <div className={styles['section-header']}>{header}</div> : ''}
{content ? <div className={styles['section-content']}>{content(item)}</div> : ''}
</div>
Expand Down
18 changes: 18 additions & 0 deletions src/cards/interfaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,31 @@ export namespace CardsProps {
export interface CardDefinition<T> {
header?(item: T): React.ReactNode;
sections?: ReadonlyArray<SectionDefinition<T>>;

/**
* Returns the test ID for each card item.
* Returned value is assigned to the `data-testid` attribute of the card's root element.
*
* @param {T} item Single item from the specified `items` array.
* @returns {string} Test id to be assigned to the corresponding card.
*/
testId?(item: T): string;
}

export interface SectionDefinition<T> {
id?: string;
header?: React.ReactNode;
content?(item: T): React.ReactNode;
width?: number;

/**
* Returns the test ID for each section item.
* Returned value is assigned to the `data-testid` attribute of the section's root element.
*
* @param {T} item Single item from the specified `items` array.
* @returns {string} Test id to be assigned to the corresponding section.
*/
testId?(item: T): string;
}

export interface CardsLayout {
Expand Down
27 changes: 27 additions & 0 deletions src/test-utils/dom/cards/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { ComponentWrapper, ElementWrapper } from '@cloudscape-design/test-utils-core/dom';
import { escapeSelector } from '@cloudscape-design/test-utils-core/utils';

import CollectionPreferencesWrapper from '../collection-preferences';
import ContainerWrapper from '../container';
Expand Down Expand Up @@ -30,6 +31,19 @@ export class CardWrapper extends ComponentWrapper {
return this.findAllByClassName(styles.section).map(c => new CardSectionWrapper(c.getElement()));
}

/**
* Returns the wrapper of the first card section that matches the specified test ID.
* Looks for the `data-testid` attribute that is assigned via `sections.testId` prop.
* If no matching card section is found, returns `null`.
*
* @param {string} testId
* @returns {CardSectionWrapper | null}
*/
findSectionByTestId(testId: string): CardSectionWrapper | null {
const escapedTestId = escapeSelector(testId);
return this.findComponent(`.${styles.section}[data-testid="${escapedTestId}"]`, CardSectionWrapper);
}

findCardHeader(): ElementWrapper | null {
return this.findByClassName(styles['card-header-inner']);
}
Expand All @@ -48,6 +62,19 @@ export default class CardsWrapper extends ComponentWrapper {
return this.findAllByClassName(styles.card).map(c => new CardWrapper(c.getElement()));
}

/**
* Returns the wrapper of the first card that matches the specified test ID.
* Looks for the `data-testid` attribute that is assigned via `cardDefinition.testId` prop.
* If no matching card is found, returns `null`.
*
* @param {string} testId
* @returns {CardWrapper | null}
*/
findItemByTestId(testId: string): CardWrapper | null {
const escapedTestId = escapeSelector(testId);
return this.findComponent(`.${styles.card}[data-testid="${escapedTestId}"]`, CardWrapper);
}

findSelectedItems(): Array<CardWrapper> {
return this.findAllByClassName(styles['card-selected']).map(c => new CardWrapper(c.getElement()));
}
Expand Down

0 comments on commit da09f0e

Please sign in to comment.