cozy-bar 1.17.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 (181) hide show
  1. package/.eslintrc.json +7 -0
  2. package/.github/auto-merge.yml +7 -0
  3. package/.nvmrc +1 -0
  4. package/.transifexrc.tpl +4 -0
  5. package/.travis.yml +28 -0
  6. package/.tx/config +8 -0
  7. package/CHANGELOG.md +493 -0
  8. package/CODEOWNERS +2 -0
  9. package/CONTRIBUTING.md +135 -0
  10. package/LICENSE +21 -0
  11. package/README.md +172 -0
  12. package/babel.config.js +3 -0
  13. package/config/aliases/globalReact.js +1 -0
  14. package/config/aliases/globalReactDOM.js +1 -0
  15. package/config/webpack.config.analyzer.js +10 -0
  16. package/config/webpack.config.base.js +61 -0
  17. package/config/webpack.config.dev.js +16 -0
  18. package/config/webpack.config.extract.js +84 -0
  19. package/config/webpack.config.inline-styles.js +82 -0
  20. package/config/webpack.config.jsx.js +14 -0
  21. package/config/webpack.config.prod.js +18 -0
  22. package/config/webpack.js +31 -0
  23. package/config/webpack.vars.js +11 -0
  24. package/dist/cozy-bar.css +9445 -0
  25. package/dist/cozy-bar.css.map +1 -0
  26. package/dist/cozy-bar.js +122668 -0
  27. package/dist/cozy-bar.js.map +1 -0
  28. package/dist/cozy-bar.min.css +9 -0
  29. package/dist/cozy-bar.min.css.map +1 -0
  30. package/dist/cozy-bar.min.js +57 -0
  31. package/dist/cozy-bar.min.js.map +1 -0
  32. package/dist/cozy-bar.mobile.js +123719 -0
  33. package/dist/cozy-bar.mobile.js.map +1 -0
  34. package/dist/cozy-bar.mobile.min.js +57 -0
  35. package/dist/cozy-bar.mobile.min.js.map +1 -0
  36. package/docs/dev.md +20 -0
  37. package/examples/Procfile +3 -0
  38. package/examples/icon.png +0 -0
  39. package/examples/index.html +7 -0
  40. package/examples/index.jsx +122 -0
  41. package/examples/logs.js +3 -0
  42. package/package.json +163 -0
  43. package/postcss.config.js +23 -0
  44. package/public/fonts/Lato-Bold.woff2 +0 -0
  45. package/public/fonts/Lato-Regular.woff2 +0 -0
  46. package/public/fonts.css +13 -0
  47. package/public/icon-type-folder-32.png +0 -0
  48. package/public/index.html +63 -0
  49. package/renovate.json +5 -0
  50. package/src/assets/icons/16/icon-claudy.svg +5 -0
  51. package/src/assets/icons/16/icon-cozy-16.svg +1 -0
  52. package/src/assets/icons/16/icon-cube-16.svg +6 -0
  53. package/src/assets/icons/16/icon-logout-16.svg +3 -0
  54. package/src/assets/icons/16/icon-magnifier-16.svg +6 -0
  55. package/src/assets/icons/16/icon-people-16.svg +3 -0
  56. package/src/assets/icons/16/icon-phone-16.svg +3 -0
  57. package/src/assets/icons/16/icon-question-mark-16.svg +3 -0
  58. package/src/assets/icons/16/icon-storage-16.svg +3 -0
  59. package/src/assets/icons/24/icon-arrow-left.svg +3 -0
  60. package/src/assets/icons/32/icon-claudy.svg +1 -0
  61. package/src/assets/icons/apps/icon-collect.svg +25 -0
  62. package/src/assets/icons/apps/icon-drive.svg +17 -0
  63. package/src/assets/icons/apps/icon-market-soon.svg +25 -0
  64. package/src/assets/icons/apps/icon-photos.svg +19 -0
  65. package/src/assets/icons/apps/icon-soon.svg +21 -0
  66. package/src/assets/icons/apps/icon-store.svg +19 -0
  67. package/src/assets/icons/claudyActions/icon-bills.svg +6 -0
  68. package/src/assets/icons/claudyActions/icon-laptop.svg +7 -0
  69. package/src/assets/icons/claudyActions/icon-phone.svg +8 -0
  70. package/src/assets/icons/claudyActions/icon-question-mark.svg +6 -0
  71. package/src/assets/icons/comingsoon/icon-bank.svg +12 -0
  72. package/src/assets/icons/comingsoon/icon-sante.svg +12 -0
  73. package/src/assets/icons/comingsoon/icon-store.svg +6 -0
  74. package/src/assets/icons/icon-cozy.svg +3 -0
  75. package/src/assets/icons/icon-shield.svg +3 -0
  76. package/src/assets/icons/spinner.svg +4 -0
  77. package/src/assets/sprites/icon-apps.svg +1 -0
  78. package/src/assets/sprites/icon-cozy-home.svg +16 -0
  79. package/src/components/Apps/AppItem.jsx +117 -0
  80. package/src/components/Apps/AppItemPlaceholder.jsx +12 -0
  81. package/src/components/Apps/AppNavButtons.jsx +88 -0
  82. package/src/components/Apps/AppsContent.jsx +89 -0
  83. package/src/components/Apps/ButtonCozyHome.jsx +30 -0
  84. package/src/components/Apps/ButtonCozyHome.spec.jsx +53 -0
  85. package/src/components/Apps/IconCozyHome.jsx +24 -0
  86. package/src/components/Apps/index.jsx +81 -0
  87. package/src/components/Banner.jsx +41 -0
  88. package/src/components/Bar.jsx +293 -0
  89. package/src/components/Bar.spec.jsx +133 -0
  90. package/src/components/Claudy.jsx +81 -0
  91. package/src/components/Drawer.jsx +227 -0
  92. package/src/components/Drawer.spec.jsx +98 -0
  93. package/src/components/SearchBar.jsx +358 -0
  94. package/src/components/Settings/SettingsContent.jsx +145 -0
  95. package/src/components/Settings/StorageData.jsx +29 -0
  96. package/src/components/Settings/helper.js +8 -0
  97. package/src/components/Settings/index.jsx +218 -0
  98. package/src/components/SupportModal.jsx +59 -0
  99. package/src/components/__snapshots__/Bar.spec.jsx.snap +302 -0
  100. package/src/config/claudyActions.yaml +14 -0
  101. package/src/config/persistWhitelist.yaml +2 -0
  102. package/src/dom.js +80 -0
  103. package/src/index.jsx +235 -0
  104. package/src/index.spec.jsx +34 -0
  105. package/src/lib/api/helpers.js +13 -0
  106. package/src/lib/api/index.jsx +145 -0
  107. package/src/lib/exceptions.js +89 -0
  108. package/src/lib/expiringMemoize.js +13 -0
  109. package/src/lib/icon.js +77 -0
  110. package/src/lib/importIcons.js +14 -0
  111. package/src/lib/intents.js +16 -0
  112. package/src/lib/logger.js +6 -0
  113. package/src/lib/middlewares/appsI18n.js +57 -0
  114. package/src/lib/realtime.js +43 -0
  115. package/src/lib/reducers/apps.js +175 -0
  116. package/src/lib/reducers/apps.spec.js +59 -0
  117. package/src/lib/reducers/content.js +50 -0
  118. package/src/lib/reducers/context.js +83 -0
  119. package/src/lib/reducers/index.js +73 -0
  120. package/src/lib/reducers/locale.js +22 -0
  121. package/src/lib/reducers/settings.js +111 -0
  122. package/src/lib/reducers/theme.js +48 -0
  123. package/src/lib/reducers/unserializable.js +26 -0
  124. package/src/lib/stack-client.js +401 -0
  125. package/src/lib/stack.js +79 -0
  126. package/src/lib/store/index.js +54 -0
  127. package/src/locales/de.json +57 -0
  128. package/src/locales/en.json +57 -0
  129. package/src/locales/es.json +57 -0
  130. package/src/locales/fr.json +57 -0
  131. package/src/locales/it.json +57 -0
  132. package/src/locales/ja.json +57 -0
  133. package/src/locales/nl_NL.json +57 -0
  134. package/src/locales/pl.json +57 -0
  135. package/src/locales/ru.json +57 -0
  136. package/src/locales/sq.json +57 -0
  137. package/src/locales/zh_CN.json +57 -0
  138. package/src/proptypes/index.js +10 -0
  139. package/src/queries/index.js +16 -0
  140. package/src/styles/apps.css +248 -0
  141. package/src/styles/banner.css +64 -0
  142. package/src/styles/bar.css +106 -0
  143. package/src/styles/base.css +41 -0
  144. package/src/styles/claudy.css +99 -0
  145. package/src/styles/drawer.css +126 -0
  146. package/src/styles/index.styl +33 -0
  147. package/src/styles/indicators.css +58 -0
  148. package/src/styles/nav.css +81 -0
  149. package/src/styles/navigation_item.css +39 -0
  150. package/src/styles/searchbar.css +158 -0
  151. package/src/styles/settings.css +44 -0
  152. package/src/styles/storage.css +22 -0
  153. package/src/styles/supportModal.css +20 -0
  154. package/src/styles/theme.styl +25 -0
  155. package/test/__mocks__/fileMock.js +3 -0
  156. package/test/__snapshots__/index.spec.js.snap +41 -0
  157. package/test/components/AppItem.spec.jsx +113 -0
  158. package/test/components/AppsContent.spec.jsx +116 -0
  159. package/test/components/Settings/helper.spec.js +23 -0
  160. package/test/components/__snapshots__/AppsContent.spec.jsx.snap +90 -0
  161. package/test/index.spec.js +24 -0
  162. package/test/jestLib/I18n.js +15 -0
  163. package/test/jestLib/setup.js +14 -0
  164. package/test/lib/__snapshots__/api.spec.jsx.snap +67 -0
  165. package/test/lib/__snapshots__/stack.spec.js.snap +3 -0
  166. package/test/lib/api.spec.jsx +142 -0
  167. package/test/lib/mockStackClient.js +7 -0
  168. package/test/lib/stack-client/stack-client.appiconprops.spec.js +65 -0
  169. package/test/lib/stack-client/stack-client.compare.spec.js +20 -0
  170. package/test/lib/stack-client/stack-client.cozyfetchjson.spec.js +46 -0
  171. package/test/lib/stack-client/stack-client.cozyurl.spec.js +29 -0
  172. package/test/lib/stack-client/stack-client.getapp.spec.js +72 -0
  173. package/test/lib/stack-client/stack-client.getapps.spec.js +72 -0
  174. package/test/lib/stack-client/stack-client.getcontext.spec.js +96 -0
  175. package/test/lib/stack-client/stack-client.getstoragedata.spec.js +85 -0
  176. package/test/lib/stack-client/stack-client.init.spec.js +56 -0
  177. package/test/lib/stack-client/stack-client.intents.spec.js +27 -0
  178. package/test/lib/stack-client/stack-client.logout.spec.js +40 -0
  179. package/test/lib/stack.spec.js +60 -0
  180. package/test/store/__snapshots__/index.spec.js.snap +14 -0
  181. package/test/store/index.spec.js +41 -0
