@transferwise/components 0.0.0-experimental-2aa3886 → 0.0.0-experimental-7b99003

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transferwise/components",
3
- "version": "0.0.0-experimental-2aa3886",
3
+ "version": "0.0.0-experimental-7b99003",
4
4
  "description": "Neptune React components",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -43,7 +43,6 @@
43
43
  "@babel/preset-env": "^7.27.2",
44
44
  "@babel/preset-react": "^7.27.1",
45
45
  "@babel/preset-typescript": "^7.27.1",
46
- "@cfaester/enzyme-adapter-react-18": "0.8.0",
47
46
  "@formatjs/cli": "^6.7.1",
48
47
  "@rollup/plugin-babel": "^6.0.4",
49
48
  "@rollup/plugin-json": "^6.1.0",
@@ -72,7 +71,6 @@
72
71
  "@tsconfig/recommended": "^1.0.8",
73
72
  "@types/babel__core": "^7.20.5",
74
73
  "@types/commonmark": "^0.27.9",
75
- "@types/enzyme": "^3.10.19",
76
74
  "@types/jest": "^29.5.14",
77
75
  "@types/lodash": "4.17.17",
78
76
  "@types/lodash.clamp": "^4.0.9",
@@ -83,7 +81,6 @@
83
81
  "@wise/art": "^2.21.1",
84
82
  "@wise/eslint-config": "^12.3.0",
85
83
  "babel-plugin-formatjs": "^10.5.38",
86
- "enzyme": "^3.11.0",
87
84
  "eslint": "^9.27.0",
88
85
  "gulp": "^5.0.0",
89
86
  "jest": "^29.7.0",
@@ -1,14 +1,10 @@
1
1
  import util from 'node:util';
2
2
  import '@testing-library/jest-dom';
3
3
 
4
- import Adapter from '@cfaester/enzyme-adapter-react-18';
5
- import Enzyme from 'enzyme';
6
4
  import fetchMock from 'jest-fetch-mock';
7
5
 
8
6
  global.fetch = fetchMock as unknown as typeof global.fetch;
9
7
 
