orc-shared 1.2.0-dev.1 → 1.2.0-dev.13

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 (227) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/dist/actions/applications.js +1 -1
  4. package/dist/actions/authentication.js +1 -1
  5. package/dist/actions/countries.js +1 -1
  6. package/dist/actions/locale.js +1 -1
  7. package/dist/actions/makeApiAction.js +3 -3
  8. package/dist/actions/makeOrcApiAction.js +2 -2
  9. package/dist/actions/metadata.js +3 -3
  10. package/dist/actions/modules.js +63 -1
  11. package/dist/actions/navigation.js +3 -3
  12. package/dist/actions/requestsApi.js +8 -8
  13. package/dist/actions/scopes.js +59 -22
  14. package/dist/actions/timezones.js +1 -1
  15. package/dist/actions/toasts.js +1 -1
  16. package/dist/actions/versionInfo.js +1 -1
  17. package/dist/actions/view.js +1 -1
  18. package/dist/buildStore.js +3 -3
  19. package/dist/components/AppFrame/About.js +11 -6
  20. package/dist/components/AppFrame/MenuItem.js +6 -15
  21. package/dist/components/AppFrame/Preferences.js +3 -3
  22. package/dist/components/AppFrame/Sidebar.js +20 -9
  23. package/dist/components/AppFrame/Topbar.js +1 -1
  24. package/dist/components/ApplicationModuleLoader.js +143 -0
  25. package/dist/components/Authenticate.js +13 -13
  26. package/dist/components/CategoryList.js +1 -1
  27. package/dist/components/Checkbox.js +1 -1
  28. package/dist/components/DropMenu/Menu.js +1 -1
  29. package/dist/components/DropMenu/index.js +1 -1
  30. package/dist/components/Form/FieldList.js +3 -3
  31. package/dist/components/Form/Form.js +1 -1
  32. package/dist/components/Form/Inputs/Button.js +1 -1
  33. package/dist/components/Form/Inputs/FieldButtons.js +1 -1
  34. package/dist/components/Form/Inputs/Number.js +1 -1
  35. package/dist/components/Form/Inputs/ReadOnly.js +1 -1
  36. package/dist/components/Form/Inputs/SmallButton.js +1 -1
  37. package/dist/components/Form/Inputs/Text.js +1 -1
  38. package/dist/components/Form/Inputs/Time.js +1 -1
  39. package/dist/components/Form/Inputs/Toggles.js +1 -1
  40. package/dist/components/Form/Inputs/Translation.js +3 -3
  41. package/dist/components/List/HeadCell.js +1 -1
  42. package/dist/components/List/List.js +1 -1
  43. package/dist/components/List/Row.js +1 -1
  44. package/dist/components/List/enhanceColumnDefs.js +2 -2
  45. package/dist/components/MaterialUI/DataDisplay/List.js +1 -1
  46. package/dist/components/MaterialUI/DataDisplay/Notification.js +2 -2
  47. package/dist/components/MaterialUI/DataDisplay/NotificationProps.js +1 -1
  48. package/dist/components/MaterialUI/DataDisplay/PredefinedElements/Translations.js +3 -3
  49. package/dist/components/MaterialUI/DataDisplay/Table.js +1 -1
  50. package/dist/components/MaterialUI/DataDisplay/TableProps.js +1 -1
  51. package/dist/components/MaterialUI/DataDisplay/TransferList.js +1 -1
  52. package/dist/components/MaterialUI/DataDisplay/chipProps.js +1 -1
  53. package/dist/components/MaterialUI/DataDisplay/collapsableListProps.js +1 -1
  54. package/dist/components/MaterialUI/DataDisplay/dividerProps.js +1 -1
  55. package/dist/components/MaterialUI/DataDisplay/index.js +1 -1
  56. package/dist/components/MaterialUI/DataDisplay/modalProps.js +1 -1
  57. package/dist/components/MaterialUI/DataDisplay/useTableSelection.js +3 -3
  58. package/dist/components/MaterialUI/Feedback/useNotification.js +1 -1
  59. package/dist/components/MaterialUI/Inputs/Autocomplete.js +2 -2
  60. package/dist/components/MaterialUI/Inputs/AutocompleteProps.js +1 -1
  61. package/dist/components/MaterialUI/Inputs/CheckboxGroupProps.js +1 -1
  62. package/dist/components/MaterialUI/Inputs/CheckboxProps.js +1 -1
  63. package/dist/components/MaterialUI/Inputs/InputBase.js +42 -5
  64. package/dist/components/MaterialUI/Inputs/InputBaseProps.js +1 -1
  65. package/dist/components/MaterialUI/Inputs/PredefinedElements/SearchControl.js +1 -1
  66. package/dist/components/MaterialUI/Inputs/RadioProps.js +1 -1
  67. package/dist/components/MaterialUI/Inputs/Select.js +2 -2
  68. package/dist/components/MaterialUI/Inputs/SelectProps.js +1 -1
  69. package/dist/components/MaterialUI/Inputs/Switch.js +3 -3
  70. package/dist/components/MaterialUI/Inputs/SwitchProps.js +1 -1
  71. package/dist/components/MaterialUI/Inputs/TimePicker.js +1 -1
  72. package/dist/components/MaterialUI/Inputs/createInput.js +3 -3
  73. package/dist/components/MaterialUI/Inputs/index.js +1 -1
  74. package/dist/components/MaterialUI/Inputs/standaloneRadioProps.js +1 -1
  75. package/dist/components/MaterialUI/Navigation/DropDownMenuProps.js +1 -1
  76. package/dist/components/MaterialUI/Navigation/ExternalLink.js +113 -0
  77. package/dist/components/MaterialUI/ScopeSelector/ScopeSelector.js +8 -2
  78. package/dist/components/MaterialUI/Surfaces/expansionPanelProps.js +1 -1
  79. package/dist/components/MaterialUI/Surfaces/paperProps.js +1 -1
  80. package/dist/components/MaterialUI/muiThemes.js +7 -2
  81. package/dist/components/MaterialUI/textProps.js +1 -1
  82. package/dist/components/Modules.js +126 -41
  83. package/dist/components/MultiSelector.js +1 -1
  84. package/dist/components/Navigation/Bar.js +3 -3
  85. package/dist/components/Navigation/Tab.js +1 -1
  86. package/dist/components/Navigation/useNavigationState.js +3 -3
  87. package/dist/components/Placeholder.js +1 -1
  88. package/dist/components/Routing/FullPage.js +3 -1
  89. package/dist/components/Routing/Page.js +5 -3
  90. package/dist/components/Routing/Segment.js +1 -1
  91. package/dist/components/Routing/SegmentPage.js +1 -1
  92. package/dist/components/Routing/withWaypointing.js +8 -4
  93. package/dist/components/Scope/ScopeNode.js +1 -1
  94. package/dist/components/Scope/Selector.js +1 -1
  95. package/dist/components/Scope/index.js +3 -3
  96. package/dist/components/Scope/useScopeConfirmationModalState.js +7 -16
  97. package/dist/components/Scope/useScopeData.js +4 -13
  98. package/dist/components/Scope/useScopeSelect.js +1 -1
  99. package/dist/components/Selector.js +1 -1
  100. package/dist/components/Spritesheet.js +1 -1
  101. package/dist/components/Switch.js +1 -1
  102. package/dist/components/Text.js +1 -1
  103. package/dist/components/ToastList.js +1 -1
  104. package/dist/components/Toolbar.js +1 -1
  105. package/dist/components/Treeview/Label.js +1 -1
  106. package/dist/components/Treeview/Leaf.js +1 -1
  107. package/dist/components/Treeview/Node.js +3 -3
  108. package/dist/components/Treeview/index.js +2 -2
  109. package/dist/components/Treeview/settings.js +1 -1
  110. package/dist/constants.js +19 -2
  111. package/dist/content/icons/lock.svg +3 -0
  112. package/dist/content/icons/orckestra-icon.svg +5 -0
  113. package/dist/content/icons/unlock.svg +3 -0
  114. package/dist/content/iconsSheet.svg +11 -0
  115. package/dist/content/orckestra-logo-white.png +0 -0
  116. package/dist/getThemeOverrides.js +2 -2
  117. package/dist/hocs/withInfiniteScroll.js +1 -1
  118. package/dist/hocs/withUpdateHandler.js +2 -2
  119. package/dist/hooks/useDispatchWithModulesData.js +1 -1
  120. package/dist/hooks/useEditState.js +3 -3
  121. package/dist/hooks/useEntityLoader.js +2 -2
  122. package/dist/hooks/useFullEntityEditState.js +3 -3
  123. package/dist/hooks/useLabelMessage.js +3 -3
  124. package/dist/hooks/useMultipleFieldEditState.js +2 -2
  125. package/dist/hooks/useNavigationHandler.js +1 -1
  126. package/dist/hooks/useNotificationRequestState.js +2 -2
  127. package/dist/hooks/useSelectorAndUnwrap.js +1 -1
  128. package/dist/reducers/metadata.js +1 -1
  129. package/dist/reducers/modules.js +39 -1
  130. package/dist/reducers/scopes.js +27 -0
  131. package/dist/reducers/settings.js +31 -2
  132. package/dist/schemas/countries.js +1 -1
  133. package/dist/schemas/definitions.js +1 -1
  134. package/dist/schemas/metadata.js +1 -1
  135. package/dist/schemas/productDefinitions.js +1 -1
  136. package/dist/schemas/timezones.js +1 -1
  137. package/dist/selectors/applications.js +1 -1
  138. package/dist/selectors/authentication.js +57 -18
  139. package/dist/selectors/countries.js +1 -1
  140. package/dist/selectors/locale.js +1 -1
  141. package/dist/selectors/metadata.js +16 -6
  142. package/dist/selectors/modules.js +15 -1
  143. package/dist/selectors/navigation.js +1 -1
  144. package/dist/selectors/requests.js +1 -1
  145. package/dist/selectors/scope.js +8 -1
  146. package/dist/selectors/settings.js +13 -1
  147. package/dist/selectors/versionInfo.js +1 -1
  148. package/dist/selectors/view.js +1 -1
  149. package/dist/spawnerMiddleware.js +1 -1
  150. package/dist/utils/displayModeHelper.js +1 -1
  151. package/dist/utils/flatten.js +2 -2
  152. package/dist/utils/localizationHelper.js +1 -1
  153. package/dist/utils/mapHelper.js +1 -1
  154. package/dist/utils/modelValidationHelper.js +1 -1
  155. package/dist/utils/parseHelper.js +1 -1
  156. package/dist/utils/propertyHelper.js +2 -2
  157. package/dist/utils/propertyValidator.js +1 -1
  158. package/dist/utils/setTranslation.js +27 -1
  159. package/dist/utils/setTranslationWithFallback.js +31 -2
  160. package/dist/utils/testUtils.js +2 -1
  161. package/dist/utils/unwrapImmutable.js +1 -1
  162. package/dist/utils/urlHelper.js +1 -1
  163. package/package.json +6 -5
  164. package/src/actions/modules.js +30 -0
  165. package/src/actions/modules.test.js +50 -1
  166. package/src/actions/scopes.js +33 -7
  167. package/src/actions/scopes.test.js +84 -14
  168. package/src/components/AppFrame/About.js +10 -3
  169. package/src/components/AppFrame/AppFrame.test.js +9 -0
  170. package/src/components/AppFrame/MenuItem.js +3 -5
  171. package/src/components/AppFrame/MenuItem.test.js +2 -24
  172. package/src/components/AppFrame/Sidebar.js +11 -12
  173. package/src/components/AppFrame/Sidebar.test.js +18 -0
  174. package/src/components/ApplicationModuleLoader.js +52 -0
  175. package/src/components/ApplicationModuleLoader.test.js +149 -0
  176. package/src/components/Authenticate.js +5 -4
  177. package/src/components/Authenticate.test.js +23 -4
  178. package/src/components/MaterialUI/DataDisplay/PredefinedElements/Translations.test.js +7 -1
  179. package/src/components/MaterialUI/Inputs/InputBase.js +19 -2
  180. package/src/components/MaterialUI/Inputs/InputBase.test.js +68 -0
  181. package/src/components/MaterialUI/Navigation/ExternalLink.js +25 -0
  182. package/src/components/MaterialUI/Navigation/ExternalLink.test.js +26 -0
  183. package/src/components/MaterialUI/ScopeSelector/ScopeSelector.js +5 -1
  184. package/src/components/MaterialUI/muiThemes.js +5 -0
  185. package/src/components/Modules.js +103 -20
  186. package/src/components/Modules.test.js +315 -28
  187. package/src/components/Provision.test.js +34 -0
  188. package/src/components/Routing/FullPage.js +2 -1
  189. package/src/components/Routing/FullPage.test.js +23 -0
  190. package/src/components/Routing/Page.js +2 -2
  191. package/src/components/Routing/Page.test.js +20 -0
  192. package/src/components/Routing/Segment.js +1 -1
  193. package/src/components/Routing/withWaypointing.js +2 -2
  194. package/src/components/Routing/withWaypointing.test.js +33 -5
  195. package/src/components/Scope/Scope.test.js +4 -0
  196. package/src/components/Scope/useScopeConfirmationModalState.js +5 -16
  197. package/src/components/Scope/useScopeConfirmationModalState.test.js +39 -13
  198. package/src/components/Scope/useScopeData.js +0 -3
  199. package/src/components/Scope/useScopeData.test.js +0 -27
  200. package/src/constants.js +15 -0
  201. package/src/content/icons/lock.svg +3 -0
  202. package/src/content/icons/orckestra-icon.svg +5 -0
  203. package/src/content/icons/unlock.svg +3 -0
  204. package/src/content/iconsSheet.svg +11 -0
  205. package/src/content/orckestra-logo-white.png +0 -0
  206. package/src/hocs/withScopeData.test.js +0 -31
  207. package/src/reducers/modules.js +48 -2
  208. package/src/reducers/modules.test.js +117 -2
  209. package/src/reducers/scopes.js +30 -0
  210. package/src/reducers/scopes.test.js +45 -1
  211. package/src/reducers/settings.js +26 -2
  212. package/src/reducers/settings.test.js +74 -6
  213. package/src/selectors/authentication.js +53 -27
  214. package/src/selectors/authentication.test.js +600 -12
  215. package/src/selectors/metadata.js +18 -7
  216. package/src/selectors/metadata.test.js +221 -283
  217. package/src/selectors/modules.js +7 -0
  218. package/src/selectors/modules.test.js +16 -1
  219. package/src/selectors/scope.js +3 -1
  220. package/src/selectors/scope.test.js +5 -0
  221. package/src/selectors/settings.js +6 -0
  222. package/src/translations/en-US.json +1 -1
  223. package/src/translations/fr-CA.json +1 -1
  224. package/src/utils/setTranslation.js +16 -1
  225. package/src/utils/setTranslation.test.js +24 -0
  226. package/src/utils/setTranslationWithFallback.js +18 -1
  227. package/src/utils/setTranslationWithFallback.test.js +108 -0
