@truedat/core 5.18.2 → 5.19.0

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": "@truedat/core",
3
- "version": "5.18.2",
3
+ "version": "5.19.0",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -117,5 +117,5 @@
117
117
  "react-dom": ">= 16.8.6 < 17",
118
118
  "semantic-ui-react": ">= 2.0.3 < 2.2"
119
119
  },
120
- "gitHead": "d69ace0ddcf34baa4f4b60a6862a053327c1d299"
120
+ "gitHead": "df3fcd6655456c3468259d79762800c3c417147d"
121
121
  }
@@ -1,9 +1,16 @@
1
1
  import _ from "lodash/fp";
2
- import React from "react";
2
+ import React, { useState, useEffect } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { FormattedMessage } from "react-intl";
5
- import { Label, Icon, Dropdown, Dimmer, Loader } from "semantic-ui-react";
6
- import { accentInsensitivePathOrder } from "../services/sort";
5
+ import {
6
+ Label,
7
+ Icon,
8
+ Dropdown,
9
+ Dimmer,
10
+ Loader,
11
+ Input,
12
+ } from "semantic-ui-react";
13
+ import { accentInsensitivePathOrder, lowerDeburr } from "../services/sort";
7
14
  import FilterItem from "./FilterItem";
8
15
 
9
16
  const removePrefix = _.replace(/^.*\./, "");
@@ -17,55 +24,128 @@ export const FilterDropdown = ({
17
24
  options,
18
25
  removeFilter,
19
26
  toggleFilterValue,
20
- }) => (
21
- <Dropdown
22
- item
23
- floating
24
- scrolling
25
- icon={false}
26
- upward={false}
27
- trigger={
28
- <Label key={filter}>
29
- <FormattedMessage
30
- id={`filters.${filter}`}
31
- defaultMessage={removePrefix(filter)}
32
- />
33
- <Icon
34
- name="delete"
35
- onClick={(e) => {
36
- e.preventDefault();
37
- e.stopPropagation();
38
- removeFilter({ filter });
39
- }}
40
- />
41
- </Label>
42
- }
43
- onOpen={() => openFilter({ filter })}
44
- onClose={() => closeFilter({ filter })}
45
- open={!_.isEmpty(options)}
46
- >
47
- <Dimmer.Dimmable as={Dropdown.Menu} dimmed={loading}>
48
- {options &&
49
- _.flow(
50
- _.sortBy(accentInsensitivePathOrder("text")),
51
- _.map.convert({ cap: false })((option, i) => (
52
- <FilterItem
53
- key={i}
54
- filter={filter}
55
- option={option}
56
- toggleFilterValue={toggleFilterValue}
57
- active={_.includes(_.prop("value")(option))(activeValues)}
58
- />
59
- ))
60
- )(options)}
61
- {loading && (
62
- <Dimmer active inverted>
63
- <Loader size="tiny" />
64
- </Dimmer>
65
- )}
66
- </Dimmer.Dimmable>
67
- </Dropdown>
68
- );
27
+ }) => {
28
+ const [selected, setSelected] = useState();
29
+ const [query, setQuery] = useState();
30
+
31
+ useEffect(() => {
32
+ const activeOptions = _.filter((option) =>
33
+ _.includes(option.value)(activeValues)
34
+ )(options);
35
+
36
+ _.flow(
37
+ _.reduce((acc, option) => [...acc, option], []),
38
+ _.uniq,
39
+ setSelected
40
+ )(activeOptions);
41
+ }, [activeValues, options]);
42
+
43
+ const handleSearch = (e, { value }) => {
44
+ e.preventDefault();
45
+ setQuery(lowerDeburr(value));
46
+ };
47
+
48
+ const toogleSelectedOption = (selectedOption) => {
49
+ const newSelectedOptions = selected.find(
50
+ (s) => s.value === selectedOption.value
51
+ )
52
+ ? selected.filter((s) => s.value !== selectedOption.value)
53
+ : [...selected, selectedOption];
54
+ setSelected(newSelectedOptions);
55
+ toggleFilterValue({ filter, value: selectedOption.value });
56
+ };
57
+
58
+ const match = (name, query) => _.contains(query)(lowerDeburr(name));
59
+ const filterSearch = (all) =>
60
+ query ? _.filter((option) => match(option.text, query))(all) : all;
61
+
62
+ const filteredOptions = filterSearch(options);
63
+
64
+ return (
65
+ <Dropdown
66
+ item
67
+ floating
68
+ scrolling
69
+ icon={false}
70
+ upward={false}
71
+ trigger={
72
+ <Label key={filter}>
73
+ <FormattedMessage
74
+ id={`filters.${filter}`}
75
+ defaultMessage={removePrefix(filter)}
76
+ />
77
+ <Icon
78
+ name="delete"
79
+ onClick={(e) => {
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+ removeFilter({ filter });
83
+ }}
84
+ />
85
+ </Label>
86
+ }
87
+ onOpen={() => openFilter({ filter })}
88
+ onClose={() => closeFilter({ filter })}
89
+ open={!_.isEmpty(options)}
90
+ >
91
+ <Dimmer.Dimmable as={Dropdown.Menu} dimmed={loading}>
92
+ <>
93
+ {options && options.length >= 8 && (
94
+ <>
95
+ <Input
96
+ icon="search"
97
+ iconPosition="left"
98
+ className="search"
99
+ onKeyDown={(e) => {
100
+ if (e.key === " ") {
101
+ e.stopPropagation();
102
+ }
103
+ }}
104
+ onChange={handleSearch}
105
+ onClick={(e) => {
106
+ e.preventDefault();
107
+ e.stopPropagation();
108
+ }}
109
+ />
110
+ {selected &&
111
+ _.flow(
112
+ _.sortBy(accentInsensitivePathOrder("text")),
113
+ _.map.convert({ cap: false })((option, i) => (
114
+ <FilterItem
115
+ key={i}
116
+ filter={filter}
117
+ option={option}
118
+ toggleFilterValue={toogleSelectedOption}
119
+ active={_.includes(_.prop("value")(option))(activeValues)}
120
+ />
121
+ ))
122
+ )(selected)}
123
+ <Dropdown.Divider />
124
+ </>
125
+ )}
126
+ {filteredOptions &&
127
+ _.flow(
128
+ _.sortBy(accentInsensitivePathOrder("text")),
129
+ _.map.convert({ cap: false })((option, i) => (
130
+ <FilterItem
131
+ key={i}
132
+ filter={filter}
133
+ option={option}
134
+ toggleFilterValue={toggleFilterValue}
135
+ active={_.includes(_.prop("value")(option))(activeValues)}
136
+ />
137
+ ))
138
+ )(filteredOptions)}
139
+ </>
140
+ {loading && (
141
+ <Dimmer active inverted>
142
+ <Loader size="tiny" />
143
+ </Dimmer>
144
+ )}
145
+ </Dimmer.Dimmable>
146
+ </Dropdown>
147
+ );
148
+ };
69
149
 