@@ -0,0 +1,41 @@
1
+ import React, { Component } from 'react'
2
+ import { ButtonLink } from 'cozy-ui/react/Button'
3
+ import { translate } from 'cozy-ui/react/I18n'
4
+
5
+ class Banner extends Component {
6
+ constructor(props) {
7
+ super(props)
8
+ this.state = { unmounted: true }
9
+ this.animate = this.animate.bind(this)
10
+ }
11
+
12
+ animate() {
13
+ // To animate we have to use a setTimeout to
14
+ // force a CSS class update and trigger CSS animation
15
+ return setTimeout(() => {
16
+ this.setState(() => ({ unmounted: false }))
17
+ }, 100)
18
+ }
19
+
20
+ componentDidMount() {
21
+ this.animate()
22
+ }
23
+
24
+ render() {
25
+ const { t, code, links } = this.props
26
+ const { unmounted } = this.state
27
+ return (
28
+ <div className={`coz-bar-banner${unmounted ? ' unmounted' : ''}`}>
29
+ <p>{t(`banner.${code}.description`)}</p>
30
+ <ButtonLink
31
+ className="coz-bar-banner-button"
32
+ size="tiny"
33
+ href={links}
34
+ label={t(`banner.${code}.CTA`)}
35
+ />
36
+ </div>
37
+ )
38
+ }
39
+ }
40
+
41
+ export default translate()(Banner)
@@ -0,0 +1,293 @@
1
+ /* global __PIWIK_TRACKER_URL__ __PIWIK_SITEID__ __PIWIK_DIMENSION_ID_APP__ */
2
+
3
+ import React, { Component } from 'react'
4
+ import { connect } from 'react-redux'
5
+
6
+ import { translate } from 'cozy-ui/react/I18n'
7
+ import Icon from 'cozy-ui/react/Icon'
8
+ import {
9
+ shouldEnableTracking,
10
+ getTracker,
11
+ configureTracker
12
+ } from 'cozy-ui/react/helpers/tracker'
13
+ import { isMobileApp } from 'cozy-device-helper'
14
+
15
+ import { ButtonCozyHome } from 'components/Apps/ButtonCozyHome'
16
+ import Banner from 'components/Banner'
17
+ import Drawer from 'components/Drawer'
18
+ import Settings from 'components/Settings'
19
+ import Apps from 'components/Apps'
20
+ import SearchBar from 'components/SearchBar'
21
+ import Claudy from 'components/Claudy'
22
+ import SupportModal from 'components/SupportModal'
23
+ import {
24
+ getTheme,
25
+ hasFetched,
26
+ getContent,
27
+ isCurrentApp,
28
+ fetchApps,
29
+ fetchContext,
30
+ fetchSettingsData,
31
+ shouldEnableClaudy,
32
+ getWebviewContext
33
+ } from 'lib/reducers'
34
+
35
+ import appsIcon from 'assets/sprites/icon-apps.svg'
36
+
37
+ export class Bar extends Component {
38
+ constructor(props) {
39
+ super(props)
40
+ this.state = {
41
+ claudyFired: false, // true to fire claudy (used by the drawer)
42
+ claudyOpened: false,
43
+ drawerVisible: false,
44
+ usageTracker: null,
45
+ supportDisplayed: false,
46
+ searchBarEnabled: props.isDrive && !props.isPublic && !isMobileApp()
47
+ }
48
+ this.fetchApps = this.fetchApps.bind(this)
49
+ this.fetchInitialData = this.fetchInitialData.bind(this)
50
+ this.handleTokenRefreshed = this.handleTokenRefreshed.bind(this)
51
+ }
52
+
53
+ componentDidMount() {
54
+ if (shouldEnableTracking()) {
55
+ this.initPiwikTracker()
56
+ }
57
+ this.fetchInitialData()
58
+
59
+ const cozyClient = this.props.cozyClient
60
+ cozyClient.on('tokenRefreshed', this.handleTokenRefreshed)
61
+ }
62
+
63
+ componentWillUnmount() {
64
+ const cozyClient = this.props.cozyClient
65
+ cozyClient.removeListener('tokenRefreshed', this.handleTokenRefreshed)
66
+ }
67
+
68
+ componentDidUpdate(prevProps, prevState) {
69
+ if (
70
+ !this.props.hasFetchedApps &&
71
+ this.state.drawerVisible &&
72
+ prevState.drawerVisible !== this.state.drawerVisible
73
+ ) {
74
+ this.fetchApps()
75
+ }
76
+ }
77
+
78
+ handleTokenRefreshed() {
79
+ this.fetchInitialData()
80
+ }
81
+
82
+ initPiwikTracker() {
83
+ const trackerInstance = getTracker(
84
+ __PIWIK_TRACKER_URL__,
85
+ __PIWIK_SITEID__,
86
+ false,
87
+ false
88
+ )
89
+ configureTracker({
90
+ appDimensionId: __PIWIK_DIMENSION_ID_APP__,
91
+ app: 'Cozy Bar',
92
+ heartbeat: 0
93
+ })
94
+ this.setState({ usageTracker: trackerInstance })
95
+ }
96
+
97
+ fetchApps() {
98
+ this.props.fetchApps()
99
+ }
100
+
101
+ fetchInitialData() {
102
+ if (this.props.isPublic) {
103
+ return
104
+ }
105
+ this.props.fetchContext()
106
+ this.props.fetchSettingsData(false)
107
+ this.fetchApps()
108
+ }
109
+
110
+ toggleDrawer = () => {
111
+ // don't allow to toggle the drawer if claudy opened or is opening
112
+ if (this.state.claudyOpened || this.state.claudyFired) return
113
+ const drawerVisible = !this.state.drawerVisible
114
+ // don't wait for transitionend if displaying
115
+ if (drawerVisible) this.props.onDrawer(drawerVisible)
116
+ this.setState({ drawerVisible })
117
+ }
118
+
119
+ toggleClaudy = (isFromDrawer = false) => {
120
+ if (!this.props.claudyEnabled) return
121
+ const { usageTracker, claudyOpened } = this.state
122
+ if (isFromDrawer && !claudyOpened) {
123
+ // if opened from drawer
124
+ // reset to toggle via the Claudy component
125
+ return this.setState({ claudyFired: true })
126
+ }
127
+ if (this.state.claudyFired) this.setState({ claudyFired: false })
128
+ if (usageTracker) {
129
+ usageTracker.push([
130
+ 'trackEvent',
131
+ 'Claudy',
132
+ claudyOpened ? 'close' : 'open',
133
+ 'claudy'
134
+ ])
135
+ }
136
+ this.setState({ claudyOpened: !claudyOpened })
137
+ }
138
+
139
+ toggleSupport = () => {
140
+ const { supportDisplayed } = this.state
141
+ this.setState({ supportDisplayed: !supportDisplayed })
142
+ }
143
+
144
+ renderCenter() {
145
+ const {
146
+ appName,
147
+ appNamePrefix,
148
+ appSlug,
149
+ iconPath,
150
+ replaceTitleOnMobile,
151
+ isPublic
152
+ } = this.props
153
+ return (
154
+ <Apps
155
+ appName={appName}
156
+ appNamePrefix={appNamePrefix}
157
+ appSlug={appSlug}
158
+ iconPath={iconPath}
159
+ replaceTitleOnMobile={replaceTitleOnMobile}
160
+ isPublic={isPublic}
161
+ />
162
+ )
163
+ }
164
+
165
+ renderLeft = () => {
166
+ const { t, isPublic, webviewContext } = this.props
167
+
168
+ if (webviewContext) {
169
+ return <ButtonCozyHome webviewContext={webviewContext} />
170
+ }
171
+
172
+ // data-tutorial attribute allows to be targeted in an application tutorial
173
+ return !isPublic ? (
174
+ <button
175
+ type="button"
176
+ className="coz-bar-btn coz-bar-burger"
177
+ onClick={this.toggleDrawer}
178
+ data-tutorial="apps-mobile"
179
+ >
180
+ <Icon icon={appsIcon} width={16} height={16} color="currentColor" />
181
+ <span className="coz-bar-hidden">{t('drawer')}</span>
182
+ </button>
183
+ ) : null
184
+ }
185
+
186
+ renderRight = () => {
187
+ const { isPublic } = this.props
188
+ return !isPublic ? (
189
+ <Settings
190
+ toggleSupport={this.toggleSupport}
191
+ onLogOut={this.props.onLogOut}
192
+ />
193
+ ) : null
194
+ }
195
+
196
+ render() {
197
+ const {
198
+ claudyFired,
199
+ claudyOpened,
200
+ drawerVisible,
201
+ searchBarEnabled,
202
+ supportDisplayed,
203
+ usageTracker
204
+ } = this.state
205
+
206
+ const {
207
+ theme,
208
+ themeOverrides,
209
+ barLeft,
210
+ barRight,
211
+ barCenter,
212
+ barSearch,
213
+ claudyEnabled,
214
+ onDrawer,
215
+ isPublic,
216
+ onLogOut,
217
+ userActionRequired
218
+ } = this.props
219
+
220
+ const {
221
+ primaryColor: pColor,
222
+ primaryContrastTextColor: pctColor
223
+ } = themeOverrides
224
+ const pStyle = pColor ? { '--cozBarThemePrimaryColor': pColor } : {}
225
+ const pctStyle = pctColor
226
+ ? { '--cozBarThemePrimaryContrastTextColor': pctColor }
227
+ : {}
228
+ const themeStyle = { ...pStyle, ...pctStyle }
229
+
230
+ return (
231
+ <div className={`coz-bar-wrapper coz-theme-${theme}`} style={themeStyle}>
232
+ <div id="cozy-bar-modal-dom-place" />
233
+ <div className="coz-bar-container">
234
+ {barLeft || this.renderLeft()}
235
+ {barCenter || this.renderCenter()}
236
+ <div className="u-flex-grow">
237
+ {barSearch || (searchBarEnabled ? <SearchBar /> : null)}
238
+ </div>
239
+ {barRight || this.renderRight()}
240
+ {!isPublic ? (
241
+ <Drawer
242
+ visible={drawerVisible}
243
+ onClose={this.toggleDrawer}
244
+ onClaudy={
245
+ (claudyEnabled && (() => this.toggleClaudy(true))) || false
246
+ }
247
+ isClaudyLoading={claudyFired}
248
+ drawerListener={() => onDrawer(drawerVisible)}
249
+ toggleSupport={this.toggleSupport}
250
+ onLogOut={onLogOut}
251
+ />
252
+ ) : null}
253
+ {claudyEnabled && (
254
+ <Claudy
255
+ usageTracker={usageTracker}
256
+ claudyFired={claudyFired}
257
+ onToggle={() => this.toggleClaudy(false)}
258
+ opened={claudyOpened}
259
+ />
260
+ )}
261
+ {supportDisplayed && <SupportModal onClose={this.toggleSupport} />}
262
+ </div>
263
+ {userActionRequired && <Banner {...userActionRequired} />}
264
+ </div>
265
+ )
266
+ }
267
+ }
268
+
269
+ export const mapStateToProps = state => ({
270
+ theme: getTheme(state).name,
271
+ themeOverrides: getTheme(state).overrides,
272
+ barLeft: getContent(state, 'left'),
273
+ barRight: getContent(state, 'right'),
274
+ barCenter: getContent(state, 'center'),
275
+ barSearch: getContent(state, 'search'),
276
+ isDrive: isCurrentApp(state, { slug: 'drive' }),
277
+ claudyEnabled: shouldEnableClaudy(state),
278
+ hasFetchedApps: hasFetched(state),
279
+ webviewContext: getWebviewContext(state)
280
+ })
281
+
282
+ export const mapDispatchToProps = dispatch => ({
283
+ fetchApps: () => dispatch(fetchApps()),
284
+ fetchContext: () => dispatch(fetchContext()),
285
+ fetchSettingsData: displayBusy => dispatch(fetchSettingsData(displayBusy))
286
+ })
287
+
288
+ export default translate()(
289
+ connect(
290
+ mapStateToProps,
291
+ mapDispatchToProps
292
+ )(Bar)
293
+ )
@@ -0,0 +1,133 @@
1
+ import React from 'react'
2
+ import { shallow } from 'enzyme'
3
+ import { isFlagshipApp, isMobileApp } from 'cozy-device-helper'
4
+ import toJson from 'enzyme-to-json'
5
+ import reducers from 'lib/reducers'
6
+ import CozyClient from 'cozy-client'
7
+
8
+ import { Bar, mapStateToProps, mapDispatchToProps } from './Bar'
9
+
10
+ jest.mock('cozy-device-helper', () => ({
11
+ ...require.requireActual('cozy-device-helper'),
12
+ isMobileApp: jest.fn(),
13
+ isFlagshipApp: jest.fn()
14
+ }))
15
+
16
+ describe('Bar', () => {
17
+ let props, client
18
+ beforeEach(() => {
19
+ jest.resetAllMocks()
20
+ jest.spyOn(Bar.prototype, 'fetchApps')
21
+ isMobileApp.mockReturnValue(false)
22
+ client = new CozyClient({})
23
+ props = {
24
+ fetchContext: jest.fn().mockResolvedValue({}),
25
+ fetchApps: jest.fn().mockResolvedValue([]),
26
+ fetchSettingsData: jest.fn().mockResolvedValue({}),
27
+ theme: 'default',
28
+ themeOverrides: {},
29
+ t: x => x,
30
+ cozyClient: client
31
+ }
32
+ })
33
+
34
+ afterEach(() => {
35
+ Bar.prototype.fetchApps.mockRestore()
36
+ isFlagshipApp.mockClear()
37
+ })
38
+
39
+ it('should fetch data when mounted', () => {
40
+ shallow(<Bar {...props} />)
41
+ expect(props.fetchContext).toHaveBeenCalled()
42
+ expect(props.fetchApps).toHaveBeenCalled()
43
+ expect(props.fetchSettingsData).toHaveBeenCalled()
44
+ expect(Bar.prototype.fetchApps).toHaveBeenCalled()
45
+ })
46
+
47
+ it('should not fetch data if public', () => {
48
+ shallow(<Bar {...props} isPublic={true} />)
49
+ expect(props.fetchContext).not.toHaveBeenCalled()
50
+ expect(props.fetchApps).not.toHaveBeenCalled()
51
+ expect(props.fetchSettingsData).not.toHaveBeenCalled()
52
+ expect(Bar.prototype.fetchApps).not.toHaveBeenCalled()
53
+ })
54
+
55
+ it('should not fetch apps if hasFetchedApps is true', () => {
56
+ const barWrapper = shallow(<Bar {...props} hasFetchedApps={true} />)
57
+ jest.resetAllMocks()
58
+ barWrapper.setState({ drawerVisible: true }) // to call componentdidUpdate
59
+ expect(props.fetchApps).not.toHaveBeenCalled()
60
+ expect(Bar.prototype.fetchApps).not.toHaveBeenCalled()
61
+ })
62
+
63
+ it('should re-fetch apps if apps are not fetched and drawer opens', () => {
64
+ const barWrapper = shallow(<Bar {...props} />)
65
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(1)
66
+ expect(props.fetchApps).toHaveBeenCalledTimes(1)
67
+ barWrapper.setState({ drawerVisible: true })
68
+ expect(props.fetchApps).toHaveBeenCalledTimes(2)
69
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(2)
70
+ barWrapper.setState({ drawerVisible: true, usageTracker: {} })
71
+ expect(props.fetchApps).toHaveBeenCalledTimes(2)
72
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(2)
73
+ })
74
+
75
+ it('should change theme', () => {
76
+ const barWrapper = shallow(<Bar {...props} theme="primary" />)
77
+ expect(toJson(barWrapper)).toMatchSnapshot()
78
+ })
79
+
80
+ it('should change allow theme overrides', () => {
81
+ const barWrapper = shallow(
82
+ <Bar
83
+ {...props}
84
+ theme="primary"
85
+ themeOverrides={{ primaryColor: 'red' }}
86
+ />
87
+ )
88
+ expect(toJson(barWrapper)).toMatchSnapshot()
89
+ })
90
+
91
+ it('should display the Searchbar', () => {
92
+ const barWrapper = shallow(<Bar {...props} isDrive isPublic={false} />)
93
+ expect(toJson(barWrapper)).toMatchSnapshot()
94
+ })
95
+
96
+ it('should not display searchbar if we are on mobile', () => {
97
+ isMobileApp.mockReturnValue(true)
98
+ const barWrapper = shallow(<Bar {...props} isDrive isPublic={false} />)
99
+ expect(toJson(barWrapper)).toMatchSnapshot()
100
+ })
101
+
102
+ it('should not display searchbar if we are not on Cozy Drive', () => {
103
+ isMobileApp.mockReturnValue(true)
104
+ const barWrapper = shallow(
105
+ <Bar {...props} isDrive={false} isPublic={false} />
106
+ )
107
+ expect(toJson(barWrapper)).toMatchSnapshot()
108
+ })
109
+
110
+ it('should not display searchbar if we are not on a public page', () => {
111
+ isMobileApp.mockReturnValue(true)
112
+ const barWrapper = shallow(<Bar {...props} isDrive isPublic={true} />)
113
+ expect(toJson(barWrapper)).toMatchSnapshot()
114
+ })
115
+
116
+ it('should have correct state props provided by the store with the initial state', () => {
117
+ const initialState = reducers(undefined, {})
118
+ expect(mapStateToProps(initialState)).toMatchSnapshot()
119
+ })
120
+
121
+ it('should have correct dispatch props provided by the store', () => {
122
+ expect(mapDispatchToProps(jest.fn())).toMatchSnapshot()
123
+ })
124
+
125
+ it('should call re-fetch data when token is refreshed', () => {
126
+ shallow(<Bar {...props} isDrive={false} isPublic={false} />)
127
+ client.emit('tokenRefreshed')
128
+ expect(props.fetchContext).toHaveBeenCalledTimes(2)
129
+ expect(props.fetchApps).toHaveBeenCalledTimes(2)
130
+ expect(props.fetchSettingsData).toHaveBeenCalledTimes(2)
131
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(2)
132
+ })
133
+ })
@@ -0,0 +1,81 @@
1
+ import React, { Component } from 'react'
2
+ import { getIntents } from 'lib/stack'
3
+
4
+ class Claudy extends Component {
5
+ constructor(props, context) {
6
+ super(props)
7
+ this.store = context.barStore
8
+ this.state = {
9
+ isLoading: false,
10
+ isActive: false
11
+ }
12
+ this.intents = getIntents()
13
+ }
14
+
15
+ UNSAFE_componentWillReceiveProps(nextProps) {
16
+ if (nextProps.claudyFired) this.toggle()
17
+ }
18
+
19
+ componentWillReceiveProps(nextProps) {
20
+ return this.UNSAFE_componentWillReceiveProps(nextProps)
21
+ }
22
+
23
+ toggle = () => {
24
+ if (!this.props.opened && !this.intentWrapperRef.childNodes.length) {
25
+ this.setState({ isLoading: true })
26
+ // init Claudy intent
27
+ this.intents
28
+ .create('CLAUDY', 'io.cozy.settings', {
29
+ exposeIntentFrameRemoval: true
30
+ })
31
+ .start(this.intentWrapperRef, () => {
32
+ this.setState({ isLoading: false, isActive: true })
33
+ this.props.onToggle() // toggle claudy when the intent is loaded
34
+ })
35
+ .then(({ removeIntentIframe }) => {
36
+ // exposeFrameRemoval intent event
37
+ // remove the intent frame at the end of the menu closing transition
38
+ const closedListener = e => {
39
+ if (e.propertyName === 'transform') {
40
+ removeIntentIframe()
41
+ this.setState({ isActive: false })
42
+ e.target.removeEventListener('transitionend', closedListener)
43
+ }
44
+ }
45
+ this.intentWrapperRef.addEventListener(
46
+ 'transitionend',
47
+ closedListener,
48
+ false
49
+ )
50
+ this.props.onToggle()
51
+ })
52
+ } else {
53
+ this.setState({ isActive: !this.state.isActive })
54
+ this.props.onToggle()
55
+ }
56
+ }
57
+
58
+ render() {
59
+ const { opened } = this.props
60
+ const { isLoading, isActive } = this.state
61
+ return (
62
+ <div className={`coz-claudy ${opened ? 'coz-claudy--opened' : ''}`}>
63
+ <button
64
+ type="button"
65
+ className="coz-claudy-icon coz-bar-hide-sm"
66
+ data-claudy-opened={isActive}
67
+ data-claudy-loading={isLoading}
68
+ onClick={this.toggle}
69
+ />
70
+ <div
71
+ className="coz-claudy-intent-wrapper"
72
+ ref={wrapper => {
73
+ this.intentWrapperRef = wrapper
74
+ }}
75
+ />
76
+ </div>
77
+ )
78
+ }
79
+ }
80
+
81
+ export default Claudy