10
- Enzyme.configure({ adapter: new Adapter() });
11
-
12
8
  global.requestAnimationFrame = (callback: (time: number) => void): number => {
13
9
  callback(performance.now());
14
10
  return 0;
@@ -0,0 +1,182 @@
1
+ import { Field } from '../field/Field';
2
+ import { mockMatchMedia, render, screen, fireEvent } from '../test-utils';
3
+ import { wait } from '../test-utils/wait';
4
+ import Typeahead from './Typeahead';
5
+
6
+ mockMatchMedia();
7
+
8
+ describe('Typeahead', () => {
9
+ it('supports `Field` for labeling', () => {
10
+ render(
11
+ <Field id="test" label="Tags">
12
+ <Typeahead id="test" name="test" options={[{ label: 'Test' }]} onChange={() => {}} />
13
+ </Field>,
14
+ );
15
+ expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Tags/);
16
+ });
17
+
18
+ describe('when no options are provided', () => {
19
+ it('does not render a dropdown when no options and no footer are provided', () => {
20
+ render(
21
+ <Field id="test" label="Tags">
22
+ <Typeahead id="test" name="test" options={[]} onChange={() => {}} />
23
+ </Field>,
24
+ );
25
+ expect(screen.queryByRole('menu')).not.toBeInTheDocument();
26
+ });
27
+ it('does render a dropdown when only a footer is provided', () => {
28
+ render(
29
+ <Field id="test" label="Tags">
30
+ <Typeahead id="test" name="test" options={[]} footer={<p>hello</p>} onChange={() => {}} />
31
+ </Field>,
32
+ );
33
+ expect(screen.getByRole('menu')).toBeInTheDocument();
34
+ });
35
+ });
36
+
37
+ it('renders input with placeholder', () => {
38
+ render(
39
+ <Typeahead id="test" name="test" options={[]} placeholder="Type here" onChange={() => {}} />,
40
+ );
41
+ expect(screen.getByPlaceholderText('Type here')).toBeInTheDocument();
42
+ });
43
+
44
+ it('renders chips when multiple is true and selected has items', () => {
45
+ const initialValue = [{ label: 'Chip 1' }, { label: 'Chip 2' }];
46
+ render(
47
+ <Typeahead
48
+ id="test"
49
+ name="test"
50
+ options={[]}
51
+ multiple
52
+ initialValue={initialValue}
53
+ onChange={() => {}}
54
+ />,
55
+ );
56
+ expect(screen.getByText('Chip 1')).toBeInTheDocument();
57
+ expect(screen.getByText('Chip 2')).toBeInTheDocument();
58
+ });
59
+
60
+ it('calls onChange when selecting an option', () => {
61
+ const onChange = jest.fn();
62
+ render(
63
+ <Typeahead
64
+ id="test"
65
+ name="test"
66
+ options={[{ label: 'Option 1' }, { label: 'Option 2' }]}
67
+ minQueryLength={0}
68
+ onChange={onChange}
69
+ />,
70
+ );
71
+ const input = screen.getByRole('combobox');
72
+ fireEvent.change(input, { target: { value: 'Option 1' } });
73
+ fireEvent.click(screen.getByText('Option 1'));
74
+ expect(onChange).toHaveBeenCalled();
75
+ });
76
+
77
+ it('shows clear button when there is a value', () => {
78
+ render(
79
+ <Typeahead
80
+ id="test"
81
+ name="test"
82
+ options={[]}
83
+ initialValue={[{ label: 'Chip' }]}
84
+ onChange={() => {}}
85
+ />,
86
+ );
87
+ expect(screen.getByRole('button', { name: /clear/i })).toBeInTheDocument();
88
+ });
89
+
90
+ it('clears value when clear button is clicked', () => {
91
+ const onChange = jest.fn();
92
+ render(
93
+ <Typeahead
94
+ id="test"
95
+ name="test"
96
+ options={[]}
97
+ initialValue={[{ label: 'Chip' }]}
98
+ onChange={onChange}
99
+ />,
100
+ );
101
+ fireEvent.click(screen.getByRole('button', { name: /clear/i }));
102
+ expect(onChange).toHaveBeenCalledWith([]);
103
+ });
104
+
105
+ it('shows InlineAlert when alert prop is provided', () => {
106
+ render(
107
+ <Typeahead
108
+ id="test"
109
+ name="test"
110
+ options={[]}
111
+ alert={{ message: 'Something went wrong', type: 'error' }}
112
+ onChange={() => {}}
113
+ />,
114
+ );
115
+ expect(screen.getByText('Something went wrong')).toBeInTheDocument();
116
+ });
117
+
118
+ it('calls onFocus when input is focused', () => {
119
+ const onFocus = jest.fn();
120
+ render(<Typeahead id="test" name="test" options={[]} onChange={() => {}} onFocus={onFocus} />);
121
+ fireEvent.focus(screen.getByRole('combobox'));
122
+ expect(onFocus).toHaveBeenCalled();
123
+ });
124
+
125
+ it('calls onInputChange when input value changes', () => {
126
+ const onInputChange = jest.fn();
127
+ render(
128
+ <Typeahead
129
+ id="test"
130
+ name="test"
131
+ options={[]}
132
+ minQueryLength={0}
133
+ onChange={() => {}}
134
+ onInputChange={onInputChange}
135
+ />,
136
+ );
137
+ fireEvent.change(screen.getByRole('combobox'), { target: { value: 'abc' } });
138
+ expect(onInputChange).toHaveBeenCalledWith('abc');
139
+ });
140
+
141
+ it('calls onSearch when input value changes', async () => {
142
+ const onSearch = jest.fn();
143
+ render(
144
+ <Typeahead
145
+ id="test"
146
+ name="test"
147
+ options={[]}
148
+ minQueryLength={0}
149
+ onChange={() => {}}
150
+ onSearch={onSearch}
151
+ />,
152
+ );
153
+ fireEvent.change(screen.getByRole('combobox'), { target: { value: 'abc' } });
154
+ // wait for debounce
155
+ await wait(500);
156
+ expect(onSearch).toHaveBeenCalledWith('abc');
157
+ });
158
+
159
+ it('adds a new chip when allowNew and multiple are true and separator is pasted', () => {
160
+ const onChange = jest.fn();
161
+ render(
162
+ <Typeahead
163
+ id="test"
164
+ name="test"
165
+ options={[]}
166
+ allowNew
167
+ multiple
168
+ chipSeparators={[',']}
169
+ onChange={onChange}
170
+ />,
171
+ );
172
+ const input = screen.getByRole('combobox');
173
+ fireEvent.paste(input, {
174
+ clipboardData: {
175
+ getData: () => 'foo,bar',
176
+ },
177
+ });
178
+ expect(onChange).toHaveBeenCalledWith(
179
+ expect.arrayContaining([{ label: 'foo' }, { label: 'bar' }]),
180
+ );
181
+ });
182
+ });
@@ -0,0 +1,103 @@
1
+ import { render, fireEvent, screen } from '../../test-utils';
2
+ import TypeaheadInput, { TypeaheadInputProps } from './TypeaheadInput';
3
+ import { TypeaheadOption } from '../Typeahead';
4
+
5
+ const defaultProps: TypeaheadInputProps<number> = {
6
+ id: 'test-id',
7
+ name: 'test-name',
8
+ typeaheadId: 'test-id',
9
+ value: '',
10
+ selected: [],
11
+ onChange: jest.fn(),
12
+ onKeyDown: jest.fn(),
13
+ onFocus: jest.fn(),
14
+ onPaste: jest.fn(),
15
+ autoComplete: 'off',
16
+ placeholder: 'Search...',
17
+ renderChip: jest.fn(),
18
+ };
19
+
20
+ describe('TypeaheadInput', () => {
21
+ afterEach(() => {
22
+ jest.clearAllMocks();
23
+ });
24
+
25
+ it('renders input with placeholder', () => {
26
+ render(<TypeaheadInput {...defaultProps} />);
27
+ expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
28
+ });
29
+
30
+ it('renders with given value', () => {
31
+ render(<TypeaheadInput {...defaultProps} value="hello" />);
32
+ expect(screen.getByDisplayValue('hello')).toBeInTheDocument();
33
+ });
34
+
35
+ it('calls onChange when input value changes', () => {
36
+ const onChange = jest.fn();
37
+ render(<TypeaheadInput {...defaultProps} onChange={onChange} />);
38
+ const input = screen.getByPlaceholderText('Search...');
39
+ fireEvent.change(input, { target: { value: 'test' } });
40
+ expect(onChange).toHaveBeenCalled();
41
+ });
42
+
43
+ it('calls onFocus when input is focused', () => {
44
+ const onFocus = jest.fn();
45
+ render(<TypeaheadInput {...defaultProps} onFocus={onFocus} />);
46
+ const input = screen.getByPlaceholderText('Search...');
47
+ fireEvent.focus(input);
48
+ expect(onFocus).toHaveBeenCalled();
49
+ });
50
+
51
+ it('calls onPaste when input is pasted into', () => {
52
+ const onPaste = jest.fn();
53
+ render(<TypeaheadInput {...defaultProps} onPaste={onPaste} />);
54
+ const input = screen.getByPlaceholderText('Search...');
55
+ fireEvent.paste(input, { clipboardData: { getData: () => 'pasted' } });
56
+ expect(onPaste).toHaveBeenCalled();
57
+ });
58
+
59
+ it('calls onKeyDown when key is pressed', () => {
60
+ const onKeyDown = jest.fn();
61
+ render(<TypeaheadInput {...defaultProps} onKeyDown={onKeyDown} />);
62
+ const input = screen.getByPlaceholderText('Search...');
63
+ fireEvent.keyDown(input, { key: 'ArrowDown', code: 'ArrowDown' });
64
+ expect(onKeyDown).toHaveBeenCalled();
65
+ });
66
+
67
+ it('renders chips when multiple is true and selected has items', () => {
68
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
69
+ const renderChip = jest.fn((chip, idx) => <span key={idx}>{chip.label}</span>);
70
+ const selected = [
71
+ { label: 'Chip 1', value: 1 },
72
+ { label: 'Chip 2', value: 2 },
73
+ ];
74
+ render(
75
+ <TypeaheadInput {...defaultProps} multiple selected={selected} renderChip={renderChip} />,
76
+ );
77
+ expect(screen.getByText('Chip 1')).toBeInTheDocument();
78
+ expect(screen.getByText('Chip 2')).toBeInTheDocument();
79
+ });
80
+
81
+ it('does not show placeholder if multiple is true and selected has items', () => {
82
+ const selected: TypeaheadOption<number>[] = [{ label: 'Chip', value: 1 }];
83
+ render(<TypeaheadInput<number> {...defaultProps} multiple selected={selected} />);
84
+ expect(screen.queryByPlaceholderText('Search...')).not.toBeInTheDocument();
85
+ });
86
+
87
+ it('applies input width style when multiple and selected has items', () => {
88
+ const selected = [{ label: 'Chip', value: 1 }];
89
+ const { container } = render(
90
+ <TypeaheadInput {...defaultProps} multiple selected={selected} value="test" />,
91
+ );
92
+ const input = container.querySelector('input');
93
+ expect(input?.style.width).not.toBe('');
94
+ });
95
+
96
+ it('sets aria attributes correctly', () => {
97
+ render(<TypeaheadInput {...defaultProps} dropdownOpen ariaActivedescendant="option-1" />);
98
+ const input = screen.getByRole('combobox');
99
+ expect(input).toHaveAttribute('aria-expanded', 'true');
100
+ expect(input).toHaveAttribute('aria-haspopup', 'listbox');
101
+ expect(input).toHaveAttribute('aria-activedescendant', expect.stringContaining('option-1'));
102
+ });
103
+ });
@@ -0,0 +1,43 @@
1
+ import { render, screen } from '../../test-utils';
2
+ import Highlight from './highlight';
3
+
4
+ describe('Highlight', () => {
5
+ it('renders value with highlighted query (case-insensitive)', () => {
6
+ render(<Highlight value="Hello World" query="world" />);
7
+ expect(screen.getByText('World')).toBeInTheDocument();
8
+ expect(screen.getByText('World').tagName).toBe('STRONG');
9
+ expect(screen.getByText('Hello', { exact: false })).toBeInTheDocument();
10
+ });
11
+
12
+ it('renders value with highlighted query (case-sensitive in output)', () => {
13
+ render(<Highlight value="Hello World" query="HELLO" />);
14
+ expect(screen.getByText('Hello').tagName).toBe('STRONG');
15
+ expect(screen.getByText('World', { exact: false })).toBeInTheDocument();
16
+ });
17
+
18
+ it('renders value without highlight if query not found', () => {
19
+ render(<Highlight value="Hello World" query="foo" />);
20
+ expect(screen.getByText('Hello World')).toBeInTheDocument();
21
+ expect(screen.queryByRole('strong')).not.toBeInTheDocument();
22
+ });
23
+
24
+ it('renders value as is if query is empty', () => {
25
+ render(<Highlight value="Hello World" query="" />);
26
+ expect(screen.getByText('Hello World')).toBeInTheDocument();
27
+ expect(screen.queryByRole('strong')).not.toBeInTheDocument();
28
+ });
29
+
30
+ it('renders value as is if value is empty', () => {
31
+ render(<Highlight value="" query="test" />);
32
+ expect(screen.queryByText(/./)).not.toBeInTheDocument();
33
+ expect(screen.queryByRole('strong')).not.toBeInTheDocument();
34
+ });
35
+
36
+ it('wraps content in span if className is provided', () => {
37
+ const { container } = render(
38
+ <Highlight value="Hello World" query="world" className="highlighted" />,
39
+ );
40
+ expect(container.querySelector('span.highlighted')).toBeInTheDocument();
41
+ expect(container.querySelector('strong')).toBeInTheDocument();
42
+ });
43
+ });
@@ -1,54 +0,0 @@
1
- import { Field } from '../field/Field';
2
- import { mockMatchMedia, render, screen } from '../test-utils';
3
- import Typeahead from './Typeahead';
4
- import { createIntl, createIntlCache } from 'react-intl';
5
- import messages from '../i18n';
6
- import { DEFAULT_LANG, DEFAULT_LOCALE } from '../common';
7
-
8
- mockMatchMedia();
9
-
10
- const cache = createIntlCache();
11
- const intl = createIntl({ locale: DEFAULT_LOCALE, messages: messages[DEFAULT_LANG] }, cache);
12
-
13
- describe('Typeahead', () => {
14
- it('supports `Field` for labeling', () => {
15
- render(
16
- <Field id="test" label="Tags">
17
- <Typeahead
18
- id="test"
19
- name="test"
20
- options={[{ label: 'Test' }]}
21
- intl={intl}
22
- onChange={() => {}}
23
- />
24
- </Field>,
25
- );
26
- expect(screen.getAllByRole('group')[0]).toHaveAccessibleName(/^Tags/);
27
- });
28
-
29
- describe('when no options are provided', () => {
30
- it('does not render a dropdown when no options and no footer are provided', () => {
31
- render(
32
- <Field id="test" label="Tags">
33
- <Typeahead id="test" name="test" options={[]} intl={intl} onChange={() => {}} />
34
- </Field>,
35
- );
36
- expect(screen.queryByRole('menu')).not.toBeInTheDocument();
37
- });
38
- it('does render a dropdown when only a footer is provided', () => {
39
- render(
40
- <Field id="test" label="Tags">
41
- <Typeahead
42
- id="test"
43
- name="test"
44
- options={[]}
45
- intl={intl}
46
- footer={<p>hello</p>}
47
- onChange={() => {}}
48
- />
49
- </Field>,
50
- );
51
- expect(screen.getByRole('menu')).toBeInTheDocument();
52
- });
53
- });
54
- });
@@ -1,404 +0,0 @@
1
- import { mount } from 'enzyme';
2
- import doTimes from 'lodash.times';
3
- import { mockMatchMedia } from '../test-utils';
4
-
5
- import { InlineAlert } from '..';
6
- import { Sentiment } from '../common';
7
- import { fakeEvent, fakeKeyDownEventForKey } from '../common/fakeEvents';
8
-
9
- import Typeahead from './Typeahead';
10
-
11
- mockMatchMedia();
12
-
13
- const defaultLocale = 'en-GB';
14
- jest.mock('react-intl', () => {
15
- const mockedIntl = {
16
- locale: defaultLocale,
17
- formatMessage: (id) => String(id),
18
- };
19
- return {
20
- injectIntl: (Component) => (props) => <Component {...props} intl={mockedIntl} />,
21
- defineMessages: (translations) => translations,
22
- useIntl: () => mockedIntl,
23
- };
24
- });
25
-
26
- describe('Typeahead', () => {
27
- let component;
28
- let props;
29
- const closeButton = () => component.find('.input-group-addon button');
30
- const formGroup = () => component.find('.form-group');
31
- const input = () => component.find('input');
32
- const chip = () => component.find('.np-chip');
33
- const option = () => component.find('.dropdown-item');
34
- const menu = () => component.find('.dropdown');
35
-
36
- beforeEach(() => {
37
- props = {
38
- id: 'test',
39
- onChange: () => {},
40
- name: 'test',
41
- options: [
42
- {
43
- label: 'Test',
44
- },
45
- {
46
- label: 'Test1',
47
- },
48
- {
49
- label: 'Test2',
50
- },
51
- {
52
- label: 'Test3',
53
- },
54
- ],
55
- };
56
-
57
- component = mount(<Typeahead {...props} />);
58
- });
59
-
60
- it('renders addon', () => {
61
- expect(component.find('#test-addon')).toHaveLength(0);
62
- component.setProps({ addon: <div id="test-addon" /> });
63
- expect(component.find('#test-addon')).toHaveLength(1);
64
- });
65
-
66
- it('renders clear button', () => {
67
- component.setProps({
68
- clearable: true,
69
- });
70
- input().simulate('change', { target: { value: 'test' } });
71
-
72
- expect(closeButton()).toHaveLength(1);
73
- });
74
-
75
- it('splits pasted value correctly', () => {
76
- const chips = ['test', 'test2', 'test3'];
77
- const event = {
78
- preventDefault: jest.fn(),
79
- clipboardData: {
80
- getData: () => chips.join(', '),
81
- },
82
- };
83
-
84
- component.setProps({
85
- allowNew: true,
86
- multiple: true,
87
- chipSeparators: [',', ' '],
88
- });
89
-
90
- input().simulate('paste', event);
91
- const renderedChips = chip().map((chipNode) => chipNode.text());
92
-
93
- expect(renderedChips.every((label, idx) => chips[idx] === label)).toBe(true);
94
- expect(renderedChips).toHaveLength(chips.length);
95
- expect(event.preventDefault).toHaveBeenCalledTimes(1);
96
- });
97
-
98
- it('removes last selected value when backspace clicked on empty input', () => {
99
- const event = {
100
- key: 'Backspace',
101
- };
102
-
103
- component.setProps({
104
- multiple: true,
105
- });
106
-
107
- expect(chip()).toHaveLength(0);
108
-
109
- input().simulate('change', { target: { value: 'test' } });
110
- option().at(0).simulate('click', fakeEvent());
111
-
112
- expect(chip()).toHaveLength(1);
113
-
114
- input().simulate('keyDown', event);
115
-
116
- expect(chip()).toHaveLength(0);
117
- });
118
-
119
- it('does not remove last selected value when backspace clicked on non-empty input', () => {
120
- const event = {
121
- key: 'Backspace',
122
- };
123
-
124
- component.setProps({
125
- multiple: true,
126
- });
127
-
128
- expect(chip()).toHaveLength(0);
129
-
130
- input().simulate('change', { target: { value: 'test' } });
131
- option().at(0).simulate('click', fakeEvent());
132
-
133
- expect(chip()).toHaveLength(1);
134
-
135
- input().simulate('change', { target: { value: 'test' } });
136
- input().simulate('keyDown', event);
137
-
138
- expect(chip()).toHaveLength(1);
139
- });
140
-
141
- it('adds new value as selected and clears the input when separator is entered', () => {
142
- const event = {
143
- key: ',',
144
- preventDefault: jest.fn(),
145
- };
146
- const text = 'test';
147
-
148
- component.setProps({
149
- multiple: true,
150
- allowNew: true,
151
- showSuggestions: false,
152
- chipSeparators: [','],
153
- });
154
-
155
- input().simulate('change', { target: { value: text } });
156
- input().simulate('keyDown', event);
157
-
158
- expect(chip().text()).toStrictEqual(text);
159
- expect(event.preventDefault).toHaveBeenCalledTimes(1);
160
- });
161
-
162
- it('adds new value as selected and clears the input when Enter is pressed', () => {
163
- const event = {
164
- key: 'Enter',
165
- preventDefault: jest.fn(),
166
- };
167
- const text = 'test';
168
-
169
- component.setProps({
170
- multiple: true,
171
- allowNew: true,
172
- showSuggestions: false,
173
- chipSeparators: [','],
174
- });
175
-
176
- input().simulate('change', { target: { value: text } });
177
- input().simulate('keyDown', event);
178
-
179
- expect(chip().text()).toStrictEqual(text);
180
- expect(event.preventDefault).toHaveBeenCalledTimes(1);
181
- });
182
-
183
- it('adds new value as selected and clears the input when Tab is pressed', () => {
184
- const event = {
185
- key: 'Tab',
186
- preventDefault: jest.fn(),
187
- };
188
- const text = 'test';
189
-
190
- component.setProps({
191
- multiple: true,
192
- allowNew: true,
193
- showSuggestions: false,
194
- chipSeparators: [','],
195
- });
196
-
197
- input().simulate('change', { target: { value: text } });
198
- input().simulate('keyDown', event);
199
-
200
- expect(chip().text()).toStrictEqual(text);
201
- expect(event.preventDefault).toHaveBeenCalledTimes(1);
202
- });
203
-
204
- it('clears typeahead when clear button is clicked', () => {
205
- const event = {
206
- key: ',',
207
- preventDefault: jest.fn(),
208
- };
209
-
210
- const query = 'test';
211
-
212
- component.setProps({
213
- multiple: true,
214
- allowNew: true,
215
- showSuggestions: false,
216
- chipSeparators: [','],
217
- clearable: true,
218
- });
219
-
220
- input().simulate('change', { target: { value: query } });
221
- input().simulate('keyDown', event);
222
- input().simulate('change', { target: { value: query } });
223
-
224
- expect(chip()).toHaveLength(1);
225
- expect(input().getDOMNode().value).toBe(query);
226
-
227
- closeButton().simulate('click', fakeEvent());
228
- expect(chip()).toHaveLength(0);
229
- expect(input().getDOMNode().value).toBe('');
230
- });
231
-
232
- it('moves selected items when down and up keys pressed', () => {
233
- doTimes(3, () => input().simulate('keyDown', fakeKeyDownEventForKey('ArrowDown')));
234
-
235
- expect(option().at(2).parent().is('.tw-dropdown-item--focused')).toBe(true);
236
-
237
- input().simulate('keyDown', fakeKeyDownEventForKey('ArrowUp'));
238
- expect(option().at(1).parent().is('.tw-dropdown-item--focused')).toBe(true);
239
-
240
- doTimes(5, () => input().simulate('keyDown', fakeKeyDownEventForKey('ArrowDown')));
241
- expect(option().last().parent().is('.tw-dropdown-item--focused')).toBe(true);
242
- });
243
-
244
- it('adds new value as selected and clears the input when no option is highlighted and enter is pressed', () => {
245
- const event = {
246
- key: 'Enter',
247
- preventDefault: jest.fn(),
248
- };
249
- const text = 'test';
250
-
251
- component.setProps({
252
- multiple: true,
253
- allowNew: true,
254
- chipSeparators: [','],
255
- });
256
-
257
- input().simulate('change', { target: { value: text } });
258
- input().simulate('keyDown', event);
259
-
260
- expect(chip().text()).toStrictEqual(text);
261
- expect(event.preventDefault).toHaveBeenCalledTimes(1);
262
- });
263
-
264
- it('sets aria-activedescendant correctly based on focus', () => {
265
- const inputElement = input().getDOMNode();
266
-
267
- doTimes(3, () => input().simulate('keyDown', fakeKeyDownEventForKey('ArrowDown')));
268
-
269
- expect(inputElement).toHaveAttribute('aria-activedescendant', 'menu-test option-Test2');
270
- });
271
-
272
- it('displays alert when alert is provided and chips are valid or alert type is error', () => {
273
- const event = {
274
- key: 'Enter',
275
- preventDefault: jest.fn(),
276
- };
277
- const text = 'test';
278
-
279
- expect(component.find(InlineAlert)).toHaveLength(0);
280
- expect(formGroup().hasClass('has-error')).toBe(false);
281
- component.setProps({
282
- validateChip: () => {
283
- return false;
284
- },
285
- allowNew: true,
286
- alert: {
287
- message: 'test',
288
- type: Sentiment.ERROR,
289
- },
290
- });
291
-
292
- expect(formGroup().hasClass('has-error')).toBe(true);
293
- expect(component.find(InlineAlert)).toHaveLength(1);
294
-
295
- input().simulate('change', { target: { value: text } });
296
- input().simulate('keyDown', event);
297
-
298
- expect(formGroup().hasClass('has-error')).toBe(true);
299
- expect(component.find(InlineAlert)).toHaveLength(1);
300
- component.setProps({
301
- alert: {
302
- message: 'test',
303
- type: Sentiment.WARNING,
304
- },
305
- });
306
-
307
- expect(formGroup().hasClass('has-error')).toBe(true);
308
- expect(component.find(InlineAlert)).toHaveLength(0);
309
-
310
- closeButton().simulate('click', fakeEvent());
311
-
312
- expect(formGroup().hasClass('has-error')).toBe(false);
313
- expect(formGroup().hasClass('has-warning')).toBe(true);
314
- expect(component.find(InlineAlert)).toHaveLength(1);
315
- });
316
-
317
- it('allows supplying options with arbitrary data that will be included with the onChange callback', () => {
318
- const text = 'test';
319
- const options = [
320
- {
321
- label: 'label 1',
322
- note: 'note-1',
323
- secondary: 'secondary-1',
324
- value: { id: 123, name: 'Neptune' },
325
- },
326
- ];
327
- let selectedOption;
328
-
329
- component.setProps({
330
- onChange: (selections) => {
331
- selectedOption = selections[0];
332
- },
333
- options,
334
- });
335
-
336
- input().simulate('change', { target: { value: text } });
337
- option().at(0).simulate('click', fakeEvent());
338
- expect(selectedOption).toStrictEqual({
339
- label: 'label 1',
340
- note: 'note-1',
341
- secondary: 'secondary-1',
342
- value: { id: 123, name: 'Neptune' },
343
- });
344
- });
345
-
346
- describe('menu', () => {
347
- it('should render footer', () => {
348
- component.setProps({
349
- footer: <div id="footer">footer</div>,
350
- });
351
-
352
- expect(component.find('#footer')).toHaveLength(1);
353
- });
354
-
355
- it('renders all options', () => {
356
- const options = option().map((optNode) => optNode.text());
357
- expect(options).toHaveLength(props.options.length);
358
- expect(options.every((label, i) => label === props.options[i].label)).toBe(true);
359
- });
360
-
361
- it('renders dropdown-menu if has options', () => {
362
- expect(component.find('.dropdown-menu')).toHaveLength(1);
363
- component.setProps({ options: [] });
364
- expect(component.find('.dropdown-menu')).toHaveLength(0);
365
- });
366
-
367
- it('does not render new option if showNewEntry is false', () => {
368
- component.setProps({
369
- allowNew: true,
370
- showNewEntry: false,
371
- });
372
- input().simulate('change', { target: { value: 'check' } });
373
-
374
- const options = option().map((optNode) => optNode.text());
375
- expect(options).toHaveLength(props.options.length);
376
- });
377
-
378
- it('renders new option if showNewEntry is true', () => {
379
- component.setProps({
380
- allowNew: true,
381
- showNewEntry: true,
382
- });
383
- input().simulate('change', { target: { value: 'check' } });
384
-
385
- const options = option().map((optNode) => optNode.text());
386
- expect(options).toHaveLength(props.options.length + 1);
387
- });
388
-
389
- it('does not show options if query is too short', () => {
390
- expect(menu().is('.open')).toBe(false);
391
- input().simulate('change', { target: { value: 'test' } });
392
- expect(menu().is('.open')).toBe(true);
393
- });
394
-
395
- it('sets aria-expanded to true when options are shown', () => {
396
- expect(input().prop('aria-expanded')).toBe(false);
397
- // we don't want aria-expanded to be true on focus or before 3 characters are entered (that's when the menu is shown)
398
- input().simulate('change', { target: { value: 'aa' } });
399
- expect(input().prop('aria-expanded')).toBe(false);
400
- input().simulate('change', { target: { value: 'aaa' } });
401
- expect(input().prop('aria-expanded')).toBe(true);
402
- });
403
- });
404
- });
@@ -1,74 +0,0 @@
1
- import { shallow } from 'enzyme';
2
-
3
- import { Input } from '../../inputs/Input';
4
-
5
- import TypeaheadInput from './TypeaheadInput';
6
-
7
- describe('Typeahead input', () => {
8
- let component;
9
- let props;
10
-
11
- const inputWrapper = () => component.find('.typeahead__input-wrapper');
12
- const input = () => component.find(Input);
13
-
14
- beforeEach(() => {
15
- props = {
16
- name: 'test',
17
- typeaheadId: 'test',
18
- multiple: false,
19
- value: '',
20
- renderChip: jest.fn(),
21
- onKeyDown: jest.fn(),
22
- onFocus: jest.fn(),
23
- onPaste: jest.fn(),
24
- onChange: jest.fn(),
25
- autoComplete: 'off',
26
- selected: [],
27
- };
28
-
29
- component = shallow(<TypeaheadInput {...props} />);
30
- });
31
-
32
- it('renders single input when multiple is false', () => {
33
- expect(component.children()).toHaveLength(0);
34
- expect(component.is(Input)).toBe(true);
35
- });
36
-
37
- it('renders chips when provided', () => {
38
- const selected = [{ label: 'test1' }, { label: 'test2' }];
39
- component.setProps({
40
- multiple: true,
41
- selected,
42
- renderChip: jest.fn(({ label }, idx) => <span key={idx}>{label}</span>),
43
- });
44
-
45
- expect(inputWrapper().find('span')).toHaveLength(selected.length);
46
- });
47
-
48
- it('adds aria-controls prop if dropdown is open', () => {
49
- expect(component.prop('aria-controls')).toBeUndefined();
50
- component.setProps({ dropdownOpen: true });
51
- expect(component.prop('aria-controls')).toBe('menu-test');
52
- });
53
-
54
- it('passes all callbacks to input', () => {
55
- const event = {};
56
- const extraProps = {
57
- multiple: true,
58
- value: '',
59
- onKeyDown: jest.fn(),
60
- onBlur: jest.fn(),
61
- onFocus: jest.fn(),
62
- onPaste: jest.fn(),
63
- };
64
- component.setProps(extraProps);
65
-
66
- input().simulate('focus', event);
67
- input().simulate('paste', event);
68
- input().simulate('keyDown', event);
69
- const inputProps = input().props();
70
- expect(inputProps.onKeyDown).toHaveBeenCalledWith(event);
71
- expect(inputProps.onFocus).toHaveBeenCalledWith(event);
72
- expect(inputProps.onPaste).toHaveBeenCalledWith(event);
73
- });
74
- });
@@ -1,75 +0,0 @@
1
- import { shallow, mount } from 'enzyme';
2
-
3
- import { fakeEvent } from '../../common/fakeEvents';
4
-
5
- import TypeaheadOption from './TypeaheadOption';
6
- import Highlight from '../util/highlight';
7
-
8
- describe('Typeahead Option', () => {
9
- let props;
10
- let component;
11
-
12
- const labelHighlight = () => component.find(Highlight);
13
- const noteSpan = () => component.find('.np-text-body-default.m-l-1');
14
- const secondaryTextHighlight = () => component.find('.np-text-body-default.text-ellipsis > span');
15
- const dropdownItem = () => component.find('.dropdown-item');
16
-
17
- beforeEach(() => {
18
- props = {
19
- option: {
20
- label: 'test',
21
- },
22
- };
23
-
24
- component = mount(<TypeaheadOption {...props} />);
25
- });
26
-
27
- it('renders a label', () => {
28
- const label = 'test';
29
- component.setProps({ option: { label } });
30
- expect(labelHighlight().text()).toStrictEqual(label);
31
- });
32
-
33
- it('renders a note', () => {
34
- const label = 'test';
35
- const note = 'test note';
36
- component.setProps({ option: { label, note } });
37
- expect(noteSpan().text()).toStrictEqual(note);
38
- });
39
-
40
- it('renders a seconday text', () => {
41
- const label = 'test';
42
- const secondary = 'test note';
43
- component.setProps({ option: { label, secondary } });
44
- expect(secondaryTextHighlight().text()).toStrictEqual(secondary);
45
- });
46
-
47
- it('highlights when selected', () => {
48
- component = shallow(<TypeaheadOption {...props} />);
49
- component.setProps({
50
- selected: true,
51
- });
52
-
53
- expect(component.is('.tw-dropdown-item--focused')).toBe(true);
54
- });
55
-
56
- it('expect aria-selected to be true', () => {
57
- component = shallow(<TypeaheadOption {...props} />);
58
- component.setProps({
59
- selected: true,
60
- });
61
-
62
- expect(component.prop('aria-selected')).toBe('true');
63
- });
64
-
65
- it('calls onClick when clicked', () => {
66
- const onClick = jest.fn();
67
- component.setProps({
68
- onClick,
69
- });
70
-
71
- dropdownItem().simulate('click', fakeEvent);
72
-
73
- expect(onClick).toHaveBeenCalledTimes(1);
74
- });
75
- });
@@ -1,34 +0,0 @@
1
- import { mount } from 'enzyme';
2
-
3
- import Highlight from './highlight';
4
-
5
- describe('Typeahead input', () => {
6
- const highlighted = (node) => node.find('strong');
7
- const getTextFromNodes = (nodes) => nodes.reduce((value, node) => value + node.text(), '');
8
-
9
- it('highlights part of label that matches the query', () => {
10
- const query = 'test';
11
- const label = `this is a ${query} label`;
12
- const result = mount(<Highlight value={label} query={query} />);
13
-
14
- expect(highlighted(result).text()).toStrictEqual(query);
15
-
16
- expect(getTextFromNodes(result)).toBe(label);
17
- });
18
-
19
- it('does not change text if query is not present in it', () => {
20
- const query = 'test';
21
- const label = `this is a label`;
22
- const result = mount(<Highlight value={label} query={query} />);
23
-
24
- expect(result.text()).toBe(label);
25
- });
26
-
27
- it('highlights whole label that matches the query', () => {
28
- const query = 'test';
29
- const label = query;
30
- const result = mount(<Highlight value={label} query={query} />);
31
-
32
- expect(highlighted(result).text()).toStrictEqual(query);
33
- });
34
- });