aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/design-system/src/components
diff options
context:
space:
mode:
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>2023-05-30 16:00:31 +0200
committersonartech <sonartech@sonarsource.com>2023-06-09 20:03:09 +0000
commit40e061b25b152d7704a989240c12191317a81f3f (patch)
treed4281cec8e1ce5597d1a5504b62e2a48d1850e36 /server/sonar-web/design-system/src/components
parent319b9ad6352943fcf6265a589d7dba6ce659fa0a (diff)
downloadsonarqube-40e061b25b152d7704a989240c12191317a81f3f.tar.gz
sonarqube-40e061b25b152d7704a989240c12191317a81f3f.zip
SONAR-19345 Issues page - list view: new facets using MIUI elements
Diffstat (limited to 'server/sonar-web/design-system/src/components')
-rw-r--r--server/sonar-web/design-system/src/components/BarChart.tsx2
-rw-r--r--server/sonar-web/design-system/src/components/DatePicker.tsx82
-rw-r--r--server/sonar-web/design-system/src/components/DateRangePicker.tsx2
-rw-r--r--server/sonar-web/design-system/src/components/FacetBox.tsx26
-rw-r--r--server/sonar-web/design-system/src/components/FacetItem.tsx31
-rw-r--r--server/sonar-web/design-system/src/components/FlagMessage.tsx2
-rw-r--r--server/sonar-web/design-system/src/components/InputSearch.tsx4
-rw-r--r--server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx46
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/DatePicker-test.tsx44
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/FacetBox-test.tsx6
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/FacetItem-test.tsx22
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx15
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap76
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap37
-rw-r--r--server/sonar-web/design-system/src/components/icons/TestFileIcon.tsx2
-rw-r--r--server/sonar-web/design-system/src/components/icons/index.ts1
16 files changed, 213 insertions, 185 deletions
diff --git a/server/sonar-web/design-system/src/components/BarChart.tsx b/server/sonar-web/design-system/src/components/BarChart.tsx
index 9b460674e69..91a01174c97 100644
--- a/server/sonar-web/design-system/src/components/BarChart.tsx
+++ b/server/sonar-web/design-system/src/components/BarChart.tsx
@@ -24,7 +24,7 @@ import { themeColor } from '../helpers';
interface DataPoint {
description: string;
- tooltip?: string;
+ tooltip?: string | JSX.Element;
x: number;
y: number;
}
diff --git a/server/sonar-web/design-system/src/components/DatePicker.tsx b/server/sonar-web/design-system/src/components/DatePicker.tsx
index e80795cbad2..0bb34725c2d 100644
--- a/server/sonar-web/design-system/src/components/DatePicker.tsx
+++ b/server/sonar-web/design-system/src/components/DatePicker.tsx
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import styled from '@emotion/styled';
+import styled from '@emotion/styled';
import classNames from 'classnames';
import {
format,
@@ -76,7 +76,7 @@ interface Props {
showClearButton?: boolean;
size?: InputSizeKeys;
value?: Date;
- valueFormatter: (date?: Date) => string;
+ valueFormatter?: (date?: Date) => string;
}
interface State {
@@ -142,6 +142,7 @@ export class DatePicker extends React.PureComponent<Props, State> {
id,
placeholder,
showClearButton = true,
+ valueFormatter = (date?: Date) => (date ? format(date, 'MMM d, yyyy') : ''),
size,
} = this.props;
const { lastHovered, currentMonth, open } = this.state;
@@ -153,10 +154,12 @@ export class DatePicker extends React.PureComponent<Props, State> {
const selectedDays = selectedDay ? [selectedDay] : [];
let highlighted: Matcher = false;
const lastHoveredOrValue = lastHovered ?? selectedDay;
+
if (highlightFrom && lastHoveredOrValue) {
highlighted = { from: highlightFrom, to: lastHoveredOrValue };
selectedDays.push(highlightFrom);
}
+
if (highlightTo && lastHoveredOrValue) {
highlighted = { from: lastHoveredOrValue, to: highlightTo };
selectedDays.push(highlightTo);
@@ -221,11 +224,13 @@ export class DatePicker extends React.PureComponent<Props, State> {
readOnly
ref={inputRef}
size={size}
- title={this.props.valueFormatter(selectedDay)}
+ title={valueFormatter(selectedDay)}
type="text"
- value={this.props.valueFormatter(selectedDay)}
+ value={valueFormatter(selectedDay)}
/>
+
<StyledCalendarIcon fill="datePickerIcon" />
+
{selectedDay !== undefined && showClearButton && (
<StyledInteractiveIcon
Icon={CloseIcon}
@@ -327,16 +332,21 @@ function getCustomCalendarNavigation({
const { goToMonth, nextMonth, previousMonth } = useCalendarNavigation();
const baseDate = startOfMonth(displayMonth); // reference date
+
const months = range(MONTHS_IN_A_YEAR).map((month) => {
const monthValue = setMonth(baseDate, month);
+
return {
label: format(monthValue, 'MMM'),
value: monthValue,
};
});
+
const startYear = fromYear ?? getYear(Date.now()) - YEARS_TO_DISPLAY;
+
const years = range(startYear, toYear ? toYear + 1 : undefined).map((year) => {
const yearValue = setYear(baseDate, year);
+
return {
label: String(year),
value: yearValue,
@@ -349,37 +359,53 @@ function getCustomCalendarNavigation({
Icon={ChevronLeftIcon}
aria-label={ariaPreviousMonthLabel}
className="sw-mr-2"
- onClick={() => previousMonth && goToMonth(previousMonth)}
- size="small"
- />
- <InputSelect
- isClearable={false}
- onChange={(value) => {
- if (value) {
- goToMonth(value.value);
- }
- }}
- options={months}
- size="full"
- value={months.find((m) => isSameMonth(m.value, displayMonth))}
- />
- <InputSelect
- className="sw-ml-1"
- isClearable={false}
- onChange={(value) => {
- if (value) {
- goToMonth(value.value);
+ onClick={() => {
+ if (previousMonth) {
+ goToMonth(previousMonth);
}
}}
- options={years}
- size="full"
- value={years.find((y) => isSameYear(y.value, displayMonth))}
+ size="small"
/>
+
+ <span data-testid="month-select">
+ <InputSelect
+ isClearable={false}
+ onChange={(value) => {
+ if (value) {
+ goToMonth(value.value);
+ }
+ }}
+ options={months}
+ size="full"
+ value={months.find((m) => isSameMonth(m.value, displayMonth))}
+ />
+ </span>
+
+ <span data-testid="year-select">
+ <InputSelect
+ className="sw-ml-1"
+ data-testid="year-select"
+ isClearable={false}
+ onChange={(value) => {
+ if (value) {
+ goToMonth(value.value);
+ }
+ }}
+ options={years}
+ size="full"
+ value={years.find((y) => isSameYear(y.value, displayMonth))}
+ />
+ </span>
+
<InteractiveIcon
Icon={ChevronRightIcon}
aria-label={ariaNextMonthLabel}
className="sw-ml-2"
- onClick={() => nextMonth && goToMonth(nextMonth)}
+ onClick={() => {
+ if (nextMonth) {
+ goToMonth(nextMonth);
+ }
+ }}
size="small"
/>
</nav>
diff --git a/server/sonar-web/design-system/src/components/DateRangePicker.tsx b/server/sonar-web/design-system/src/components/DateRangePicker.tsx
index 132deafcd04..01e73b17baf 100644
--- a/server/sonar-web/design-system/src/components/DateRangePicker.tsx
+++ b/server/sonar-web/design-system/src/components/DateRangePicker.tsx
@@ -41,7 +41,7 @@ interface Props {
separatorText?: string;
toLabel: string;
value?: DateRange;
- valueFormatter: (date?: Date) => string;
+ valueFormatter?: (date?: Date) => string;
}
export class DateRangePicker extends React.PureComponent<Props> {
diff --git a/server/sonar-web/design-system/src/components/FacetBox.tsx b/server/sonar-web/design-system/src/components/FacetBox.tsx
index 2e100268542..0366e6fe69e 100644
--- a/server/sonar-web/design-system/src/components/FacetBox.tsx
+++ b/server/sonar-web/design-system/src/components/FacetBox.tsx
@@ -26,7 +26,7 @@ import tw from 'twin.macro';
import { themeColor } from '../helpers';
import { Badge } from './Badge';
import { DeferredSpinner } from './DeferredSpinner';
-import { InteractiveIcon } from './InteractiveIcon';
+import { DestructiveIcon } from './InteractiveIcon';
import Tooltip from './Tooltip';
import { BareButton } from './buttons';
import { OpenCloseIndicator } from './icons';
@@ -39,7 +39,9 @@ export interface FacetBoxProps {
clearIconLabel?: string;
count?: number;
countLabel?: string;
+ 'data-property'?: string;
disabled?: boolean;
+ hasEmbeddedFacets?: boolean;
id?: string;
inner?: boolean;
loading?: boolean;
@@ -57,7 +59,9 @@ export function FacetBox(props: FacetBoxProps) {
clearIconLabel,
count,
countLabel,
+ 'data-property': dataProperty,
disabled = false,
+ hasEmbeddedFacets = false,
id: idProp,
inner = false,
loading = false,
@@ -73,7 +77,13 @@ export function FacetBox(props: FacetBoxProps) {
const id = React.useMemo(() => idProp ?? uniqueId('filter-facet-'), [idProp]);
return (
- <Accordion className={classNames(className, { open })} inner={inner} role="listitem">
+ <Accordion
+ className={classNames(className, { open })}
+ data-property={dataProperty}
+ hasEmbeddedFacets={hasEmbeddedFacets}
+ inner={inner}
+ role="listitem"
+ >
<Header>
<ChevronAndTitle
aria-controls={`${id}-panel`}
@@ -106,6 +116,7 @@ export function FacetBox(props: FacetBoxProps) {
<ClearIcon
Icon={CloseIcon}
aria-label={clearIconLabel ?? ''}
+ data-testid={`clear-${name}`}
onClick={onClear}
size="small"
/>
@@ -116,7 +127,7 @@ export function FacetBox(props: FacetBoxProps) {
</Header>
{open && (
- <div aria-labelledby={`${id}-header`} id={`${id}-panel`} role="region">
+ <div aria-labelledby={`${id}-header`} id={`${id}-panel`} role="list">
{children}
</div>
)}
@@ -124,14 +135,19 @@ export function FacetBox(props: FacetBoxProps) {
);
}
+FacetBox.displayName = 'FacetBox'; // so that tests don't see the obfuscated production name
+
const Accordion = styled.div<{
+ hasEmbeddedFacets?: boolean;
inner?: boolean;
}>`
${tw`sw-flex-col`};
${tw`sw-flex`};
${tw`sw-gap-3`};
- ${({ inner }) => (inner ? tw`sw-gap-1 sw-ml-3` : '')};
+ ${({ hasEmbeddedFacets }) => (hasEmbeddedFacets ? tw`sw-gap-0` : '')};
+
+ ${({ inner }) => (inner ? tw`sw-gap-1 sw-ml-3 sw-mt-1` : '')};
`;
const BadgeAndIcons = styled.div`
@@ -150,7 +166,7 @@ const ChevronAndTitle = styled(BareButton)<{
cursor: ${({ expandable }) => (expandable ? 'pointer' : 'default')};
`;
-const ClearIcon = styled(InteractiveIcon)`
+const ClearIcon = styled(DestructiveIcon)`
--color: ${themeColor('dangerButton')};
`;
diff --git a/server/sonar-web/design-system/src/components/FacetItem.tsx b/server/sonar-web/design-system/src/components/FacetItem.tsx
index 0d318feec0a..62d5e38b150 100644
--- a/server/sonar-web/design-system/src/components/FacetItem.tsx
+++ b/server/sonar-web/design-system/src/components/FacetItem.tsx
@@ -26,8 +26,9 @@ import { ButtonProps, ButtonSecondary } from './buttons';
export type FacetItemProps = Omit<ButtonProps, 'name' | 'onClick'> & {
active?: boolean;
- name: string;
+ name: string | React.ReactNode;
onClick: (x: string, multiple?: boolean) => void;
+ small?: boolean;
stat?: React.ReactNode;
/** Textual version of `name` */
tooltip?: string;
@@ -41,11 +42,12 @@ export function FacetItem({
icon,
name,
onClick,
+ small,
stat,
tooltip,
value,
}: FacetItemProps) {
- const disabled = disabledProp || (stat as number) === 0;
+ const disabled = disabledProp || (stat !== undefined && stat === 0);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
@@ -56,12 +58,15 @@ export function FacetItem({
return (
<StyledButton
active={active}
+ aria-checked={active}
+ aria-label={typeof name === 'string' ? name : undefined}
className={className}
data-facet={value}
disabled={disabled}
icon={icon}
onClick={handleClick}
- role="listitem"
+ role="checkbox"
+ small={small}
title={tooltip}
>
<span className="container">
@@ -72,10 +77,17 @@ export function FacetItem({
);
}
-const StyledButton = styled(ButtonSecondary)<{ active?: boolean }>`
+FacetItem.displayName = 'FacetItem'; // so that tests don't see the obfuscated production name
+
+const StyledButton = styled(ButtonSecondary)<{ active?: boolean; small?: boolean }>`
${tw`sw-body-sm`};
- ${tw`sw-p-1`};
+ ${tw`sw-box-border`};
+ ${tw`sw-h-7`};
+ ${tw`sw-px-1`};
${tw`sw-rounded-1`};
+ ${tw`sw-w-full`};
+
+ ${({ small }) => (small ? tw`sw-body-xs sw-pr-0` : '')};
--background: ${({ active }) => (active ? themeColor('facetItemSelected') : 'transparent')};
--backgroundHover: ${({ active }) => (active ? themeColor('facetItemSelected') : 'transparent')};
@@ -95,6 +107,15 @@ const StyledButton = styled(ButtonSecondary)<{ active?: boolean }>`
${tw`sw-items-center`};
${tw`sw-justify-between`};
+ & span.name {
+ ${tw`sw-pr-1`};
+ ${tw`sw-truncate`};
+
+ & mark {
+ background-color: ${themeColor('searchHighlight')};
+ }
+ }
+
& span.stat {
color: ${themeColor('facetItemLight')};
}
diff --git a/server/sonar-web/design-system/src/components/FlagMessage.tsx b/server/sonar-web/design-system/src/components/FlagMessage.tsx
index 3a3aed03c94..72ef3a9808f 100644
--- a/server/sonar-web/design-system/src/components/FlagMessage.tsx
+++ b/server/sonar-web/design-system/src/components/FlagMessage.tsx
@@ -90,6 +90,8 @@ export function FlagMessage(props: Props & React.HTMLAttributes<HTMLDivElement>)
);
}
+FlagMessage.displayName = 'FlagMessage'; // so that tests don't see the obfuscated production name
+
export const StyledFlag = styled.div<{
variantInfo: VariantInformation;
}>`
diff --git a/server/sonar-web/design-system/src/components/InputSearch.tsx b/server/sonar-web/design-system/src/components/InputSearch.tsx
index 3c0233ef392..354fab3b5d2 100644
--- a/server/sonar-web/design-system/src/components/InputSearch.tsx
+++ b/server/sonar-web/design-system/src/components/InputSearch.tsx
@@ -164,7 +164,7 @@ export function InputSearch({
<StyledInteractiveIcon
Icon={CloseIcon}
aria-label={clearIconAriaLabel}
- className="js-input-search-clear"
+ className="it__search-box-clear"
onClick={handleClearClick}
size="small"
/>
@@ -180,6 +180,8 @@ export function InputSearch({
);
}
+InputSearch.displayName = 'InputSearch'; // so that tests don't see the obfuscated production name
+
export const InputSearchWrapper = styled.div`
width: var(--inputSize);
diff --git a/server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx b/server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx
index 7aa37f175d3..230a5dc7231 100644
--- a/server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx
+++ b/server/sonar-web/design-system/src/components/KeyboardHintKeys.tsx
@@ -17,17 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import styled from '@emotion/styled';
import tw from 'twin.macro';
import { themeColor, themeContrast } from '../helpers';
import { Key } from '../helpers/keyboard';
import { TriangleDownIcon, TriangleLeftIcon, TriangleRightIcon, TriangleUpIcon } from './icons';
-const COMMAND = '⌘';
-const CTRL = 'Ctrl';
-const OPTION = '⌥';
-const ALT = 'Alt';
-const NON_KEY_SYMBOLS = ['+', ' '];
+export const mappedKeys = {
+ [Key.Alt]: 'Alt',
+ [Key.ArrowDown]: <TriangleDownIcon />,
+ [Key.ArrowLeft]: <TriangleLeftIcon />,
+ [Key.ArrowRight]: <TriangleRightIcon />,
+ [Key.ArrowUp]: <TriangleUpIcon />,
+ [Key.Command]: '⌘',
+ [Key.Control]: 'Ctrl',
+ [Key.Option]: '⌥',
+};
export function KeyboardHintKeys({ command }: { command: string }) {
const keys = command
@@ -35,11 +41,12 @@ export function KeyboardHintKeys({ command }: { command: string }) {
.split(' ')
.map((key, index) => {
const uniqueKey = `${key}-${index}`;
- if (NON_KEY_SYMBOLS.includes(key)) {
+
+ if (!(Object.keys(mappedKeys).includes(key) || Object.values(mappedKeys).includes(key))) {
return <span key={uniqueKey}>{key}</span>;
}
- return <KeyBox key={uniqueKey}>{getKey(key)}</KeyBox>;
+ return <KeyBox key={uniqueKey}>{mappedKeys[key as keyof typeof mappedKeys] || key}</KeyBox>;
});
return <div className="sw-flex sw-gap-1">{keys}</div>;
@@ -50,29 +57,6 @@ export const KeyBox = styled.span`
${tw`sw-px-1/2`}
${tw`sw-rounded-1/2`}
- color: ${themeContrast('keyboardHintKey')};
background-color: ${themeColor('keyboardHintKey')};
+ color: ${themeContrast('keyboardHintKey')};
`;
-
-function getKey(key: string) {
- switch (key) {
- case Key.Control:
- return CTRL;
- case Key.Command:
- return COMMAND;
- case Key.Alt:
- return ALT;
- case Key.Option:
- return OPTION;
- case Key.ArrowUp:
- return <TriangleUpIcon />;
- case Key.ArrowDown:
- return <TriangleDownIcon />;
- case Key.ArrowLeft:
- return <TriangleLeftIcon />;
- case Key.ArrowRight:
- return <TriangleRightIcon />;
- default:
- return key;
- }
-}
diff --git a/server/sonar-web/design-system/src/components/__tests__/DatePicker-test.tsx b/server/sonar-web/design-system/src/components/__tests__/DatePicker-test.tsx
index 8edfa140e2a..01aa68be4c7 100644
--- a/server/sonar-web/design-system/src/components/__tests__/DatePicker-test.tsx
+++ b/server/sonar-web/design-system/src/components/__tests__/DatePicker-test.tsx
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getMonth, getYear, parseISO } from 'date-fns';
@@ -85,19 +86,46 @@ it('behaves correctly', async () => {
expect(getYear(newDate3)).toBe(2019);
});
-it('highlights the appropriate days', async () => {
+it('should clear the value', async () => {
const user = userEvent.setup();
- const value = parseISO('2022-06-14');
- renderDatePicker({ highlightFrom: parseISO('2022-06-12'), showClearButton: true, value });
+ const onChange = jest.fn((_: Date) => undefined);
+
+ const currentDate = parseISO('2022-06-13');
+
+ renderDatePicker({
+ currentMonth: currentDate,
+ onChange,
+ showClearButton: true,
+ value: currentDate,
+ // eslint-disable-next-line jest/no-conditional-in-test
+ valueFormatter: (date?: Date) => (date ? 'formatted date' : 'no date'),
+ });
+
+ await user.click(screen.getByRole('textbox'));
+
+ await user.click(screen.getByLabelText('clear'));
+
+ expect(onChange).toHaveBeenCalledWith(undefined);
+});
+
+it.each([
+ [{ highlightFrom: parseISO('2022-06-12'), value: parseISO('2022-06-14') }],
+ [{ alignRight: true, highlightTo: parseISO('2022-06-14'), value: parseISO('2022-06-12') }],
+])('highlights the appropriate days', async (props) => {
+ const user = userEvent.setup();
+
+ const hightlightClass = 'rdp-highlighted';
+
+ renderDatePicker(props);
await user.click(screen.getByRole('textbox'));
- expect(screen.getByText('11')).not.toHaveClass('rdp-highlighted');
- expect(screen.getByText('12')).toHaveClass('rdp-highlighted');
- expect(screen.getByText('13')).toHaveClass('rdp-highlighted');
- expect(screen.getByText('14')).toHaveClass('rdp-highlighted');
- expect(screen.getByText('15')).not.toHaveClass('rdp-highlighted');
+ expect(screen.getByText('11')).not.toHaveClass(hightlightClass);
+ expect(screen.getByText('12')).toHaveClass(hightlightClass);
+ expect(screen.getByText('13')).toHaveClass(hightlightClass);
+ expect(screen.getByText('14')).toHaveClass(hightlightClass);
+ expect(screen.getByText('15')).not.toHaveClass(hightlightClass);
});
function renderDatePicker(overrides: Partial<DatePicker['props']> = {}) {
diff --git a/server/sonar-web/design-system/src/components/__tests__/FacetBox-test.tsx b/server/sonar-web/design-system/src/components/__tests__/FacetBox-test.tsx
index 88dd9499885..434a6147e5c 100644
--- a/server/sonar-web/design-system/src/components/__tests__/FacetBox-test.tsx
+++ b/server/sonar-web/design-system/src/components/__tests__/FacetBox-test.tsx
@@ -28,11 +28,11 @@ it('should render an empty disabled facet box', async () => {
const onClick = jest.fn();
- renderComponent({ disabled: true, onClick });
+ renderComponent({ disabled: true, hasEmbeddedFacets: true, onClick });
expect(screen.getByRole('listitem')).toBeInTheDocument();
- expect(screen.queryByRole('region')).not.toBeInTheDocument();
+ expect(screen.queryByRole('list')).not.toBeInTheDocument();
expect(screen.getByText('Test FacetBox')).toBeInTheDocument();
@@ -58,7 +58,7 @@ it('should render an inner expanded facet box with count', async () => {
open: true,
});
- expect(screen.getByRole('region')).toBeInTheDocument();
+ expect(screen.getByRole('list')).toBeInTheDocument();
expect(screen.getByRole('button', { expanded: true })).toBeInTheDocument();
diff --git a/server/sonar-web/design-system/src/components/__tests__/FacetItem-test.tsx b/server/sonar-web/design-system/src/components/__tests__/FacetItem-test.tsx
index 12703d37f00..a916181172a 100644
--- a/server/sonar-web/design-system/src/components/__tests__/FacetItem-test.tsx
+++ b/server/sonar-web/design-system/src/components/__tests__/FacetItem-test.tsx
@@ -30,9 +30,9 @@ it('should render a disabled facet item', async () => {
renderComponent({ disabled: true, onClick });
- expect(screen.getByRole('listitem')).toHaveAttribute('aria-disabled', 'true');
+ expect(screen.getByRole('checkbox')).toHaveAttribute('aria-disabled', 'true');
- await user.click(screen.getByRole('listitem'));
+ await user.click(screen.getByRole('checkbox'));
expect(onClick).not.toHaveBeenCalled();
});
@@ -44,18 +44,30 @@ it('should render a non-disabled facet item', async () => {
renderComponent({ active: true, onClick, stat: 3, value: 'foo' });
- expect(screen.getByRole('listitem')).toHaveAttribute('aria-disabled', 'false');
+ expect(screen.getByRole('checkbox')).toHaveAttribute('aria-disabled', 'false');
- await user.click(screen.getByRole('listitem'));
+ await user.click(screen.getByRole('checkbox'));
expect(onClick).toHaveBeenCalledWith('foo', false);
await user.keyboard('{Meta>}');
- await user.click(screen.getByRole('listitem'));
+ await user.click(screen.getByRole('checkbox'));
expect(onClick).toHaveBeenLastCalledWith('foo', true);
});
+it('should add an aria label if the name is a string', () => {
+ renderComponent({ name: 'Foo' });
+
+ expect(screen.getByRole('checkbox')).toHaveAccessibleName('Foo');
+});
+
+it('should not add an aria label if the name is not a string', () => {
+ renderComponent({ name: <div>Foo</div>, small: true });
+
+ expect(screen.getByRole('checkbox')).not.toHaveAttribute('aria-label');
+});
+
function renderComponent(props: Partial<FacetItemProps> = {}) {
return render(<FacetItem name="Test facet item" onClick={jest.fn()} value="Value" {...props} />);
}
diff --git a/server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx b/server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx
index 4d1ff44cede..b26ee4e2801 100644
--- a/server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx
+++ b/server/sonar-web/design-system/src/components/__tests__/KeyboardHintKeys-test.tsx
@@ -21,24 +21,15 @@
import { Key } from '../../helpers/keyboard';
import { render } from '../../helpers/testUtils';
import { FCProps } from '../../types/misc';
-import { KeyboardHintKeys } from '../KeyboardHintKeys';
+import { KeyboardHintKeys, mappedKeys } from '../KeyboardHintKeys';
-it.each([
- Key.Control,
- Key.Command,
- Key.Alt,
- Key.Option,
- Key.ArrowUp,
- Key.ArrowDown,
- Key.ArrowLeft,
- Key.ArrowRight,
-])('should render %s', (key) => {
+it.each(Object.keys(mappedKeys))('should render %s', (key) => {
const { container } = setupWithProps({ command: key });
expect(container).toMatchSnapshot();
});
it('should render multiple keys', () => {
- const { container } = setupWithProps({ command: `${Key.ArrowUp} ${Key.ArrowDown}` });
+ const { container } = setupWithProps({ command: `Use Ctrl + ${Key.ArrowUp} ${Key.ArrowDown}` });
expect(container).toMatchSnapshot();
});
diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap
index 081af387a40..6ac0d945eef 100644
--- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap
+++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHint-test.tsx.snap
@@ -34,8 +34,8 @@ exports[`renders on mac 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -94,8 +94,8 @@ exports[`renders on windows 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -138,26 +138,6 @@ exports[`renders with command 1`] = `
color: rgb(106,117,144);
}
-.emotion-2 {
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-align-items: center;
- -webkit-box-align: center;
- -ms-flex-align: center;
- align-items: center;
- -webkit-box-pack: center;
- -ms-flex-pack: center;
- -webkit-justify-content: center;
- justify-content: center;
- padding-left: 0.125rem;
- padding-right: 0.125rem;
- border-radius: 0.125rem;
- color: rgb(62,67,87);
- background-color: rgb(225,230,243);
-}
-
<div>
<div
class="emotion-0 emotion-1"
@@ -165,9 +145,7 @@ exports[`renders with command 1`] = `
<div
class="sw-flex sw-gap-1"
>
- <span
- class="emotion-2 emotion-3"
- >
+ <span>
command
</span>
</div>
@@ -193,26 +171,6 @@ exports[`renders with title 1`] = `
color: rgb(106,117,144);
}
-.emotion-2 {
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-align-items: center;
- -webkit-box-align: center;
- -ms-flex-align: center;
- align-items: center;
- -webkit-box-pack: center;
- -ms-flex-pack: center;
- -webkit-justify-content: center;
- justify-content: center;
- padding-left: 0.125rem;
- padding-right: 0.125rem;
- border-radius: 0.125rem;
- color: rgb(62,67,87);
- background-color: rgb(225,230,243);
-}
-
<div>
<div
class="emotion-0 emotion-1"
@@ -225,9 +183,7 @@ exports[`renders with title 1`] = `
<div
class="sw-flex sw-gap-1"
>
- <span
- class="emotion-2 emotion-3"
- >
+ <span>
click
</span>
</div>
@@ -253,26 +209,6 @@ exports[`renders without title 1`] = `
color: rgb(106,117,144);
}
-.emotion-2 {
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-align-items: center;
- -webkit-box-align: center;
- -ms-flex-align: center;
- align-items: center;
- -webkit-box-pack: center;
- -ms-flex-pack: center;
- -webkit-justify-content: center;
- justify-content: center;
- padding-left: 0.125rem;
- padding-right: 0.125rem;
- border-radius: 0.125rem;
- color: rgb(62,67,87);
- background-color: rgb(225,230,243);
-}
-
<div>
<div
class="emotion-0 emotion-1"
@@ -280,9 +216,7 @@ exports[`renders without title 1`] = `
<div
class="sw-flex sw-gap-1"
>
- <span
- class="emotion-2 emotion-3"
- >
+ <span>
click
</span>
</div>
diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap
index 907e3994ef0..be6c83072f5 100644
--- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap
+++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/KeyboardHintKeys-test.tsx.snap
@@ -17,8 +17,8 @@ exports[`should render Alt 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -51,8 +51,8 @@ exports[`should render ArrowDown 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -99,8 +99,8 @@ exports[`should render ArrowLeft 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -147,8 +147,8 @@ exports[`should render ArrowRight 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -195,8 +195,8 @@ exports[`should render ArrowUp 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -243,8 +243,8 @@ exports[`should render Command 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -277,8 +277,8 @@ exports[`should render Control 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -311,8 +311,8 @@ exports[`should render Option 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -345,8 +345,8 @@ exports[`should render a default text if no keys match 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
@@ -361,9 +361,7 @@ exports[`should render a default text if no keys match 1`] = `
<span>
+
</span>
- <span
- class="emotion-0 emotion-1"
- >
+ <span>
click
</span>
</div>
@@ -387,14 +385,25 @@ exports[`should render multiple keys 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
<div
class="sw-flex sw-gap-1"
>
+ <span>
+ Use
+ </span>
+ <span
+ class="emotion-0 emotion-1"
+ >
+ Ctrl
+ </span>
+ <span>
+ +
+ </span>
<span
class="emotion-0 emotion-1"
>
@@ -454,8 +463,8 @@ exports[`should render multiple keys with non-key symbols 1`] = `
padding-left: 0.125rem;
padding-right: 0.125rem;
border-radius: 0.125rem;
- color: rgb(62,67,87);
background-color: rgb(225,230,243);
+ color: rgb(62,67,87);
}
<div>
diff --git a/server/sonar-web/design-system/src/components/icons/TestFileIcon.tsx b/server/sonar-web/design-system/src/components/icons/TestFileIcon.tsx
index fae7278a2f6..9ab29f18b37 100644
--- a/server/sonar-web/design-system/src/components/icons/TestFileIcon.tsx
+++ b/server/sonar-web/design-system/src/components/icons/TestFileIcon.tsx
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { useTheme } from '@emotion/react';
import { themeColor } from '../../helpers/theme';
import { CustomIcon, IconProps } from './Icon';
@@ -24,6 +25,7 @@ import { CustomIcon, IconProps } from './Icon';
export function TestFileIcon({ fill = 'currentColor', ...iconProps }: IconProps) {
const theme = useTheme();
const fillColor = themeColor(fill)({ theme });
+
return (
<CustomIcon {...iconProps}>
<path
diff --git a/server/sonar-web/design-system/src/components/icons/index.ts b/server/sonar-web/design-system/src/components/icons/index.ts
index d81089a367a..898e9a46890 100644
--- a/server/sonar-web/design-system/src/components/icons/index.ts
+++ b/server/sonar-web/design-system/src/components/icons/index.ts
@@ -70,6 +70,7 @@ export { StatusConfirmedIcon } from './StatusConfirmedIcon';
export { StatusOpenIcon } from './StatusOpenIcon';
export { StatusReopenedIcon } from './StatusReopenedIcon';
export { StatusResolvedIcon } from './StatusResolvedIcon';
+export { TestFileIcon } from './TestFileIcon';
export { TrashIcon } from './TrashIcon';
export { TriangleDownIcon } from './TriangleDownIcon';
export { TriangleLeftIcon } from './TriangleLeftIcon';