cozy-ui 113.8.0 → 114.0.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/CHANGELOG.md +20 -0
- package/jest.config.js +1 -2
- package/package.json +6 -12
- package/react/AppSections/components/AppsSection.spec.jsx +10 -19
- package/react/AppSections/index.spec.jsx +98 -77
- package/react/ContactsList/ContactRow.spec.js +28 -53
- package/react/DateMonthPicker/index.spec.jsx +17 -45
- package/react/Field/index.spec.js +28 -5
- package/react/Figure/Figure.spec.jsx +9 -4
- package/react/Figure/__snapshots__/Figure.spec.jsx.snap +289 -225
- package/react/FileInput/index.jsx +1 -0
- package/react/FileInput/index.spec.jsx +16 -38
- package/react/Paywall/Paywall.spec.jsx +3 -5
- package/react/Popup/index.spec.jsx +90 -41
- package/react/QualificationIconStack/Readme.md +4 -1
- package/react/QualificationIconStack/index.jsx +28 -7
- package/react/QualificationModal/Readme.md +28 -0
- package/react/QualificationModal/index.jsx +94 -0
- package/react/QualificationModal/locales/en.json +5 -0
- package/react/QualificationModal/locales/fr.json +5 -0
- package/react/QualificationModal/locales/index.jsx +7 -0
- package/react/deprecated/ViewStack/example.jsx +1 -1
- package/react/hooks/useClientErrors.spec.jsx +16 -24
- package/react/index.js +2 -0
- package/react/providers/I18n/index.spec.jsx +13 -5
- package/react/providers/I18n/withLocales.spec.jsx +4 -4
- package/transpiled/react/FileInput/index.js +2 -1
- package/transpiled/react/QualificationIconStack/index.js +26 -6
- package/transpiled/react/QualificationModal/index.js +126 -0
- package/transpiled/react/QualificationModal/locales/index.js +14 -0
- package/transpiled/react/deprecated/ViewStack/example.js +1 -1
- package/transpiled/react/index.js +2 -0
- package/react/AppSections/__snapshots__/index.spec.jsx.snap +0 -1843
- package/react/AppSections/components/__snapshots__/AppsSection.spec.jsx.snap +0 -41
- package/react/ContactsList/__snapshots__/ContactRow.spec.js.snap +0 -69
- package/react/FileInput/__snapshots__/index.spec.jsx.snap +0 -86
- package/react/Input/__snapshots__/index.spec.jsx.snap +0 -11
- package/react/Input/index.spec.jsx +0 -12
- package/react/__snapshots__/examples.spec.jsx.snap +0 -3720
- package/react/deprecated/ActionMenu/__snapshots__/index.spec.jsx.snap +0 -157
- package/react/deprecated/ActionMenu/index.spec.jsx +0 -115
- package/react/deprecated/Alerter/__snapshots__/alerter.spec.js.snap +0 -88
- package/react/deprecated/Alerter/alerter.spec.js +0 -78
- package/react/deprecated/InfosCarrousel/index.spec.jsx +0 -71
- package/react/deprecated/Modal/index.spec.jsx +0 -70
- package/react/deprecated/ViewStack/index.spec.jsx +0 -64
- package/react/examples.spec.jsx +0 -67
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
1
2
|
import uniqueId from 'lodash/uniqueId'
|
|
2
3
|
import React from 'react'
|
|
3
4
|
|
|
@@ -20,66 +21,43 @@ describe('FileInput component', () => {
|
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
it('should render a file selector', () => {
|
|
23
|
-
|
|
24
|
+
render(
|
|
24
25
|
<FileInput onChange={onChangeSpy}>
|
|
25
26
|
<span>Click me</span>
|
|
26
27
|
</FileInput>
|
|
27
|
-
)
|
|
28
|
-
expect(component).toMatchSnapshot()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should render a default file selector', () => {
|
|
32
|
-
const component = shallow(
|
|
33
|
-
<FileInput hidden={false} onChange={onChangeSpy} />
|
|
34
|
-
).getElement()
|
|
35
|
-
expect(component).toMatchSnapshot()
|
|
36
|
-
})
|
|
28
|
+
)
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
const component = shallow(
|
|
40
|
-
<FileInput disabled onChange={onChangeSpy}>
|
|
41
|
-
<span>Click me</span>
|
|
42
|
-
</FileInput>
|
|
43
|
-
).getElement()
|
|
44
|
-
expect(component).toMatchSnapshot()
|
|
45
|
-
})
|
|
30
|
+
const button = screen.getByText('Click me')
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<FileInput accept="image/*" onChange={onChangeSpy}>
|
|
50
|
-
<span>Click me</span>
|
|
51
|
-
</FileInput>
|
|
52
|
-
).getElement()
|
|
53
|
-
expect(component).toMatchSnapshot()
|
|
32
|
+
expect(button).toBeInTheDocument()
|
|
33
|
+
expect(screen.getByTestId('file-input')).toBeInTheDocument()
|
|
54
34
|
})
|
|
55
35
|
|
|
56
36
|
it('should process selected file on change', () => {
|
|
57
37
|
const filelist = [pic1]
|
|
58
|
-
|
|
38
|
+
render(
|
|
59
39
|
<FileInput accept="image/*" onChange={onChangeSpy}>
|
|
60
40
|
<span>Click me</span>
|
|
61
41
|
</FileInput>
|
|
62
42
|
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
})
|
|
43
|
+
|
|
44
|
+
const input = screen.getByTestId('file-input')
|
|
45
|
+
fireEvent.change(input, { target: { files: filelist } })
|
|
46
|
+
|
|
68
47
|
expect(onChangeSpy).toHaveBeenCalledWith(pic1)
|
|
69
48
|
})
|
|
70
49
|
|
|
71
50
|
it('should process selected files on change if it is multiple', () => {
|
|
72
51
|
const filelist = [pic1, pic2]
|
|
73
|
-
|
|
52
|
+
render(
|
|
74
53
|
<FileInput accept="image/*" multiple onChange={onChangeSpy}>
|
|
75
54
|
<span>Click me</span>
|
|
76
55
|
</FileInput>
|
|
77
56
|
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
})
|
|
57
|
+
|
|
58
|
+
const input = screen.getByTestId('file-input')
|
|
59
|
+
fireEvent.change(input, { target: { files: filelist } })
|
|
60
|
+
|
|
83
61
|
expect(onChangeSpy).toHaveBeenCalledWith([pic1, pic2])
|
|
84
62
|
})
|
|
85
63
|
})
|
|
@@ -42,15 +42,13 @@ describe('Paywall', () => {
|
|
|
42
42
|
useInstanceInfo.mockReturnValue({
|
|
43
43
|
context: {
|
|
44
44
|
data: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
manager_url: 'http://mycozy.cloud'
|
|
48
|
-
}
|
|
45
|
+
enable_premium_links: enablePremiumLinks,
|
|
46
|
+
manager_url: 'http://mycozy.cloud'
|
|
49
47
|
}
|
|
50
48
|
},
|
|
51
49
|
instance: {
|
|
52
50
|
data: {
|
|
53
|
-
|
|
51
|
+
uuid: hasUuid ? '123' : null
|
|
54
52
|
}
|
|
55
53
|
},
|
|
56
54
|
isLoaded: true
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { render, fireEvent } from '@testing-library/react'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
|
|
4
4
|
import { isMobileApp } from 'cozy-device-helper'
|
|
@@ -17,35 +17,56 @@ const props = {
|
|
|
17
17
|
height: '200'
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.source = options.source ||
|
|
20
|
+
class MessageEventMock extends Event {
|
|
21
|
+
constructor(options = {}, mock) {
|
|
22
|
+
super('message', options)
|
|
23
|
+
this.source = options.source || mock
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class LoadStartEventMock extends Event {
|
|
28
|
+
constructor(url) {
|
|
29
|
+
super('loadstart')
|
|
30
|
+
this.url = url
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class ExitEventMock extends Event {
|
|
35
|
+
constructor() {
|
|
36
|
+
super('exit')
|
|
24
37
|
}
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
describe('Popup', () => {
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
const setup = ({ mockAddEventListener = true }) => {
|
|
42
|
+
const popupMock = new EventTarget()
|
|
43
|
+
|
|
44
|
+
isMobileApp.mockReturnValue(false)
|
|
30
45
|
|
|
31
46
|
jest.spyOn(global, 'open').mockReturnValue(popupMock)
|
|
32
47
|
jest.spyOn(global, 'addEventListener')
|
|
33
|
-
})
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})
|
|
49
|
+
if (mockAddEventListener) {
|
|
50
|
+
popupMock.addEventListener = jest.fn()
|
|
51
|
+
}
|
|
39
52
|
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
isMobileApp.mockReturnValue(false)
|
|
42
|
-
popupMock.addEventListener = jest.fn()
|
|
43
53
|
popupMock.close = jest.fn()
|
|
44
54
|
popupMock.focus = jest.fn()
|
|
45
55
|
popupMock.closed = false
|
|
46
56
|
props.onClose = jest.fn()
|
|
47
57
|
props.onMessage = jest.fn()
|
|
48
58
|
props.onMobileUrlChange = jest.fn()
|
|
59
|
+
|
|
60
|
+
return popupMock
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
beforeAll(() => {
|
|
64
|
+
jest.useFakeTimers()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
afterAll(() => {
|
|
68
|
+
global.open.mockRestore()
|
|
69
|
+
global.addEventListener.mockRestore()
|
|
49
70
|
})
|
|
50
71
|
|
|
51
72
|
afterEach(() => {
|
|
@@ -54,7 +75,10 @@ describe('Popup', () => {
|
|
|
54
75
|
})
|
|
55
76
|
|
|
56
77
|
it('should open new window', () => {
|
|
57
|
-
|
|
78
|
+
const popupMock = setup({})
|
|
79
|
+
|
|
80
|
+
render(<Popup {...props} />)
|
|
81
|
+
|
|
58
82
|
expect(global.open).toHaveBeenCalledWith(
|
|
59
83
|
props.initialUrl,
|
|
60
84
|
props.title,
|
|
@@ -64,66 +88,91 @@ describe('Popup', () => {
|
|
|
64
88
|
})
|
|
65
89
|
|
|
66
90
|
it('should subscribe to message events', () => {
|
|
67
|
-
|
|
91
|
+
setup({})
|
|
92
|
+
|
|
93
|
+
render(<Popup {...props} />)
|
|
94
|
+
|
|
68
95
|
expect(global.addEventListener).toHaveBeenCalledWith(
|
|
69
96
|
'message',
|
|
70
|
-
|
|
97
|
+
expect.any(Function)
|
|
71
98
|
)
|
|
72
99
|
})
|
|
73
100
|
|
|
74
101
|
it('should subcribe to mobile events', () => {
|
|
102
|
+
const popupMock = setup({})
|
|
75
103
|
isMobileApp.mockReturnValue(true)
|
|
76
|
-
|
|
104
|
+
|
|
105
|
+
render(<Popup {...props} />)
|
|
106
|
+
|
|
77
107
|
expect(popupMock.addEventListener).toHaveBeenCalledWith(
|
|
78
108
|
'loadstart',
|
|
79
|
-
|
|
109
|
+
expect.any(Function)
|
|
80
110
|
)
|
|
81
111
|
expect(popupMock.addEventListener).toHaveBeenCalledWith(
|
|
82
112
|
'exit',
|
|
83
|
-
|
|
113
|
+
expect.any(Function)
|
|
84
114
|
)
|
|
85
115
|
})
|
|
86
116
|
|
|
87
|
-
describe('monitorClosing', () => {
|
|
88
|
-
it('should detect closing', () => {
|
|
89
|
-
const wrapper = shallow(<Popup {...props} />)
|
|
90
|
-
jest.spyOn(wrapper.instance(), 'handleClose')
|
|
91
|
-
popupMock.closed = true
|
|
92
|
-
jest.runAllTimers()
|
|
93
|
-
expect(wrapper.instance().handleClose).toHaveBeenCalled()
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
117
|
describe('handleClose', () => {
|
|
98
118
|
it('should call onClose', () => {
|
|
99
|
-
const
|
|
100
|
-
|
|
119
|
+
const popupMock = setup({
|
|
120
|
+
mockAddEventListener: false
|
|
121
|
+
})
|
|
122
|
+
isMobileApp.mockReturnValue(true)
|
|
123
|
+
|
|
124
|
+
render(<Popup {...props} />)
|
|
125
|
+
|
|
126
|
+
const messageEvent = new ExitEventMock()
|
|
127
|
+
fireEvent(popupMock, messageEvent)
|
|
128
|
+
|
|
101
129
|
expect(props.onClose).toHaveBeenCalled()
|
|
102
130
|
})
|
|
103
131
|
})
|
|
104
132
|
|
|
105
133
|
describe('handleMessage', () => {
|
|
106
134
|
it('should call onMessage', () => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
const popupMock = setup({
|
|
136
|
+
mockAddEventListener: false
|
|
137
|
+
})
|
|
138
|
+
isMobileApp.mockReturnValue(true)
|
|
139
|
+
|
|
140
|
+
render(<Popup {...props} />)
|
|
141
|
+
|
|
142
|
+
const messageEvent = new MessageEventMock({}, popupMock)
|
|
143
|
+
fireEvent(window, messageEvent)
|
|
144
|
+
|
|
110
145
|
expect(props.onMessage).toHaveBeenCalledWith(messageEvent)
|
|
111
146
|
})
|
|
112
147
|
|
|
113
148
|
it('should ignore messageEvent from another window ', () => {
|
|
114
|
-
|
|
149
|
+
setup({
|
|
150
|
+
mockAddEventListener: false
|
|
151
|
+
})
|
|
152
|
+
isMobileApp.mockReturnValue(true)
|
|
153
|
+
|
|
154
|
+
render(<Popup {...props} />)
|
|
155
|
+
|
|
115
156
|
const messageEvent = new MessageEventMock({ source: {} })
|
|
116
|
-
|
|
157
|
+
fireEvent(window, messageEvent)
|
|
158
|
+
|
|
117
159
|
expect(props.onMessage).not.toHaveBeenCalled()
|
|
118
160
|
})
|
|
119
161
|
})
|
|
120
162
|
|
|
121
163
|
describe('handleLoadStart', () => {
|
|
122
164
|
it('should call onMobileUrlChange', () => {
|
|
123
|
-
const
|
|
165
|
+
const popupMock = setup({
|
|
166
|
+
mockAddEventListener: false
|
|
167
|
+
})
|
|
168
|
+
isMobileApp.mockReturnValue(true)
|
|
169
|
+
|
|
170
|
+
render(<Popup {...props} />)
|
|
171
|
+
|
|
124
172
|
const url = 'https://cozy.io'
|
|
125
|
-
const
|
|
126
|
-
|
|
173
|
+
const messageEvent = new LoadStartEventMock(url)
|
|
174
|
+
fireEvent(popupMock, messageEvent)
|
|
175
|
+
|
|
127
176
|
expect(props.onMobileUrlChange).toHaveBeenCalledWith(expect.any(URL))
|
|
128
177
|
expect(props.onMobileUrlChange).toHaveBeenCalledWith(new URL(url))
|
|
129
178
|
})
|
|
@@ -4,8 +4,11 @@ import QualificationIconStack from 'cozy-ui/transpiled/react/QualificationIconSt
|
|
|
4
4
|
;
|
|
5
5
|
|
|
6
6
|
<>
|
|
7
|
+
<QualificationIconStack className="u-mr-1" />
|
|
7
8
|
<QualificationIconStack className="u-mr-1" qualification="isp_invoice" />
|
|
8
9
|
<QualificationIconStack className="u-mr-1" qualification="phone_invoice" />
|
|
9
|
-
<QualificationIconStack qualification="family_record_book" />
|
|
10
|
+
<QualificationIconStack className="u-mr-1" qualification="family_record_book" />
|
|
11
|
+
<QualificationIconStack className="u-mr-1" theme="identity" />
|
|
12
|
+
<QualificationIconStack theme="transport" />
|
|
10
13
|
</>
|
|
11
14
|
```
|
|
@@ -10,7 +10,9 @@ import BankCheckIcon from '../Icons/BankCheck'
|
|
|
10
10
|
import BenefitIcon from '../Icons/Benefit'
|
|
11
11
|
import BillIcon from '../Icons/Bill'
|
|
12
12
|
import CarIcon from '../Icons/Car'
|
|
13
|
+
import ChessIcon from '../Icons/Chess'
|
|
13
14
|
import ChildIcon from '../Icons/Child'
|
|
15
|
+
import DotsIcon from '../Icons/Dots'
|
|
14
16
|
import EmailIcon from '../Icons/Email'
|
|
15
17
|
import EuroIcon from '../Icons/Euro'
|
|
16
18
|
import ExchangeIcon from '../Icons/Exchange'
|
|
@@ -51,13 +53,15 @@ function FileDuotoneWhite(props) {
|
|
|
51
53
|
)
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
const
|
|
56
|
+
const IconByLabel = {
|
|
55
57
|
'bank-check': BankCheckIcon,
|
|
56
58
|
bank: BankIcon,
|
|
57
59
|
benefit: BenefitIcon,
|
|
58
60
|
bill: BillIcon,
|
|
59
61
|
car: CarIcon,
|
|
62
|
+
chess: ChessIcon,
|
|
60
63
|
child: ChildIcon,
|
|
64
|
+
dots: DotsIcon,
|
|
61
65
|
email: EmailIcon,
|
|
62
66
|
euro: EuroIcon,
|
|
63
67
|
exchange: ExchangeIcon,
|
|
@@ -84,8 +88,25 @@ const qualificationIcon = {
|
|
|
84
88
|
work: WorkIcon
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
const
|
|
88
|
-
|
|
91
|
+
const themeIconByLabel = {
|
|
92
|
+
identity: 'people',
|
|
93
|
+
family: 'team',
|
|
94
|
+
work_study: 'work',
|
|
95
|
+
health: 'heart',
|
|
96
|
+
home: 'home',
|
|
97
|
+
transport: 'car',
|
|
98
|
+
activity: 'chess',
|
|
99
|
+
finance: 'bank',
|
|
100
|
+
invoice: 'bill',
|
|
101
|
+
others: 'dots'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const QualificationIconStack = ({ theme, qualification, ...props }) => {
|
|
105
|
+
const ForegroundIcon = qualification
|
|
106
|
+
? IconByLabel[getIconByLabel(qualification)]
|
|
107
|
+
: theme
|
|
108
|
+
? IconByLabel[themeIconByLabel[theme]]
|
|
109
|
+
: null
|
|
89
110
|
|
|
90
111
|
return (
|
|
91
112
|
<IconStack
|
|
@@ -96,16 +117,16 @@ const QualificationIconStack = ({ qualification, ...props }) => {
|
|
|
96
117
|
<Icon icon={FileDuotoneIcon} color="#E049BF" size={32} />
|
|
97
118
|
</>
|
|
98
119
|
}
|
|
99
|
-
foregroundIcon={
|
|
100
|
-
<Icon icon={QualificationIcon} color="#E049BF" size={16} />
|
|
101
|
-
}
|
|
120
|
+
foregroundIcon={<Icon icon={ForegroundIcon} color="#E049BF" size={16} />}
|
|
102
121
|
/>
|
|
103
122
|
)
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
QualificationIconStack.propTypes = {
|
|
107
126
|
/** The name of the qualification (isp\_invoice, family\_record\_book, etc.) */
|
|
108
|
-
qualification: PropTypes.string
|
|
127
|
+
qualification: PropTypes.string,
|
|
128
|
+
/** The name of the qualification theme (indentity, family, etc.) */
|
|
129
|
+
theme: PropTypes.string
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
export default QualificationIconStack
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
```jsx
|
|
2
|
+
import DemoProvider from 'cozy-ui/docs/components/DemoProvider'
|
|
3
|
+
import QualificationModal from 'cozy-ui/transpiled/react/QualificationModal'
|
|
4
|
+
import Typography from 'cozy-ui/transpiled/react/Typography'
|
|
5
|
+
import CloudIcon from 'cozy-ui/transpiled/react/Icons/Cloud'
|
|
6
|
+
import FormControlLabel from 'cozy-ui/transpiled/react/FormControlLabel'
|
|
7
|
+
import RadioGroup from 'cozy-ui/transpiled/react/RadioGroup'
|
|
8
|
+
import Radio from 'cozy-ui/transpiled/react/Radios'
|
|
9
|
+
import FormControl from 'cozy-ui/transpiled/react/FormControl'
|
|
10
|
+
import Button from 'cozy-ui/transpiled/react/Buttons'
|
|
11
|
+
|
|
12
|
+
initialState = { show: isTesting() ? true : false }
|
|
13
|
+
|
|
14
|
+
const show = () => setState({show: true})
|
|
15
|
+
const hide = () => setState({show: false})
|
|
16
|
+
|
|
17
|
+
;
|
|
18
|
+
|
|
19
|
+
<DemoProvider client={{ collection: () => ({ updateMetadataAttribute: () => {}}) }}>
|
|
20
|
+
<Button label={state.show ? "Close modal" : "Open modal"} variant="ghost" onClick={show} />
|
|
21
|
+
{state.show &&
|
|
22
|
+
<QualificationModal
|
|
23
|
+
file={{ name: "toto.txt", metadata: { qualification: { label: 'isp_invoice' } } }}
|
|
24
|
+
onClose={hide}
|
|
25
|
+
/>
|
|
26
|
+
}
|
|
27
|
+
</DemoProvider>
|
|
28
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React, { useMemo } from 'react'
|
|
3
|
+
|
|
4
|
+
import { useClient } from 'cozy-client'
|
|
5
|
+
import { themesList } from 'cozy-client/dist/models/document/documentTypeData'
|
|
6
|
+
import { isQualificationNote } from 'cozy-client/dist/models/document/documentTypeDataHelpers'
|
|
7
|
+
import { getBoundT } from 'cozy-client/dist/models/document/locales'
|
|
8
|
+
import { getQualification } from 'cozy-client/dist/models/document/qualification'
|
|
9
|
+
|
|
10
|
+
import { locales } from './locales'
|
|
11
|
+
import Icon from '../Icon'
|
|
12
|
+
import FileTypeNoteIcon from '../Icons/FileTypeNote'
|
|
13
|
+
import NestedSelectResponsive from '../NestedSelect/NestedSelectResponsive'
|
|
14
|
+
import QualificationIconStack from '../QualificationIconStack'
|
|
15
|
+
import { useI18n, useExtendI18n } from '../providers/I18n'
|
|
16
|
+
|
|
17
|
+
const makeOptions = lang => {
|
|
18
|
+
const qualifT = getBoundT(lang)
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
children: [
|
|
22
|
+
{
|
|
23
|
+
id: 'none',
|
|
24
|
+
title: qualifT('Scan.themes.none'),
|
|
25
|
+
icon: <QualificationIconStack />
|
|
26
|
+
},
|
|
27
|
+
...themesList.map(theme => ({
|
|
28
|
+
id: theme.id,
|
|
29
|
+
title: qualifT(`Scan.themes.${theme.label}`),
|
|
30
|
+
icon: <QualificationIconStack theme={theme.label} />,
|
|
31
|
+
children: theme.items.map(item => ({
|
|
32
|
+
id: item.label,
|
|
33
|
+
item,
|
|
34
|
+
title: qualifT(`Scan.items.${item.label}`),
|
|
35
|
+
icon: isQualificationNote(item) ? (
|
|
36
|
+
<Icon icon={FileTypeNoteIcon} size={64} />
|
|
37
|
+
) : (
|
|
38
|
+
<QualificationIconStack qualification={item.label} />
|
|
39
|
+
)
|
|
40
|
+
}))
|
|
41
|
+
}))
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const QualificationModal = ({ file, title, onClose }) => {
|
|
47
|
+
useExtendI18n(locales)
|
|
48
|
+
const client = useClient()
|
|
49
|
+
const { t, lang } = useI18n()
|
|
50
|
+
|
|
51
|
+
const qualificationLabel = getQualification(file)?.label
|
|
52
|
+
const options = useMemo(() => makeOptions(lang), [lang])
|
|
53
|
+
|
|
54
|
+
const isSelected = ({ id, item }) => {
|
|
55
|
+
return qualificationLabel
|
|
56
|
+
? qualificationLabel === item?.label
|
|
57
|
+
: id === 'none'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const handleClick = async ({ id, item }) => {
|
|
61
|
+
const fileCollection = client.collection('io.cozy.files')
|
|
62
|
+
const removeQualification = qualificationLabel && id === 'none'
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
In the case where we remove the qualification it's necessary to define the attribute to `null` and not `undefined`, with `undefined` the stack does not return the attribute and today the Redux store is not updated for a missing attribute.
|
|
66
|
+
As a result, the UI is not updated and continues to display the qualification on the document, even though it has been deleted in CouchDB.
|
|
67
|
+
*/
|
|
68
|
+
await fileCollection.updateMetadataAttribute(file._id, {
|
|
69
|
+
qualification: removeQualification ? null : item
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
onClose()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<NestedSelectResponsive
|
|
77
|
+
title={title || t('QualificationModal.title')}
|
|
78
|
+
options={options}
|
|
79
|
+
noDivider
|
|
80
|
+
document={file}
|
|
81
|
+
isSelected={isSelected}
|
|
82
|
+
onSelect={handleClick}
|
|
83
|
+
onClose={onClose}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
QualificationModal.propTypes = {
|
|
89
|
+
file: PropTypes.object,
|
|
90
|
+
title: PropTypes.string,
|
|
91
|
+
onClose: PropTypes.func
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default QualificationModal
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { screen, render, act } from '@testing-library/react'
|
|
2
|
+
import { renderHook } from '@testing-library/react-hooks'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
|
|
5
|
-
import { CozyProvider } from 'cozy-client'
|
|
6
5
|
import { FetchError } from 'cozy-stack-client'
|
|
7
6
|
|
|
8
7
|
import useClientErrors from './useClientErrors'
|
|
8
|
+
import DemoProvider from '../providers/DemoProvider'
|
|
9
9
|
|
|
10
10
|
function createCozyClient() {
|
|
11
11
|
return {
|
|
@@ -14,9 +14,15 @@ function createCozyClient() {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
jest.mock('../deprecated/QuotaAlert', () => ({ onClose }) => (
|
|
18
|
+
<>
|
|
19
|
+
QuotaAlert <button onClick={onClose}>Dismiss</button>
|
|
20
|
+
</>
|
|
21
|
+
))
|
|
22
|
+
|
|
17
23
|
function createWrapper(client = createCozyClient()) {
|
|
18
24
|
function Wrapper({ children }) {
|
|
19
|
-
return <
|
|
25
|
+
return <DemoProvider client={client}>{children}</DemoProvider>
|
|
20
26
|
}
|
|
21
27
|
return Wrapper
|
|
22
28
|
}
|
|
@@ -27,7 +33,7 @@ function renderWrappedHook(client) {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
function wrappedShallow(tree, client) {
|
|
30
|
-
return
|
|
36
|
+
return render(tree, { wrapper: createWrapper(client) })
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
describe('useClientErrors', () => {
|
|
@@ -47,14 +53,12 @@ describe('useClientErrors', () => {
|
|
|
47
53
|
it('displays nothing by default', () => {
|
|
48
54
|
const { result } = renderWrappedHook()
|
|
49
55
|
const { ClientErrors } = result.current
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
wrappedShallow(<ClientErrors />)
|
|
57
|
+
|
|
58
|
+
expect(screen.queryByText('QuotaAlert')).not.toBeInTheDocument()
|
|
52
59
|
})
|
|
53
60
|
|
|
54
61
|
describe('for quota errors', () => {
|
|
55
|
-
const findQuotaAlert = node => {
|
|
56
|
-
return node.at(0).dive()
|
|
57
|
-
}
|
|
58
62
|
const setup = async () => {
|
|
59
63
|
const client = createCozyClient()
|
|
60
64
|
const response = new Response(null, {
|
|
@@ -81,21 +85,9 @@ describe('useClientErrors', () => {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
it('displays a a QuotaAlert', async () => {
|
|
84
|
-
|
|
85
|
-
expect(node).toHaveLength(1)
|
|
86
|
-
expect(findQuotaAlert(node).type().name).toEqual('QuotaAlert')
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('can be dismissed', async () => {
|
|
90
|
-
const { node, result, client } = await setup()
|
|
91
|
-
const quotaAlert = findQuotaAlert(node)
|
|
92
|
-
const onClose = quotaAlert.props().onClose
|
|
93
|
-
act(() => onClose())
|
|
88
|
+
await setup()
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
const { ClientErrors } = result.current
|
|
97
|
-
const updatedNode = wrappedShallow(<ClientErrors />, client)
|
|
98
|
-
expect(updatedNode.at(0).length).toBe(0)
|
|
90
|
+
expect(screen.queryByText('QuotaAlert')).toBeInTheDocument()
|
|
99
91
|
})
|
|
100
92
|
})
|
|
101
93
|
})
|
package/react/index.js
CHANGED
|
@@ -125,6 +125,8 @@ export { default as Thumbnail } from './Thumbnail'
|
|
|
125
125
|
export { default as ButtonBase } from './ButtonBase'
|
|
126
126
|
export { default as QualificationGrid } from './QualificationGrid'
|
|
127
127
|
export { default as QualificationItem } from './QualificationItem'
|
|
128
|
+
export { default as QualificationIconStack } from './QualificationIconStack'
|
|
129
|
+
export { default as QualificationModal } from './QualificationModal'
|
|
128
130
|
export { default as Timeline } from './Timeline'
|
|
129
131
|
export { default as TimelineConnector } from './TimelineConnector'
|
|
130
132
|
export { default as TimelineContent } from './TimelineContent'
|