@lumx/react 3.3.1-alpha.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/_internal/ClickAwayProvider.js.map +1 -1
  2. package/_internal/types.d.ts +0 -2
  3. package/index.d.ts +2 -0
  4. package/index.js +347 -75
  5. package/index.js.map +1 -1
  6. package/package.json +23 -19
  7. package/src/components/autocomplete/Autocomplete.test.tsx +55 -142
  8. package/src/components/autocomplete/AutocompleteMultiple.test.tsx +37 -75
  9. package/src/components/autocomplete/__mockData__/index.ts +6 -1
  10. package/src/components/badge/Badge.test.tsx +20 -64
  11. package/src/components/button/Button.test.tsx +44 -121
  12. package/src/components/button/ButtonGroup.test.tsx +16 -39
  13. package/src/components/button/IconButton.stories.tsx +7 -0
  14. package/src/components/button/IconButton.test.tsx +37 -78
  15. package/src/components/button/IconButton.tsx +8 -1
  16. package/src/components/checkbox/Checkbox.test.tsx +62 -67
  17. package/src/components/chip/Chip.test.tsx +89 -139
  18. package/src/components/chip/ChipGroup.test.tsx +27 -10
  19. package/src/components/date-picker/DatePicker.test.tsx +15 -23
  20. package/src/components/date-picker/DatePickerControlled.test.tsx +24 -20
  21. package/src/components/date-picker/DatePickerField.test.tsx +43 -27
  22. package/src/components/dialog/Dialog.test.tsx +36 -35
  23. package/src/components/divider/Divider.test.tsx +23 -69
  24. package/src/components/dropdown/Dropdown.test.tsx +30 -61
  25. package/src/components/expansion-panel/ExpansionPanel.test.tsx +12 -8
  26. package/src/components/flag/Flag.test.tsx +28 -53
  27. package/src/components/generic-block/GenericBlock.test.tsx +93 -89
  28. package/src/components/grid-column/GridColumn.stories.tsx +3 -3
  29. package/src/components/icon/Icon.test.tsx +80 -64
  30. package/src/components/index.ts +0 -2
  31. package/src/components/inline-list/InlineList.test.tsx +30 -17
  32. package/src/components/input-helper/InputHelper.test.tsx +21 -81
  33. package/src/components/input-label/InputLabel.test.tsx +19 -61
  34. package/src/components/lightbox/Lightbox.test.tsx +3 -2
  35. package/src/components/link/Link.test.tsx +47 -31
  36. package/src/components/link-preview/LinkPreview.test.tsx +51 -51
  37. package/src/components/message/Message.test.tsx +31 -52
  38. package/src/components/mosaic/Mosaic.test.tsx +56 -72
  39. package/src/components/notification/Notification.test.tsx +51 -82
  40. package/src/components/popover/Popover.tsx +7 -9
  41. package/src/components/progress-tracker/ProgressTracker.test.tsx +20 -33
  42. package/src/components/progress-tracker/ProgressTrackerProvider.test.tsx +61 -36
  43. package/src/components/progress-tracker/ProgressTrackerStep.test.tsx +19 -109
  44. package/src/components/progress-tracker/ProgressTrackerStepPanel.test.tsx +21 -58
  45. package/src/components/progress-tracker/ProgressTrackerStepPanel.tsx +1 -1
  46. package/src/components/radio-button/RadioButton.test.tsx +78 -92
  47. package/src/components/radio-button/RadioGroup.test.tsx +13 -59
  48. package/src/components/select/Select.test.tsx +115 -284
  49. package/src/components/select/SelectMultiple.stories.tsx +105 -2
  50. package/src/components/select/SelectMultiple.test.tsx +126 -322
  51. package/src/components/select/WithSelectContext.tsx +10 -4
  52. package/src/components/side-navigation/SideNavigation.test.tsx +22 -35
  53. package/src/components/side-navigation/SideNavigationItem.test.tsx +72 -139
  54. package/src/components/switch/Switch.test.tsx +70 -149
  55. package/src/components/table/Table.test.tsx +2 -0
  56. package/src/components/table/TableBody.test.tsx +18 -8
  57. package/src/components/table/TableCell.test.tsx +34 -9
  58. package/src/components/table/TableHeader.test.tsx +18 -8
  59. package/src/components/table/TableRow.test.tsx +28 -8
  60. package/src/components/tabs/Tab.test.tsx +27 -96
  61. package/src/components/tabs/TabList.test.tsx +21 -56
  62. package/src/components/tabs/TabPanel.test.tsx +20 -55
  63. package/src/components/tabs/TabPanel.tsx +1 -1
  64. package/src/components/tabs/TabProvider.test.tsx +158 -37
  65. package/src/components/tabs/test-utils.ts +39 -0
  66. package/src/components/text-field/TextField.stories.tsx +14 -5
  67. package/src/components/text-field/TextField.test.tsx +54 -8
  68. package/src/components/text-field/TextField.tsx +49 -5
  69. package/src/components/tooltip/Tooltip.test.tsx +134 -75
  70. package/src/components/tooltip/useInjectTooltipRef.tsx +9 -2
  71. package/src/components/uploader/Uploader.test.tsx +60 -48
  72. package/src/components/user-block/UserBlock.test.tsx +69 -13
  73. package/src/hooks/useFocusTrap.ts +2 -2
  74. package/src/testing/utils/commonTestsSuiteRTL.ts +18 -8
  75. package/src/testing/utils/index.ts +0 -1
  76. package/src/utils/flattenChildren.ts +5 -0
  77. package/src/components/autocomplete/__snapshots__/Autocomplete.test.tsx.snap +0 -213
  78. package/src/components/autocomplete/__snapshots__/AutocompleteMultiple.test.tsx.snap +0 -88
  79. package/src/components/badge/__snapshots__/Badge.test.tsx.snap +0 -11
  80. package/src/components/button/ButtonRoot.test.tsx +0 -203
  81. package/src/components/button/__snapshots__/Button.test.tsx.snap +0 -96
  82. package/src/components/button/__snapshots__/ButtonGroup.test.tsx.snap +0 -22
  83. package/src/components/button/__snapshots__/ButtonRoot.test.tsx.snap +0 -160
  84. package/src/components/button/__snapshots__/IconButton.test.tsx.snap +0 -83
  85. package/src/components/checkbox/__snapshots__/Checkbox.test.tsx.snap +0 -141
  86. package/src/components/chip/__snapshots__/Chip.test.tsx.snap +0 -12
  87. package/src/components/chip/__snapshots__/ChipGroup.test.tsx.snap +0 -29
  88. package/src/components/date-picker/__snapshots__/DatePicker.test.tsx.snap +0 -22
  89. package/src/components/date-picker/__snapshots__/DatePickerControlled.test.tsx.snap +0 -597
  90. package/src/components/date-picker/__snapshots__/DatePickerField.test.tsx.snap +0 -43
  91. package/src/components/divider/__snapshots__/Divider.test.tsx.snap +0 -9
  92. package/src/components/dropdown/__snapshots__/Dropdown.test.tsx.snap +0 -35
  93. package/src/components/icon/__snapshots__/Icon.test.tsx.snap +0 -49
  94. package/src/components/input-helper/__snapshots__/InputHelper.test.tsx.snap +0 -9
  95. package/src/components/input-label/__snapshots__/InputLabel.test.tsx.snap +0 -10
  96. package/src/components/link/__snapshots__/Link.test.tsx.snap +0 -29
  97. package/src/components/message/__snapshots__/Message.test.tsx.snap +0 -15
  98. package/src/components/notification/__snapshots__/Notification.test.tsx.snap +0 -34
  99. package/src/components/progress-tracker/__snapshots__/ProgressTracker.test.tsx.snap +0 -41
  100. package/src/components/progress-tracker/__snapshots__/ProgressTrackerStep.test.tsx.snap +0 -141
  101. package/src/components/progress-tracker/__snapshots__/ProgressTrackerStepPanel.test.tsx.snap +0 -25
  102. package/src/components/radio-button/__snapshots__/RadioButton.test.tsx.snap +0 -113
  103. package/src/components/radio-button/__snapshots__/RadioGroup.test.tsx.snap +0 -26
  104. package/src/components/select/__snapshots__/Select.test.tsx.snap +0 -43
  105. package/src/components/select/__snapshots__/SelectMultiple.test.tsx.snap +0 -87
  106. package/src/components/side-navigation/__snapshots__/SideNavigation.test.tsx.snap +0 -7
  107. package/src/components/side-navigation/__snapshots__/SideNavigationItem.test.tsx.snap +0 -30
  108. package/src/components/switch/__snapshots__/Switch.test.tsx.snap +0 -179
  109. package/src/components/tabs/__snapshots__/Tab.test.tsx.snap +0 -62
  110. package/src/components/tabs/__snapshots__/TabList.test.tsx.snap +0 -22
  111. package/src/components/tabs/__snapshots__/TabPanel.test.tsx.snap +0 -25
  112. package/src/components/tabs/test.mocks.ts +0 -33
  113. package/src/components/text-field/__snapshots__/TextField.test.tsx.snap +0 -42
  114. package/src/components/tooltip/__snapshots__/Tooltip.test.tsx.snap +0 -233
  115. package/src/components/uploader/__snapshots__/Uploader.test.tsx.snap +0 -14
  116. package/src/testing/utils/commonTestsSuite.ts +0 -71
  117. package/src/utils/flattenChildren.test.tsx +0 -58