@@ -24,6 +24,15 @@ describe("Sidebar", () => {
24
24
  state = Immutable.fromJS({
25
25
  navigation: { route: { match: { params: { scope: "Global" } } }, config: { prependHref: "/Global/" } },
26
26
  settings: { defaultScope: "myScope" },
27
+ modules: {
28
+ tree: "modulesTree",
29
+ visibleModules: ["a", "module123"],
30
+ lastScopeAndModuleSelection: {
31
+ scope: "Norway",
32
+ moduleName: "Profiles",
33
+ routingPerformed: false,
34
+ },
35
+ },
27
36
  });
28
37
  store = {
29
38
  subscribe: () => {},
@@ -153,6 +162,15 @@ describe("EnhancedMenuItem", () => {
153
162
  },
154
163
  config: { prependHref: "/Global/" },
155
164
  },
165
+ modules: {
166
+ tree: "modulesTree",
167
+ visibleModules: ["route"],
168
+ lastScopeAndModuleSelection: {
169
+ scope: "Norway",
170
+ moduleName: "Profiles",
171
+ routingPerformed: false,
172
+ },
173
+ },
156
174
  settings: { defaultScope: "myScope" },
157
175
  }),
158
176
  };
@@ -0,0 +1,52 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { getDefaultScope, getScopes } from "../actions/scopes";
4
+ import {
5
+ defaultScopeSelector,
6
+ getApplicationModulesSelector,
7
+ getLoadedModulesScopeSelector,
8
+ } from "../selectors/settings";
9
+ import { Loader } from "./Authenticate";
10
+ import { overtureModule, scopeTypes } from "../constants";
11
+ import { initializeFirstModuleScope } from "../actions/modules";
12
+
13
+ const ApplicationModuleLoader = ({ children }) => {
14
+ const dispatch = useDispatch();
15
+
16
+ const [scopesLoaded, setScopesLoaded] = useState(false);
17
+
18
+ const loadedModules = useSelector(getLoadedModulesScopeSelector);
19
+ const applicationModules = useSelector(getApplicationModulesSelector);
20
+ const defaultScope = useSelector(defaultScopeSelector);
21
+
22
+ useEffect(() => {
23
+ if (applicationModules.length > 0 && scopesLoaded === false) {
24
+ if (applicationModules.includes(overtureModule.System)) {
25
+ dispatch(initializeFirstModuleScope(scopeTypes.global));
26
+ }
27
+
28
+ applicationModules.forEach(x => {
29
+ // For the default scope, the latest that will be returned will be the chosen one
30
+ dispatch(getDefaultScope(x));
31
+ // For scopes, they need to be merged
32
+ dispatch(getScopes(x));
33
+ });
34
+
35
+ setScopesLoaded(true);
36
+ }
37
+ }, [dispatch, applicationModules, scopesLoaded]);
38
+
39
+ const scopeLoadedFromAllModules =
40
+ applicationModules.length > 0 &&
41
+ applicationModules.reduce((prev, current) => prev && loadedModules.includes(current), true);
42
+
43
+ const applicationModuleReady = scopeLoadedFromAllModules && defaultScope != null;
44
+
45
+ if (!applicationModuleReady) {
46
+ return <Loader />;
47
+ }
48
+
49
+ return React.Children.only(children);
50
+ };
51
+
52
+ export default ApplicationModuleLoader;
@@ -0,0 +1,149 @@
1
+ import React from "react";
2
+ import Immutable from "immutable";
3
+ import { Provider } from "react-redux";
4
+ import { ThemeProvider } from "styled-components";
5
+ import { Loader } from "./Authenticate";
6
+ import ApplicationModuleLoader from "./ApplicationModuleLoader";
7
+ import { mount } from "enzyme";
8
+ import { getDefaultScope, getScopes } from "../actions/scopes";
9
+ import sinon from "sinon";
10
+ import { initializeFirstModuleScope } from "../actions/modules";
11
+ import { scopeTypes } from "../constants";
12
+
13
+ const TestComp = () => {
14
+ return <div className="test" />;
15
+ };
16
+
17
+ jest.mock("../utils/buildUrl", () => {
18
+ const modExport = {};
19
+ modExport.loadConfig = () => Promise.resolve({});
20
+ modExport.buildUrl = (path = [], params = "") => "URL: " + path.join("/") + " " + JSON.stringify(params);
21
+ return modExport;
22
+ });
23
+
24
+ describe("ApplicationModuleLoader", () => {
25
+ let state, store;
26
+
27
+ beforeEach(() => {
28
+ state = Immutable.fromJS({
29
+ requests: {},
30
+ scopes: {
31
+ test1: {
32
+ id: "test1",
33
+ name: { "en-CA": "Test 1", "en-US": "Test 1" },
34
+ children: ["test2"],
35
+ },
36
+ },
37
+ authentication: {
38
+ name: "foo@bar.com",
39
+ },
40
+ settings: {
41
+ defaultScope: "aDefaultScope",
42
+ loadedModulesScope: ["moduleA", "moduleB"],
43
+ modules: ["moduleA", "moduleB"],
44
+ },
45
+ });
46
+ store = state => ({
47
+ subscribe: () => {},
48
+ getState: () => state,
49
+ dispatch: sinon.spy().named("dispatch"),
50
+ });
51
+ });
52
+
53
+ it("shows the component when scopes configuration are all loaded", () =>
54
+ expect(
55
+ <Provider store={store(state)}>
56
+ <ApplicationModuleLoader>
57
+ <TestComp />
58
+ </ApplicationModuleLoader>
59
+ </Provider>,
60
+ "when mounted",
61
+ "to exhaustively satisfy",
62
+ <TestComp />,
63
+ ));
64
+
65
+ it("shows a load indicator component when default scope is still unknown", () => {
66
+ state = state.setIn(["settings", "defaultScope"], null);
67
+ return expect(
68
+ <Provider store={store(state)}>
69
+ <ThemeProvider theme={{}}>
70
+ <ApplicationModuleLoader>
71
+ <TestComp />
72
+ </ApplicationModuleLoader>
73
+ </ThemeProvider>
74
+ </Provider>,
75
+ "when mounted",
76
+ "to exhaustively satisfy",
77
+ <ThemeProvider theme={{}}>
78
+ <Loader />
79
+ </ThemeProvider>,
80
+ );
81
+ });
82
+
83
+ it("shows a load indicator component when scopes trees are not loaded for all modules", () => {
84
+ state = state.setIn(["settings", "loadedModulesScope"], Immutable.fromJS(["moduleA"]));
85
+ return expect(
86
+ <Provider store={store(state)}>
87
+ <ThemeProvider theme={{}}>
88
+ <ApplicationModuleLoader>
89
+ <TestComp />
90
+ </ApplicationModuleLoader>
91
+ </ThemeProvider>
92
+ </Provider>,
93
+ "when mounted",
94
+ "to exhaustively satisfy",
95
+ <ThemeProvider theme={{}}>
96
+ <Loader />
97
+ </ThemeProvider>,
98
+ );
99
+ });
100
+
101
+ it("scopes tree and default scope should be loaded when rendering the component", () => {
102
+ const theStore = store(state);
103
+
104
+ const component = (
105
+ <Provider store={theStore}>
106
+ <ThemeProvider theme={{}}>
107
+ <ApplicationModuleLoader>
108
+ <TestComp />
109
+ </ApplicationModuleLoader>
110
+ </ThemeProvider>
111
+ </Provider>
112
+ );
113
+
114
+ mount(component);
115
+
116
+ expect(theStore.dispatch, "to have calls satisfying", [
117
+ { args: [getDefaultScope("moduleA")] },
118
+ { args: [getScopes("moduleA")] },
119
+ { args: [getDefaultScope("moduleB")] },
120
+ { args: [getScopes("moduleB")] },
121
+ ]);
122
+ });
123
+
124
+ it("First module scope should be initialized when System is part application modules", () => {
125
+ state = state.setIn(["settings", "modules"], Immutable.fromJS(["moduleA", "System"]));
126
+
127
+ const theStore = store(state);
128
+
129
+ const component = (
130
+ <Provider store={theStore}>
131
+ <ThemeProvider theme={{}}>
132
+ <ApplicationModuleLoader>
133
+ <TestComp />
134
+ </ApplicationModuleLoader>
135
+ </ThemeProvider>
136
+ </Provider>
137
+ );
138
+
139
+ mount(component);
140
+
141
+ expect(theStore.dispatch, "to have calls satisfying", [
142
+ { args: [initializeFirstModuleScope(scopeTypes.global)] },
143
+ { args: [getDefaultScope("moduleA")] },
144
+ { args: [getScopes("moduleA")] },
145
+ { args: [getDefaultScope("System")] },
146
+ { args: [getScopes("System")] },
147
+ ]);
148
+ });
149
+ });
@@ -6,10 +6,10 @@ import { useSelector } from "react-redux";
6
6
  import { unwrapImmutable } from "../utils";