70
150
  FilterDropdown.propTypes = {
71
151
  activeValues: PropTypes.array,
@@ -1,7 +1,9 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
- import { shallow } from "enzyme";
4
- import { Dropdown } from "semantic-ui-react";
3
+ import { render } from "@truedat/test/render";
4
+ import { waitFor, within, screen } from "@testing-library/react";
5
+ import userEvent from "@testing-library/user-event";
6
+
5
7
  import { FilterDropdown } from "../FilterDropdown";
6
8
 
7
9
  const options = _.map((value) => ({ value, text: value }))([
@@ -9,42 +11,172 @@ const options = _.map((value) => ({ value, text: value }))([
9
11
  "value2",
10
12
  ]);
11
13
 
14
+ const largeOptions = _.range(0, 8).map((n) => ({
15
+ value: `value${n}`,
16
+ text: `value${n}`,
17
+ }));
18
+
19
+ const props = {
20
+ openFilter: jest.fn(),
21
+ closeFilter: jest.fn(),
22
+ removeFilter: jest.fn(),
23
+ toggleFilterValue: jest.fn(),
24
+ filter: "foo",
25
+ options,
26
+ };
27
+
12
28
  describe("<FilterDropdown/>", () => {
13
29
  it("matches the latest snapshot", () => {
14
- const filter = "foo";
15
- const props = { filter, options };
16
- const wrapper = shallow(<FilterDropdown {...props} />);
17
- expect(wrapper).toMatchSnapshot();
18
- });
19
-
20
- it("dispatches openFilter, closeFilter and removeFilter", () => {
21
- const closeFilter = jest.fn();
22
- const openFilter = jest.fn();
23
- const removeFilter = jest.fn();
24
- const filter = "foo";
25
- const props = {
26
- closeFilter,
27
- filter,
28
- openFilter,
29
- options,
30
- removeFilter,
30
+ const { container } = render(<FilterDropdown {...props} />);
31
+ expect(container).toMatchSnapshot();
32
+ });
33
+
34
+ it("render search input if has more than eight options", () => {
35
+ const customProps = {
36
+ ...props,
37
+ options: largeOptions,
38
+ };
39
+ const { getByRole } = render(<FilterDropdown {...customProps} />);
40
+
41
+ expect(getByRole("textbox")).toBeInTheDocument();
42
+ });
43
+
44
+ it("no render search input if has more than eight options", () => {
45
+ const { queryByRole } = render(<FilterDropdown {...props} />);
46
+ expect(queryByRole("textbox")).toBeNull();
47
+ });
48
+
49
+ it("check option check it correctly", () => {
50
+ const customProps = {
51
+ ...props,
52
+ options: largeOptions,
53
+ };
54
+
55
+ const selectedOption = customProps.options[6].text;
56
+
57
+ const { getByRole, getAllByRole, rerender } = render(
58
+ <FilterDropdown {...customProps} />
59
+ );
60
+
61
+ expect(customProps.toggleFilterValue).toBeCalledTimes(0);
62
+ userEvent.click(
63
+ getByRole("option", { name: new RegExp(selectedOption, "i") })
64
+ );
65
+ expect(customProps.toggleFilterValue).toBeCalledWith({
66
+ filter: props.filter,
67
+ value: selectedOption,
68
+ });
69
+
70
+ const rerenderProps = {
71
+ ...customProps,
72
+ activeValues: [selectedOption],
73
+ };
74
+ rerender(<FilterDropdown {...rerenderProps} />);
75
+
76
+ expect(
77
+ within(getAllByRole("option")[0]).queryByText(selectedOption)
78
+ ).not.toBeNull();
79
+ });
80
+
81
+ it("type in filter input filter options", () => {
82
+ const customProps = {
83
+ ...props,
84
+ options: largeOptions,
31
85
  };
32
- const wrapper = shallow(<FilterDropdown {...props} />);
33
- expect(openFilter.mock.calls.length).toBe(0);
34
- expect(closeFilter.mock.calls.length).toBe(0);
35
- expect(removeFilter.mock.calls.length).toBe(0);
36
-
37
- wrapper.find(Dropdown).simulate("open");
38
- expect(openFilter.mock.calls.length).toBe(1);
39
-
40
- wrapper.find(Dropdown).simulate("close");
41
- expect(closeFilter.mock.calls.length).toBe(1);
42
-
43
- const e = { preventDefault: jest.fn(), stopPropagation: jest.fn() };
44
- wrapper
45
- .prop("trigger")
46
- .props["children"].find(_.has("props.onClick"))
47
- .props.onClick(e);
48
- expect(removeFilter.mock.calls.length).toBe(1);
86
+
87
+ const searchText = "3";
88
+
89
+ const { getByRole, queryByRole, getAllByRole } = render(
90
+ <FilterDropdown {...customProps} />
91
+ );
92
+ const input = getByRole("textbox");
93
+
94
+ expect(
95
+ getByRole("option", {
96
+ name: new RegExp(customProps.options[1].text, "i"),
97
+ })
98
+ ).toBeInTheDocument();
99
+
100
+ userEvent.type(input, searchText);
101
+
102
+ expect(
103
+ queryByRole("option", {
104
+ name: new RegExp(customProps.options[1].text, "i"),
105
+ })
106
+ ).toBeNull();
107
+
108
+ expect(
109
+ within(getByRole("option")).getByText(`value${searchText}`)
110
+ ).toBeInTheDocument();
111
+
112
+ expect(getAllByRole("option").length).toBe(1);
113
+ });
114
+
115
+ it("type in filter not hidde selected values", () => {
116
+ const selectedOption = largeOptions[6].text;
117
+
118
+ const customProps = {
119
+ ...props,
120
+ options: largeOptions,
121
+ activeValues: [selectedOption],
122
+ };
123
+
124
+ const searchText = "3";
125
+
126
+ const { getByRole, queryByRole, getAllByRole } = render(
127
+ <FilterDropdown {...customProps} />
128
+ );
129
+ const input = getByRole("textbox");
130
+
131
+ userEvent.type(input, searchText);
132
+
133
+ expect(
134
+ within(getAllByRole("option")[0]).getByText(selectedOption)
135
+ ).toBeInTheDocument();
136
+
137
+ expect(
138
+ within(getAllByRole("option")[1]).getByText(`value${searchText}`)
139
+ ).toBeInTheDocument();
140
+
141
+ expect(
142
+ queryByRole("option", {
143
+ name: new RegExp(customProps.options[1].text, "i"),
144
+ })
145
+ ).toBeNull();
146
+
147
+ expect(getAllByRole("option").length).toBe(2);
148
+ });
149
+
150
+ it("dispatches openFilter", () => {
151
+ const customProps = {
152
+ ...props,
153
+ options: [],
154
+ };
155
+
156
+ const { rerender } = render(<FilterDropdown {...customProps} />);
157
+
158
+ rerender(<FilterDropdown {...props} />);
159
+
160
+ waitFor(() => expect(props.openFilter).toBeCalledTimes(1));
161
+ });
162
+
163
+ it("dispatches closeFilter", () => {
164
+ const customProps = {
165
+ ...props,
166
+ options: [],
167
+ };
168
+
169
+ const { rerender } = render(<FilterDropdown {...props} />);
170
+ rerender(<FilterDropdown {...customProps} />);
171
+
172
+ waitFor(() => expect(props.closeFilter).toBeCalledTimes(1));
173
+ });
174
+
175
+ it("remove filter dispatches removeFilter", async () => {
176
+ const { container } = render(<FilterDropdown {...props} />);
177
+ userEvent.click(container.querySelector('[class="delete icon"]'));
178
+ await waitFor(() => {
179
+ expect(props.removeFilter).toBeCalledTimes(1);
180
+ });
49
181
  });
50
182
  });
@@ -1,67 +1,48 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<FilterDropdown/> matches the latest snapshot 1`] = `
4
- <Dropdown
5
- additionLabel="Add "
6
- additionPosition="top"
7
- closeOnBlur={true}
8
- closeOnEscape={true}
9
- deburr={false}
10
- floating={true}
11
- icon={false}
12
- item={true}
13
- minCharacters={1}
14
- noResultsMessage="No results found."
15
- onClose={[Function]}
16
- onOpen={[Function]}
17
- open={true}
18
- openOnFocus={true}
19
- renderLabel={[Function]}
20
- scrolling={true}
21
- searchInput="text"
22
- selectOnBlur={true}
23
- selectOnNavigation={true}
24
- trigger={
25
- <Label>
26
- <Memo(MemoizedFormattedMessage)
27
- defaultMessage="foo"
28
- id="filters.foo"
29
- />
30
- <Icon
31
- as="i"
32
- name="delete"
33
- onClick={[Function]}
34
- />
35
- </Label>
36
- }
37
- upward={false}
38
- wrapSelection={true}
39
- >
40
- <DimmerDimmable
41
- as={[Function]}
4
+ <div>
5
+ <div
6
+ aria-expanded="true"
7
+ class="ui active visible floating item scrolling dropdown"
8
+ role="listbox"
9
+ tabindex="0"
42
10
  >
43
- <FilterItem
44
- active={false}
45
- filter="foo"
46
- key="0"
47
- option={
48
- {
49
- "text": "value1",
50
- "value": "value1",
51
- }
52
- }
53
- />
54
- <FilterItem
55
- active={false}
56
- filter="foo"
57
- key="1"
58
- option={
59
- {
60
- "text": "value2",
61
- "value": "value2",
62
- }
63
- }
64
- />
65
- </DimmerDimmable>
66
- </Dropdown>
11
+ <div
12
+ class="ui label"
13
+ >
14
+ foo
15
+ <i
16
+ aria-hidden="true"
17
+ class="delete icon"
18
+ />
19
+ </div>
20
+ <div
21
+ class="menu transition dimmable visible"
22
+ >
23
+ <div
24
+ aria-checked="false"
25
+ class="item"
26
+ role="option"
27
+ >
28
+ <i
29
+ aria-hidden="true"
30
+ class="square outline icon"
31
+ />
32
+ value1
33
+ </div>
34
+ <div
35
+ aria-checked="false"
36
+ class="item"
37
+ role="option"
38
+ >
39
+ <i
40
+ aria-hidden="true"
41
+ class="square outline icon"
42
+ />
43
+ value2
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
67
48
  `;