@@ -1,8 +1,9 @@
1
1
  import React from 'react';
2
- import { mount, shallow } from 'enzyme';
3
- import 'jest-enzyme';
4
- import { commonTestsSuite } from '@lumx/react/testing/utils';
5
2
 
3
+ import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
4
+
5
+ import { render, screen } from '@testing-library/react';
6
+ import { getByClassName } from '@lumx/react/testing/utils/queries';
6
7
  import { TableRow, TableRowProps } from './TableRow';
7
8
 
8
9
  const CLASSNAME = TableRow.className as string;
@@ -10,13 +11,32 @@ const CLASSNAME = TableRow.className as string;
10
11
  /**
11
12
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
12
13
  */
13
- const setup = (props: Partial<TableRowProps> = {}, shallowRendering = true) => {
14
- const renderer: any = shallowRendering ? shallow : mount;
15
- const wrapper: any = renderer(<TableRow {...(props as any)} />);
16
- return { props, wrapper };
14
+ const setup = (props: Partial<TableRowProps> = {}) => {
15
+ render(
16
+ <table>
17
+ <tbody>
18
+ <TableRow {...(props as any)} />
19
+ </tbody>
20
+ </table>,
21
+ );
22
+ const tableRow = getByClassName(document.body, CLASSNAME);
23
+ return { props, tableRow };
17
24
  };
18
25
 
19
26
  describe(`<${TableRow.displayName}>`, () => {
27
+ it('should render default', () => {
28
+ const content = 'Content';
29
+ const { tableRow } = setup({ children: <td>{content}</td> });
30
+ expect(tableRow).toBe(screen.queryByRole('row', { name: content }));
31
+ expect(tableRow).toHaveAttribute('tabindex', '-1');
32
+ expect(tableRow).not.toHaveAttribute('aria-disabled');
33
+ });
34
+
20
35
  // Common tests suite.
21
- commonTestsSuite(setup, { className: 'wrapper', prop: 'wrapper' }, { className: CLASSNAME });
36
+ commonTestsSuiteRTL(setup, {
37
+ baseClassName: CLASSNAME,
38
+ forwardClassName: 'tableRow',
39
+ forwardAttributes: 'tableRow',
40
+ forwardRef: 'tableRow',
41
+ });
22
42
  });
@@ -1,116 +1,47 @@
1
- import { mdiCheck } from '@lumx/icons';
2
- import { commonTestsSuite, Wrapper } from '@lumx/react/testing/utils';
3
- import { getBasicClass } from '@lumx/react/utils/className';
1
+ import React from 'react';
4
2
 
5
- import { mount, shallow } from 'enzyme';
6
- import 'jest-enzyme';
7
- import React, { ReactElement } from 'react';
3
+ import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
4
+ import { render, screen } from '@testing-library/react';
5
+ import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
8
6
 
7
+ import { Icon } from '@lumx/react';
8
+ import { mdiPlay } from '@lumx/icons';
9
9
  import { Tab, TabProps } from './Tab';
10
- import { setupTabProviderMocks } from './test.mocks';
11
10
 
12
11
  const CLASSNAME = Tab.className as string;
13
12
 
14
- // Mock useTabProviderContext.
15
- jest.mock('./state', () => {
16
- const state = jest.requireActual('./state');
17
- return { ...state, useTabProviderContext: jest.fn(), useTabProviderContextState: jest.fn() };
18
- });
19
-
20
13
  type SetupProps = Partial<TabProps>;
21
14
 
22
15
  /**
23
16
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
24
17
  */
25
- const setup = ({ ...propsOverride }: SetupProps = {}, shallowRendering = true) => {
26
- const props: TabProps = { label: 'Test Tab Label', ...propsOverride };
27
- const renderer: (el: ReactElement) => Wrapper = shallowRendering ? shallow : mount;
28
- const wrapper: Wrapper = renderer(<Tab {...props} />);
29
-
30
- return { props, wrapper };
18
+ const setup = (propsOverride: SetupProps = {}) => {
19
+ const props = { label: 'Label', ...propsOverride };
20
+ render(<Tab {...props} />);
21
+ const tab = getByClassName(document.body, CLASSNAME);
22
+ const icon = queryByClassName(tab, Icon.className as string);
23
+ return { props, tab, icon };
31
24
  };
32
25
 
33
26
  describe(`<${Tab.displayName}>`, () => {
34
- // 1. Test render via snapshot (default states of component).
35
- describe('Snapshots and structure', () => {
36
- it('should render', () => {
37
- setupTabProviderMocks({ index: 1 });
38
- const { wrapper } = setup({});
39
- expect(wrapper).toMatchSnapshot();
40
- expect(wrapper).toHaveClassName(CLASSNAME);
41
- });
42
-
43
- it('should render icon', () => {
44
- setupTabProviderMocks({ index: 2 });
45
- const { wrapper } = setup({ icon: mdiCheck });
46
- expect(wrapper).toMatchSnapshot();
47
- });
48
-
49
- it('should render active state', () => {
50
- setupTabProviderMocks({ index: 3, isActive: true });
51
- const { wrapper } = setup({});
52
- expect(wrapper).toMatchSnapshot();
53
- });
27
+ it('should render default', () => {
28
+ const label = 'Label';
29
+ const { tab, icon } = setup({ label });
30
+ expect(tab).toBe(screen.queryByRole('tab', { name: label }));
31
+ expect(tab.tagName).toBe('BUTTON');
32
+ expect(tab).toHaveAttribute('type', 'button');
33
+ expect(icon).not.toBeInTheDocument();
54
34
  });
55
35
 
56
- // 2. Test defaultProps value and important props custom values.
57
- describe('Props', () => {
58
- it('should use the given props to add classes', () => {
59
- setupTabProviderMocks();
60
- const props: any = {
61
- isActive: true,
62
- isDisabled: true,
63
- };
64
- const { wrapper } = setup(props);
65
-
66
- Object.keys(props).forEach((prop) => {
67
- expect(wrapper).toHaveClassName(getBasicClass({ prefix: CLASSNAME, type: prop, value: props[prop] }));
68
- });
69
- });
36
+ it('should render icon', () => {
37
+ const { icon } = setup({ icon: mdiPlay });
38
+ expect(icon).toBeInTheDocument();
70
39
  });
71
40
 
72
- // 3. Test events.
73
- describe('Events', () => {
74
- it('should trigger `onChange` when focused if the provider is configured to do so', () => {
75
- const { changeToTab } = setupTabProviderMocks({ shouldActivateOnFocus: true });
76
- const { wrapper } = setup({}, false);
77
-
78
- wrapper.simulate('focus');
79
- expect(changeToTab).toHaveBeenCalledTimes(1);
80
- });
81
-
82
- it('should trigger `onChange` when clicked', () => {
83
- const { changeToTab } = setupTabProviderMocks();
84
- const { wrapper } = setup({}, false);
85
-
86
- wrapper.simulate('click');
87
- expect(changeToTab).toHaveBeenCalledTimes(1);
88
- });
89
-
90
- it('should trigger `onChange` when pressing `enter`', () => {
91
- const { changeToTab } = setupTabProviderMocks();
92
- const { wrapper } = setup({}, false);
93
-
94
- wrapper.simulate('keypress', { key: 'Enter' });
95
- expect(changeToTab).toHaveBeenCalledTimes(1);
96
- });
97
-
98
- it('should not trigger `onChange` when pressing any other key', () => {
99
- const { changeToTab } = setupTabProviderMocks();
100
- const { wrapper } = setup({}, false);
101
-
102
- wrapper.simulate('keypress', { key: 'a' });
103
- expect(changeToTab).not.toHaveBeenCalled();
104
- });
41
+ commonTestsSuiteRTL(setup, {
42
+ baseClassName: CLASSNAME,
43
+ forwardClassName: 'tab',
44
+ forwardAttributes: 'tab',
45
+ forwardRef: 'tab',
105
46
  });
106
-
107
- // Common tests suite.
108
- commonTestsSuite(
109
- (...args: any[]) => {
110
- setupTabProviderMocks();
111
- return setup(...args);
112
- },
113
- { className: 'wrapper', prop: 'wrapper' },
114
- { className: CLASSNAME },
115
- );
116
47
  });
@@ -1,80 +1,45 @@
1
- import { Tab, Alignment } from '@lumx/react';
1
+ import React from 'react';
2
2
 
3
- import { Wrapper, commonTestsSuite } from '@lumx/react/testing/utils';
4
- import { getBasicClass } from '@lumx/react/utils/className';
3
+ import { Tab } from '@lumx/react';
4
+ import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
5
+ import { render, screen } from '@testing-library/react';
6
+ import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
5
7
 
6
- import { mount, shallow } from 'enzyme';
7
- import 'jest-enzyme';
8
- import React, { ReactElement } from 'react';
9
- import { build, oneOf } from 'test-data-bot';
10
- import { TabList, TabListLayout, TabListProps } from './TabList';
11
- import { setupTabProviderMocks } from './test.mocks';
8
+ import { TabList, TabListProps } from './TabList';
12
9
 
13
10
  const CLASSNAME = TabList.className as string;
14
11
 
15
- // Mock useTabProviderContext.
16
- jest.mock('./state', () => {
17
- const state = jest.requireActual('./state');
18
- return { ...state, useTabProviderContext: jest.fn(), useTabProviderContextState: jest.fn() };
19
- });
20
-
21
12
  type SetupProps = Partial<TabListProps>;
22
13
 
23
14
  /**
24
15
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
25
16
  */
26
- const setup = ({ ...propsOverride }: SetupProps = {}, shallowRendering = true) => {
17
+ const setup = (propsOverride: SetupProps = {}) => {
27
18
  const tabs = [<Tab key={0} label="Tab 0" />, <Tab key={1} label="Tab 1" />];
28
19
  const props: TabListProps = {
29
20
  children: tabs,
30
21
  'aria-label': 'Tab list',
31
22
  ...propsOverride,
32
23
  };
33
- const renderer: (el: ReactElement) => Wrapper = shallowRendering ? shallow : mount;
34
- const wrapper = renderer(<TabList {...props} />);
35
24
 
36
- return { props, wrapper };
25
+ render(<TabList {...props} />);
26
+ const tabList = getByClassName(document.body, CLASSNAME);
27
+ const links = queryByClassName(tabList, `${CLASSNAME}__links`);
28
+ return { props, tabList, links };
37
29
  };
38
30
 
39
31
  describe(`<${TabList.displayName}>`, () => {
40
- beforeEach(() => {
41
- setupTabProviderMocks();
42
- });
43
-
44
- // 1. Test render via snapshot (default states of component).
45
- describe('Snapshots and structure', () => {
46
- it('should render correctly', () => {
47
- const { wrapper } = setup();
48
- expect(wrapper).toMatchSnapshot();
49
-
50
- expect(wrapper).toExist();
51
- expect(wrapper).toHaveClassName(CLASSNAME);
52
-
53
- expect(wrapper.childAt(0)).toExist();
54
- expect(wrapper.childAt(0)).toHaveClassName('lumx-tabs__links');
55
- });
56
- });
57
-
58
- // 2. Test defaultProps value and important props custom values.
59
- describe('Props', () => {
60
- it('should use the given props', () => {
61
- const modifiedPropsBuilder: () => SetupProps = build('props').fields({
62
- layout: TabListLayout.clustered,
63
- position: oneOf(Alignment.center, Alignment.right),
64
- });
65
-
66
- const modifiedProps: SetupProps = modifiedPropsBuilder();
67
-
68
- const { wrapper } = setup({ ...modifiedProps });
69
-
70
- Object.keys(modifiedProps).forEach((prop) => {
71
- expect(wrapper).toHaveClassName(
72
- getBasicClass({ prefix: CLASSNAME, type: prop, value: modifiedProps[prop] }),
73
- );
74
- });
75
- });
32
+ it('should render default', () => {
33
+ const name = 'Tab list';
34
+ const { links } = setup({ 'aria-label': name });
35
+ expect(links).toBe(screen.queryByRole('tablist', { name }));
76
36
  });
77
37
 
78
38
  // Common tests suite.
79
- commonTestsSuite(setup, { className: 'wrapper', prop: 'wrapper' }, { className: CLASSNAME });
39
+ commonTestsSuiteRTL(setup, {
40
+ baseClassName: CLASSNAME,
41
+ forwardClassName: 'tabList',
42
+ forwardAttributes: 'tabList',
43
+ forwardRef: 'tabList',
44
+ });
80
45
  });
@@ -1,74 +1,39 @@
1
- import { Wrapper, commonTestsSuite } from '@lumx/react/testing/utils';
2
- import { mount, shallow } from 'enzyme';
3
- import 'jest-enzyme';
4
- import React, { ReactElement } from 'react';
1
+ import React from 'react';
2
+
3
+ import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
4
+ import { getByClassName } from '@lumx/react/testing/utils/queries';
5
+ import { render, screen } from '@testing-library/react';
5
6
 
6
7
  import { TabPanel, TabPanelProps } from './TabPanel';
7
- import { setupTabProviderMocks } from './test.mocks';
8
8
 
9
9
  const CLASSNAME = TabPanel.className as string;
10
10
 
11
- // Mock useTabProviderContext.
12
- jest.mock('./state', () => {
13
- const state = jest.requireActual('./state');
14
- return { ...state, useTabProviderContext: jest.fn(), useTabProviderContextState: jest.fn() };
15
- });
16
-
17
11
  type SetupProps = Partial<TabPanelProps>;
18
12
 
19
13
  /**
20
14
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
21
15
  */
22
- const setup = ({ ...propsOverride }: SetupProps = {}, shallowRendering = true) => {
16
+ const setup = (propsOverride: SetupProps = {}) => {
23
17
  const props: TabPanelProps = { ...propsOverride, children: 'Tab panel content' };
24
- const renderer: (el: ReactElement) => Wrapper = shallowRendering ? shallow : mount;
25
- const wrapper: Wrapper = renderer(<TabPanel {...props} />);
26
18
 
27
- return { props, wrapper };
19
+ render(<TabPanel {...props} />);
20
+ const tabPanel = getByClassName(document.body, CLASSNAME);
21
+
22
+ return { props, tabPanel };
28
23
  };
29
24
 
30
25
  describe(`<${TabPanel.displayName}>`, () => {
31
- // 1. Test render via snapshot (default states of component).
32
- describe('Snapshots and structure', () => {
33
- it('should render correctly', () => {
34
- setupTabProviderMocks({ index: 1 });
35
- const { wrapper } = setup();
36
- expect(wrapper).toMatchSnapshot();
37
- expect(wrapper).toHaveClassName(CLASSNAME);
38
- });
39
-
40
- it('should render isActive state', () => {
41
- setupTabProviderMocks({ index: 2, isActive: true });
42
- const { wrapper } = setup();
43
- expect(wrapper).toMatchSnapshot();
44
- });
45
-
46
- it('should not render children when not active and lazy', () => {
47
- setupTabProviderMocks({ isLazy: true, isActive: false });
48
- const { wrapper } = setup();
49
- expect(wrapper.find('div').props().children).toBeFalsy();
50
- });
51
-
52
- it('should render children when active and lazy', () => {
53
- setupTabProviderMocks({ isLazy: true, isActive: true });
54
- const { wrapper } = setup();
55
- expect(wrapper.find('div').props().children).toBeTruthy();
56
- });
57
-
58
- it('should render children when not active and not lazy', () => {
59
- setupTabProviderMocks({ isActive: false, isLazy: false });
60
- const { wrapper } = setup();
61
- expect(wrapper.find('div').props().children).toBeTruthy();
62
- });
26
+ it('should render default', () => {
27
+ const content = 'Content';
28
+ const { tabPanel } = setup({ children: content });
29
+ expect(tabPanel).toBe(screen.queryByRole('tabpanel'));
63
30
  });
64
31
 
65
32
  // Common tests suite.
66
- commonTestsSuite(
67
- (...args: any[]) => {
68
- setupTabProviderMocks();
69
- return setup(...args);
70
- },
71
- { className: 'wrapper', prop: 'wrapper' },
72
- { className: CLASSNAME },
73
- );
33
+ commonTestsSuiteRTL(setup, {
34
+ baseClassName: CLASSNAME,
35
+ forwardClassName: 'tabPanel',
36
+ forwardAttributes: 'tabPanel',
37
+ forwardRef: 'tabPanel',
38
+ });
74
39
  });
@@ -53,7 +53,7 @@ export const TabPanel: Comp<TabPanelProps, HTMLDivElement> = forwardRef((props,
53
53
  id={state?.tabPanelId}
54
54
  className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, isActive }))}
55
55
  role="tabpanel"
56
- tabIndex={0}
56
+ tabIndex={isActive ? 0 : -1}
57
57
  aria-labelledby={state?.tabId}
58
58
  >
59
59
  {(!state?.isLazy || isActive) && children}
@@ -1,42 +1,163 @@
1
- import { Tab, TabList, TabPanel, TabProvider } from '@lumx/react';
2
- import { mount } from 'enzyme';
3
- import 'jest-enzyme';
4
1
  import React from 'react';
5
2
 
6
- describe(`<${TabProvider.displayName}>`, () => {
7
- // 1. Test render via snapshot (default states of component).
8
- describe('Snapshots and structure', () => {
9
- it('should render', () => {
10
- const wrapper = mount(
11
- <TabProvider>
12
- <TabList aria-label="Tab list">
13
- <Tab label="tab 1" />
14
- <Tab label="tab 1" />
15
- </TabList>
16
-
17
- <TabPanel>Tab 1 content</TabPanel>
18
- <TabPanel>Tab 2 content</TabPanel>
19
- </TabProvider>,
20
- );
21
- const tabs = wrapper.find(Tab).find('button');
22
- const firstTab = tabs.get(0);
23
- const secondTab = tabs.get(1);
24
-
25
- const tabPanels = wrapper.find(TabPanel).find('div');
26
- const firstTabPanel = tabPanels.get(0);
27
- const secondTabPanel = tabPanels.get(1);
28
-
29
- // First tab is selected.
30
- expect(firstTab.props['aria-selected']).toBe(true);
31
- expect(secondTab.props['aria-selected']).toBe(false);
32
-
33
- // Tab id and tab panel aria-labelledby by should match
34
- expect(firstTab.props.id).toBe(firstTabPanel.props['aria-labelledby']);
35
- expect(secondTab.props.id).toBe(secondTabPanel.props['aria-labelledby']);
36
-
37
- // Tab panel id and tab aria-controls by should match
38
- expect(firstTabPanel.props.id).toBe(firstTab.props['aria-controls']);
39
- expect(secondTabPanel.props.id).toBe(secondTab.props['aria-controls']);
3
+ import { Tab, TabList, TabPanel, TabProvider, TabProviderProps } from '@lumx/react';
4
+ import { render } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import { checkTabActive, query } from '@lumx/react/components/tabs/test-utils';
7
+
8
+ const setup = (props: Partial<TabProviderProps> = {}) => {
9
+ render(
10
+ <TabProvider {...props}>
11
+ <TabList aria-label="Tab list">
12
+ <Tab label="Tab 1" />
13
+ <Tab label="Tab 2" isDisabled />
14
+ <Tab label="Tab 3" />
15
+ </TabList>
16
+
17
+ <TabPanel>Tab 1 content</TabPanel>
18
+ <TabPanel>Tab 2 content</TabPanel>
19
+ <TabPanel>Tab 3 content</TabPanel>
20
+ </TabProvider>,
21
+ );
22
+ };
23
+
24
+ describe(`TabProvider`, () => {
25
+ describe('default config', () => {
26
+ it('should render with accessible DOM', () => {
27
+ setup();
28
+
29
+ // Tab list
30
+ const tabList = query.tabList('Tab list');
31
+ expect(tabList).toBeInTheDocument();
32
+ expect(query.tabs(tabList).length).toBe(3);
33
+
34
+ // Tab 1
35
+ const tab1 = query.tab('Tab 1', tabList);
36
+ expect(tab1).toBeInTheDocument();
37
+
38
+ // Tab 2
39
+ const tab2 = query.tab('Tab 2', tabList);
40
+ expect(tab2).toBeInTheDocument();
41
+
42
+ // Tab 3
43
+ const tab3 = query.tab('Tab 3', tabList);
44
+ expect(tab3).toBeInTheDocument();
45
+
46
+ // Tab panel 1
47
+ const tabPanel1 = query.tabPanel('Tab 1');
48
+ expect(tabPanel1).toBeInTheDocument();
49
+ expect(tab1).toHaveAttribute('aria-controls', tabPanel1?.id);
50
+
51
+ // Tab panel 2
52
+ const tabPanel2 = query.tabPanel('Tab 2');
53
+ expect(tabPanel2).toBeInTheDocument();
54
+ expect(tab2).toHaveAttribute('aria-controls', tabPanel2?.id);
55
+
56
+ // Tab panel 3
57
+ const tabPanel3 = query.tabPanel('Tab 3');
58
+ expect(tabPanel3).toBeInTheDocument();
59
+ expect(tab3).toHaveAttribute('aria-controls', tabPanel3?.id);
60
+
61
+ // First tab is active
62
+ checkTabActive('Tab 1');
63
+ });
64
+
65
+ it('should switch tab on click', async () => {
66
+ setup();
67
+
68
+ checkTabActive('Tab 1');
69
+
70
+ // Click on second tab (that is disabled) should do nothing
71
+ await userEvent.click(query.tab('Tab 2'));
72
+ checkTabActive('Tab 1');
73
+
74
+ // Click on third tab should work
75
+ await userEvent.click(query.tab('Tab 3'));
76
+ checkTabActive('Tab 3');
77
+ });
78
+
79
+ it('should be navigable with keyboard', async () => {
80
+ setup();
81
+
82
+ checkTabActive('Tab 1');
83
+
84
+ // First tab stop on active tab (tab1)
85
+ await userEvent.tab();
86
+ expect(query.tab('Tab 1')).toHaveFocus();
87
+
88
+ // Second tab stop on active tab panel (tabPanel1)
89
+ await userEvent.tab();
90
+ expect(query.tabPanel('Tab 1')).toHaveFocus();
91
+
92
+ // Go back to tab1
93
+ await userEvent.tab({ shift: true });
94
+ expect(query.tab('Tab 1')).toHaveFocus();
95
+
96
+ // Navigate with ArrowRight to go to the next tab (tab2)
97
+ await userEvent.keyboard('[ArrowRight]');
98
+ expect(query.tab('Tab 2')).toHaveFocus();
99
+
100
+ // Activate tab with Enter should not work on disabled tab
101
+ await userEvent.keyboard('{Enter}');
102
+ expect(query.tab('Tab 2')).toHaveFocus();
103
+ checkTabActive('Tab 1');
104
+
105
+ // Navigate with ArrowRight to go to the next tab (tab3)
106
+ await userEvent.keyboard('[ArrowRight]');
107
+ expect(query.tab('Tab 3')).toHaveFocus();
108
+
109
+ // Activate tab with Enter
110
+ await userEvent.keyboard('{Enter}');
111
+ expect(query.tab('Tab 3')).toHaveFocus();
112
+ checkTabActive('Tab 3');
113
+
114
+ // Focus next should go to active tab panel (tabPanel3)
115
+ await userEvent.tab();
116
+ expect(query.tabPanel('Tab 3')).toHaveFocus();
117
+
118
+ // Go back to tab3
119
+ await userEvent.tab({ shift: true });
120
+ expect(query.tab('Tab 3')).toHaveFocus();
121
+
122
+ // Navigate with ArrowRight to loop back to the first tab (tab1)
123
+ await userEvent.keyboard('[ArrowRight]');
124
+ expect(query.tab('Tab 1')).toHaveFocus();
125
+
126
+ // Navigate with ArrowLeft to loop back to the last tab (tab3)
127
+ await userEvent.keyboard('[ArrowLeft]');
128
+ expect(query.tab('Tab 3')).toHaveFocus();
129
+
130
+ checkTabActive('Tab 3');
131
+ });
132
+ });
133
+
134
+ describe('not lazy', () => {
135
+ it('should render tab panel everytime', () => {
136
+ setup({ isLazy: false });
137
+ // Check the first tab is active and all panel are loaded
138
+ checkTabActive('Tab 1', { isLazy: false });
139
+ });
140
+ });
141
+
142
+ describe('activate on focus', () => {
143
+ it('should activate tab on focus', async () => {
144
+ setup({ shouldActivateOnFocus: true });
145
+
146
+ checkTabActive('Tab 1');
147
+
148
+ await userEvent.tab();
149
+ expect(query.tab('Tab 1')).toHaveFocus();
150
+ checkTabActive('Tab 1');
151
+
152
+ await userEvent.keyboard('[ArrowRight]');
153
+ expect(query.tab('Tab 2')).toHaveFocus();
154
+ // Active tab not changed since tab 2 is disabled
155
+ checkTabActive('Tab 1');
156
+
157
+ await userEvent.keyboard('[ArrowRight]');
158
+ expect(query.tab('Tab 3')).toHaveFocus();
159
+ // Active tab changed to tab 3
160
+ checkTabActive('Tab 3');
40
161
  });
41
162
  });
42
163
  });
@@ -0,0 +1,39 @@
1
+ /* istanbul ignore file */
2
+
3
+ /**
4
+ * Test util module. Do not import in production code !
5
+ */
6
+
7
+ // eslint-disable-next-line import/no-extraneous-dependencies
8
+ import { screen, within } from '@testing-library/react';
9
+
10
+ /** RTL queries for tabs */
11
+ export const query = {
12
+ tabList: (name: string) => screen.getByRole('tablist', { name }),
13
+ tabs: (tabList?: any) => (tabList ? within(tabList) : screen).getAllByRole('tab'),
14
+ tab: (name: string, tabList?: any) => (tabList ? within(tabList) : screen).getByRole('tab', { name }),
15
+ tabPanel: (name: string) => screen.getByRole('tabpanel', { name }),
16
+ };
17
+
18
+ /** Assert that the given tab is active */
19
+ export const checkTabActive = (activeTabName: string, { isLazy = true } = {}) => {
20
+ for (const tab of query.tabs()) {
21
+ const isTabActive = tab.textContent === activeTabName;
22
+
23
+ // Tab state
24
+ expect(tab).toHaveAttribute('aria-selected', String(isTabActive));
25
+ expect(tab).toHaveAttribute('tabindex', isTabActive ? '0' : '-1');
26
+
27
+ const tabPanel = query.tabPanel(tab.textContent as string);
28
+
29
+ // Tab panel state
30
+ expect(tabPanel).toHaveAttribute('tabindex', isTabActive ? '0' : '-1');
31
+
32
+ if (!isTabActive && isLazy) {
33
+ // Tab panel content not rendered when isLazy and not active
34
+ expect(tabPanel).toBeEmptyDOMElement();
35
+ } else {
36
+ expect(tabPanel).not.toBeEmptyDOMElement();
37
+ }
38
+ }
39
+ };