7
7
  import { GET_AUTHENTICATION_PROFILE } from "../actions/authentication";
8
8
  import { ERROR, LOGOUT } from "../reducers/request";
9
+ import ApplicationModuleLoader from "./ApplicationModuleLoader";
9
10
 
10
11
  export const useAuthenticationData = () => ({
11
12
  loading: useSelector(state => state.getIn(["requests", "actives", GET_AUTHENTICATION_PROFILE])),
12
- defaultScope: useSelector(state => state.getIn(["settings", "defaultScope"])) || null,
13
13
  authedUser: useSelector(state => state.getIn(["authentication", "name"])),
14
14
  requestError: unwrapImmutable(useSelector(state => state.getIn(["requests", ERROR]) || null)),
15
15
  needLogin: useSelector(state => state.getIn(["requests", LOGOUT])),
@@ -47,14 +47,15 @@ export const Error = ({ requestError, needLogin }) => {
47
47
  };
48
48
 
49
49
  const Authenticate = ({ children }) => {
50
- const { loading, defaultScope, authedUser, requestError, needLogin } = useAuthenticationData();
51
- if (loading || defaultScope === null) {
50
+ const { loading, authedUser, requestError, needLogin } = useAuthenticationData();
51
+
52
+ if (loading) {
52
53
  return <Loader />;
53
54
  }
54
55
  if (!authedUser) {
55
56
  return <Error {...{ requestError, needLogin }} />;
56
57
  } else {
57
- return React.Children.only(children);
58
+ return <ApplicationModuleLoader children={children} />;
58
59
  }
59
60
  };
60
61
 
@@ -7,19 +7,36 @@ import { ERROR, LOGOUT } from "../reducers/request";
7
7
  import { GET_AUTHENTICATION_PROFILE } from "../actions/authentication";
8
8
 
9
9
  const TestComp = () => {
10
- return <div className="test"></div>;
10
+ return <div className="test" />;
11
11
  };
12
12
 
13
+ jest.mock("../utils/buildUrl", () => {
14
+ const modExport = {};
15
+ modExport.loadConfig = () => Promise.resolve({});
16
+ modExport.buildUrl = () => "URL";
17
+ return modExport;
18
+ });
19
+
13
20
  describe("Authenticate", () => {
14
21
  let state, store;
22
+
15
23
  beforeEach(() => {
16
24
  state = Immutable.fromJS({
17
25
  requests: {},
26
+ scopes: {
27
+ test1: {
28
+ id: "test1",
29
+ name: { "en-CA": "Test 1", "en-US": "Test 1" },
30
+ children: ["test2"],
31
+ },
32
+ },
18
33
  authentication: {
19
34
  name: "foo@bar.com",
20
35
  },
21
36
  settings: {
22
37
  defaultScope: "aDefaultScope",
38
+ loadedModulesScope: ["moduleA", "moduleB"],
39
+ modules: ["moduleA", "moduleB"],
23
40
  },
24
41
  });
25
42
  store = state => ({
@@ -38,7 +55,9 @@ describe("Authenticate", () => {
38
55
  </Provider>,
39
56
  "when mounted",
40
57
  "to exhaustively satisfy",
41
- <TestComp />,
58
+ <Provider store={store(state)}>
59
+ <TestComp />
60
+ </Provider>,
42
61
  ));
43
62
 
44
63
  it("shows a load indicator component if authentication is ongoing", () => {
@@ -59,12 +78,12 @@ describe("Authenticate", () => {
59
78
  );
60
79
  });
61
80
 
62
- it("shows a load indicator component if default scope is unknown", () => {
81
+ it("shows a load indicator component when application modules not ready", () => {
63
82
  state = state.setIn(["settings", "defaultScope"], null);
64
83
  return expect(
65
84
  <Provider store={store(state)}>
66
85
  <ThemeProvider theme={{}}>
67
- <Authenticate>
86
+ <Authenticate applicationModuleReady={false}>
68
87
  <TestComp />
69
88
  </Authenticate>
70
89
  </ThemeProvider>
@@ -18,6 +18,8 @@ describe("Translations ", () => {
18
18
  const theme = createMuiTheme();
19
19
 
20
20
  beforeEach(() => {
21
+ window.bypassDebounce = true;
22
+
21
23
  collapsibleListProps = new CollapsibleListProps();
22
24
  collapsibleListProps.set(CollapsibleListProps.propNames.hasMessage, true);
23
25
  collapsibleListProps.set(
@@ -32,6 +34,10 @@ describe("Translations ", () => {
32
34
  collapsibleListProps.set(CollapsibleListProps.propNames.containerWidth, "unset");
33
35
  });
34
36
 
37
+ afterEach(() => {
38
+ delete window.bypassDebounce;
39
+ });
40
+
35
41
  it("Renders Translations correctly", () => {
36
42
  const component = (
37
43
  <TestWrapper stylesProvider muiThemeProvider={{ theme }} intlProvider={{ messages }}>
@@ -135,7 +141,7 @@ describe("Translations ", () => {
135
141
  expect(component, "when mounted", "to satisfy", expected);
136
142
  });
137
143
 
138
- it("Translations component handles change", () => {
144
+ it("Translations component handles change", () => {
139
145
  const aValue = "aValue";
140
146
  const update = sinon.spy().named("update");
141
147
 
@@ -111,12 +111,29 @@ const InputBase = ({ inputProps }) => {
111
111
 
112
112
  const onChangeHandler = event => {
113
113
  event.persist();
114
- update(event.target.value, metadata);
114
+
115
+ if (!event.target.value || window.bypassDebounce === true) {
116
+ update(event.target.value, metadata);
117
+ }
118
+
119
+ setInputText(event.target.value);
115
120
  };
116
121
 
117
122
  const inputBaseInputStyle = inputProps?.getStyle(InputBaseProps.ruleNames.input);
118
123
  const errorTextStyle = inputProps?.getStyle(InputBaseProps.ruleNames.errorText);
119
124
 
125
+ const [inputText, setInputText] = React.useState(null);
126
+
127
+ const textToDisplay = inputText ?? value;
128
+
129
+ React.useEffect(() => {
130
+ if (inputText !== value && inputText != null && window.bypassDebounce !== true) {
131
+ const timeOutId = setTimeout(() => update(inputText, metadata), 100);
132
+ return () => clearTimeout(timeOutId);
133
+ }
134
+ // eslint-disable-next-line react-hooks/exhaustive-deps
135
+ }, [inputText, value]);
136
+
120
137
  return (
121
138
  <div className={classes.container}>
122
139
  <div className={classes.inputContainer}>
@@ -133,7 +150,7 @@ const InputBase = ({ inputProps }) => {
133
150
  onClick={onClick}
134
151
  type={type}
135
152
  placeholder={placeholder}
136
- value={value}
153
+ value={textToDisplay}
137
154
  fullWidth={true}
138
155
  onChange={event => onChangeHandler(event)}
139
156
  error={!!error}
@@ -5,15 +5,19 @@ import InputBaseMUI from "@material-ui/core/InputBase";
5
5
  import sinon from "sinon";
6
6
  import { ignoreConsoleError } from "../../../utils/testUtils";
7
7
  import InputBaseProps from "./InputBaseProps";
8
+ import { act } from "unexpected-reaction";
8
9
 
9
10
  describe("InputBase Component", () => {
10
11
  let update, container;
11
12
  beforeEach(() => {
13
+ window.bypassDebounce = true;
14
+
12
15
  container = document.createElement("div");
13
16
  document.body.appendChild(container);
14
17
  update = sinon.spy().named("update");
15
18
  });
16
19
  afterEach(() => {
20
+ delete window.bypassDebounce;
17
21
  document.body.removeChild(container);
18
22
  container = null;
19
23
  });
@@ -243,3 +247,67 @@ describe("InputBase Component", () => {
243
247
  expect(mountedComponent.containsMatchingElement(expected), "to be truthy");
244
248
  });
245
249
  });
250
+
251
+ describe("InputBase component debouce", () => {
252
+ const clock = sinon.useFakeTimers();
253
+ let update, container;
254
+
255
+ beforeEach(() => {
256
+ container = document.createElement("div");
257
+ document.body.appendChild(container);
258
+ update = sinon.spy().named("update");
259
+ });
260
+
261
+ afterEach(() => {
262
+ clock.restore();
263
+ });
264
+
265
+ it("InputBase component updates when debounced update", async () => {
266
+ const inputProps = new InputBaseProps();
267
+ const aLabel = "aLabel";
268
+ const aValue = "value";
269
+
270
+ const metadata = {
271
+ test: "value",
272
+ };
273
+
274
+ inputProps.set(InputBaseProps.propNames.value, "");
275
+ inputProps.set(InputBaseProps.propNames.update, update);
276
+ inputProps.set(InputBaseProps.propNames.label, aLabel);
277
+ inputProps.set(InputBaseProps.propNames.metadata, metadata);
278
+
279
+ const component = <InputBase inputProps={inputProps} />;
280
+ const mountedComponent = mount(component);
281
+
282
+ const input = mountedComponent.find("input");
283
+ input.simulate("change", { target: { value: aValue } });
284
+
285
+ act(() => {
286
+ clock.tick(200);
287
+ });
288
+
289
+ input.simulate("change", { target: { value: "differentValue" } });
290
+ expect(update, "to have calls satisfying", [{ args: [aValue, metadata] }]);
291
+ });
292
+
293
+ it("InputBase component update to empty when text to display is null", async () => {
294
+ const inputProps = new InputBaseProps();
295
+ const aLabel = "aLabel";
296
+
297
+ const metadata = {
298
+ test: "value",
299
+ };
300
+
301
+ inputProps.set(InputBaseProps.propNames.value, null);
302
+ inputProps.set(InputBaseProps.propNames.update, update);
303
+ inputProps.set(InputBaseProps.propNames.label, aLabel);
304
+ inputProps.set(InputBaseProps.propNames.metadata, metadata);
305
+
306
+ const component = <InputBase inputProps={inputProps} />;
307
+ const mountedComponent = mount(component);
308
+
309
+ const input = mountedComponent.find("input");
310
+ input.simulate("change", { target: { value: null } });
311
+ expect(input.get(0).props.value, "to equal", "");
312
+ });
313
+ });
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import Link from "@material-ui/core/Link";
3
+ import Icon from "../DataDisplay/Icon";
4
+ import { makeStyles } from "@material-ui/core/styles";
5
+
6
+ const useStyles = makeStyles(theme => ({
7
+ icon: {
8
+ paddingLeft: theme.spacing(1),
9
+ width: theme.spacing(1.2),
10
+ color: theme.palette.primary.main,
11
+ },
12
+ }));
13
+
14
+ const ExternalLink = ({ id, url, children }) => {
15
+ const classes = useStyles();
16
+
17
+ return (
18
+ <Link id={id} href={url} rel="noreferrer" target="_blank" underline="always">
19
+ {children}
20
+ <Icon id="open-in-new-tab" className={classes.icon} />
21
+ </Link>
22
+ );
23
+ };
24
+
25
+ export default ExternalLink;
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import Link from "@material-ui/core/Link";
3
+ import Icon from "../DataDisplay/Icon";
4
+ import ExternalLink from "./ExternalLink";
5
+
6
+ describe("ExternalLink", () => {
7
+ it("render ExternalLink without errors", () => {
8
+ const children = <div>a div element</div>;
9
+
10
+ expect(
11
+ <ExternalLink id="an_Id" url="https://www.npmjs.com/package/orc-shared" children={children} />,
12
+ "when mounted",
13
+ "to satisfy",
14
+ <Link
15
+ id="an_Id"
16
+ href="https://www.npmjs.com/package/orc-shared"
17
+ rel="noreferrer"
18
+ target="_blank"
19
+ underline="always"
20
+ >
21
+ {children}
22
+ <Icon id="open-in-new-tab" />
23
+ </Link>,
24
+ );
25
+ });
26
+ });
@@ -16,6 +16,10 @@ const useStyles = makeStyles(theme => ({
16
16
  border: `1px solid ${theme.palette.grey.borders}`,
17
17
  boxShadow: "0 2px 4px rgba(0,0,0,0.5)",
18
18
  width: theme.spacing(50),
19
+ display: "flex",
20
+ },
21
+ scopeContainer: {
22
+ width: "100%",
19
23
  },
20
24
  scopeSelector: {
21
25
  display: "flex",
@@ -64,7 +68,7 @@ const ScopeSelector = ({ show, getScope, selectedScope, closeSelector, filter, u
64
68
  <Sidepanel className={classes.container} in={show} timeout={300}>
65
69
  <ClickAwayListener onClickAway={e => closeSelector(e)}>
66
70
  {/* this div is required since ClickAwayListener child element should be able to hold ref */}
67
- <div>{show ? scopeSelectorContent : null}</div>
71
+ <div className={classes.scopeContainer}>{show ? scopeSelectorContent : null}</div>
68
72
  </ClickAwayListener>
69
73
  </Sidepanel>
70
74
  );
@@ -158,6 +158,11 @@ const setThemeOverrides = theme => ({
158
158
  },
159
159
  },
160
160
  },
161
+ MuiLink: {
162
+ root: {
163
+ fontSize: theme.spacing(1.3),
164
+ },
165
+ },
161
166
  MuiButton: {
162
167
  ...theme.MuiButton,
163
168
  root: {