@ledgerhq/live-common 27.2.0 → 27.3.0-nightly.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.
Files changed (219) hide show
  1. package/lib/bot/index.d.ts.map +1 -1
  2. package/lib/bot/index.js +8 -3
  3. package/lib/bot/index.js.map +1 -1
  4. package/lib/bot/specs.d.ts +9 -0
  5. package/lib/bot/specs.d.ts.map +1 -1
  6. package/lib/bot/specs.js +49 -1
  7. package/lib/bot/specs.js.map +1 -1
  8. package/lib/currencies/support.d.ts.map +1 -1
  9. package/lib/currencies/support.js +2 -1
  10. package/lib/currencies/support.js.map +1 -1
  11. package/lib/derivation.d.ts.map +1 -1
  12. package/lib/derivation.js +2 -4
  13. package/lib/derivation.js.map +1 -1
  14. package/lib/env.js +1 -1
  15. package/lib/exchange/swap/getExchangeRates.d.ts.map +1 -1
  16. package/lib/exchange/swap/getExchangeRates.js +1 -1
  17. package/lib/exchange/swap/getExchangeRates.js.map +1 -1
  18. package/lib/exchange/swap/getProviders.d.ts.map +1 -1
  19. package/lib/exchange/swap/getProviders.js +45 -5
  20. package/lib/exchange/swap/getProviders.js.map +1 -1
  21. package/lib/exchange/swap/getProviders.test.d.ts +2 -0
  22. package/lib/exchange/swap/getProviders.test.d.ts.map +1 -0
  23. package/lib/exchange/swap/getProviders.test.js +519 -0
  24. package/lib/exchange/swap/getProviders.test.js.map +1 -0
  25. package/lib/exchange/swap/hooks/useProviderRates.d.ts +1 -0
  26. package/lib/exchange/swap/hooks/useProviderRates.d.ts.map +1 -1
  27. package/lib/exchange/swap/hooks/useProviderRates.js +19 -4
  28. package/lib/exchange/swap/hooks/useProviderRates.js.map +1 -1
  29. package/lib/exchange/swap/hooks/useProviderRates.test.js +42 -0
  30. package/lib/exchange/swap/hooks/useProviderRates.test.js.map +1 -1
  31. package/lib/exchange/swap/hooks/useSwapTransaction.d.ts +1 -0
  32. package/lib/exchange/swap/hooks/useSwapTransaction.d.ts.map +1 -1
  33. package/lib/exchange/swap/hooks/useSwapTransaction.js +2 -1
  34. package/lib/exchange/swap/hooks/useSwapTransaction.js.map +1 -1
  35. package/lib/exchange/swap/index.d.ts +2 -1
  36. package/lib/exchange/swap/index.d.ts.map +1 -1
  37. package/lib/exchange/swap/index.js +11 -1
  38. package/lib/exchange/swap/index.js.map +1 -1
  39. package/lib/exchange/swap/index.test.js +22 -0
  40. package/lib/exchange/swap/index.test.js.map +1 -1
  41. package/lib/exchange/swap/mock.js +1 -1
  42. package/lib/exchange/swap/mock.js.map +1 -1
  43. package/lib/exchange/swap/types.d.ts +14 -0
  44. package/lib/exchange/swap/types.d.ts.map +1 -1
  45. package/lib/exchange/swap/utils/index.d.ts +1 -0
  46. package/lib/exchange/swap/utils/index.d.ts.map +1 -1
  47. package/lib/exchange/swap/utils/index.js +6 -1
  48. package/lib/exchange/swap/utils/index.js.map +1 -1
  49. package/lib/exchange/swap/utils/index.test.js +12 -0
  50. package/lib/exchange/swap/utils/index.test.js.map +1 -1
  51. package/lib/families/algorand/speculos-deviceActions.d.ts.map +1 -1
  52. package/lib/families/algorand/speculos-deviceActions.js +2 -29
  53. package/lib/families/algorand/speculos-deviceActions.js.map +1 -1
  54. package/lib/families/bitcoin/specs.d.ts.map +1 -1
  55. package/lib/families/bitcoin/specs.js +0 -1
  56. package/lib/families/bitcoin/specs.js.map +1 -1
  57. package/lib/families/bitcoin/speculos-deviceActions.d.ts.map +1 -1
  58. package/lib/families/bitcoin/speculos-deviceActions.js +2 -22
  59. package/lib/families/bitcoin/speculos-deviceActions.js.map +1 -1
  60. package/lib/families/cardano/specs.d.ts.map +1 -1
  61. package/lib/families/cardano/specs.js +4 -3
  62. package/lib/families/cardano/specs.js.map +1 -1
  63. package/lib/families/cardano/speculos-deviceActions.d.ts.map +1 -1
  64. package/lib/families/cardano/speculos-deviceActions.js +2 -10
  65. package/lib/families/cardano/speculos-deviceActions.js.map +1 -1
  66. package/lib/families/celo/speculos-deviceActions.d.ts.map +1 -1
  67. package/lib/families/celo/speculos-deviceActions.js +3 -10
  68. package/lib/families/celo/speculos-deviceActions.js.map +1 -1
  69. package/lib/families/elrond/speculos-deviceActions.d.ts.map +1 -1
  70. package/lib/families/elrond/speculos-deviceActions.js +3 -8
  71. package/lib/families/elrond/speculos-deviceActions.js.map +1 -1
  72. package/lib/families/ethereum/speculos-deviceActions.d.ts.map +1 -1
  73. package/lib/families/ethereum/speculos-deviceActions.js +10 -33
  74. package/lib/families/ethereum/speculos-deviceActions.js.map +1 -1
  75. package/lib/families/evm/specs.d.ts.map +1 -1
  76. package/lib/families/evm/specs.js +9 -5
  77. package/lib/families/evm/specs.js.map +1 -1
  78. package/lib/families/evm/speculos-deviceActions.d.ts.map +1 -1
  79. package/lib/families/evm/speculos-deviceActions.js +5 -25
  80. package/lib/families/evm/speculos-deviceActions.js.map +1 -1
  81. package/lib/families/filecoin/speculos-deviceActions.d.ts.map +1 -1
  82. package/lib/families/filecoin/speculos-deviceActions.js +6 -7
  83. package/lib/families/filecoin/speculos-deviceActions.js.map +1 -1
  84. package/lib/families/polkadot/speculos-deviceActions.d.ts.map +1 -1
  85. package/lib/families/polkadot/speculos-deviceActions.js +3 -10
  86. package/lib/families/polkadot/speculos-deviceActions.js.map +1 -1
  87. package/lib/families/ripple/speculos-deviceActions.d.ts.map +1 -1
  88. package/lib/families/ripple/speculos-deviceActions.js +2 -22
  89. package/lib/families/ripple/speculos-deviceActions.js.map +1 -1
  90. package/lib/families/solana/speculos-deviceActions.d.ts.map +1 -1
  91. package/lib/families/solana/speculos-deviceActions.js +5 -7
  92. package/lib/families/solana/speculos-deviceActions.js.map +1 -1
  93. package/lib/families/stellar/speculos-deviceActions.d.ts.map +1 -1
  94. package/lib/families/stellar/speculos-deviceActions.js +4 -8
  95. package/lib/families/stellar/speculos-deviceActions.js.map +1 -1
  96. package/lib/families/tezos/speculos-deviceActions.d.ts.map +1 -1
  97. package/lib/families/tezos/speculos-deviceActions.js +6 -11
  98. package/lib/families/tezos/speculos-deviceActions.js.map +1 -1
  99. package/lib/families/tron/speculos-deviceActions.d.ts.map +1 -1
  100. package/lib/families/tron/speculos-deviceActions.js +2 -14
  101. package/lib/families/tron/speculos-deviceActions.js.map +1 -1
  102. package/lib/featureFlags/defaultFeatures.d.ts.map +1 -1
  103. package/lib/featureFlags/defaultFeatures.js +3 -0
  104. package/lib/featureFlags/defaultFeatures.js.map +1 -1
  105. package/lib/hw/extractOnboardingState.d.ts +5 -1
  106. package/lib/hw/extractOnboardingState.d.ts.map +1 -1
  107. package/lib/hw/extractOnboardingState.js +17 -9
  108. package/lib/hw/extractOnboardingState.js.map +1 -1
  109. package/lib/hw/extractOnboardingState.test.js +21 -11
  110. package/lib/hw/extractOnboardingState.test.js.map +1 -1
  111. package/lib/postOnboarding/PostOnboardingProvider.d.ts +20 -0
  112. package/lib/postOnboarding/PostOnboardingProvider.d.ts.map +1 -0
  113. package/lib/postOnboarding/PostOnboardingProvider.js +33 -0
  114. package/lib/postOnboarding/PostOnboardingProvider.js.map +1 -0
  115. package/lib/postOnboarding/actions.d.ts +20 -0
  116. package/lib/postOnboarding/actions.d.ts.map +1 -0
  117. package/lib/postOnboarding/actions.js +37 -0
  118. package/lib/postOnboarding/actions.js.map +1 -0
  119. package/lib/postOnboarding/hooks/index.d.ts +6 -0
  120. package/lib/postOnboarding/hooks/index.d.ts.map +1 -0
  121. package/lib/postOnboarding/hooks/index.js +22 -0
  122. package/lib/postOnboarding/hooks/index.js.map +1 -0
  123. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.d.ts +8 -0
  124. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.d.ts.map +1 -0
  125. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.js +16 -0
  126. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.js.map +1 -0
  127. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.test.d.ts +2 -0
  128. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.test.d.ts.map +1 -0
  129. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.test.js +75 -0
  130. package/lib/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.test.js.map +1 -0
  131. package/lib/postOnboarding/hooks/usePostOnboardingContext.d.ts +3 -0
  132. package/lib/postOnboarding/hooks/usePostOnboardingContext.d.ts.map +1 -0
  133. package/lib/postOnboarding/hooks/usePostOnboardingContext.js +10 -0
  134. package/lib/postOnboarding/hooks/usePostOnboardingContext.js.map +1 -0
  135. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.d.ts +8 -0
  136. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.d.ts.map +1 -0
  137. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.js +20 -0
  138. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.js.map +1 -0
  139. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.test.d.ts +2 -0
  140. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.test.d.ts.map +1 -0
  141. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.test.js +64 -0
  142. package/lib/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.test.js.map +1 -0
  143. package/lib/postOnboarding/hooks/usePostOnboardingHubState.d.ts +12 -0
  144. package/lib/postOnboarding/hooks/usePostOnboardingHubState.d.ts.map +1 -0
  145. package/lib/postOnboarding/hooks/usePostOnboardingHubState.js +65 -0
  146. package/lib/postOnboarding/hooks/usePostOnboardingHubState.js.map +1 -0
  147. package/lib/postOnboarding/hooks/usePostOnboardingHubState.test.d.ts +2 -0
  148. package/lib/postOnboarding/hooks/usePostOnboardingHubState.test.d.ts.map +1 -0
  149. package/lib/postOnboarding/hooks/usePostOnboardingHubState.test.js +129 -0
  150. package/lib/postOnboarding/hooks/usePostOnboardingHubState.test.js.map +1 -0
  151. package/lib/postOnboarding/hooks/useStartPostOnboardingCallback.d.ts +13 -0
  152. package/lib/postOnboarding/hooks/useStartPostOnboardingCallback.d.ts.map +1 -0
  153. package/lib/postOnboarding/hooks/useStartPostOnboardingCallback.js +34 -0
  154. package/lib/postOnboarding/hooks/useStartPostOnboardingCallback.js.map +1 -0
  155. package/lib/postOnboarding/mock.d.ts +8 -0
  156. package/lib/postOnboarding/mock.d.ts.map +1 -0
  157. package/lib/postOnboarding/mock.js +46 -0
  158. package/lib/postOnboarding/mock.js.map +1 -0
  159. package/lib/postOnboarding/reducer.d.ts +22 -0
  160. package/lib/postOnboarding/reducer.d.ts.map +1 -0
  161. package/lib/postOnboarding/reducer.js +71 -0
  162. package/lib/postOnboarding/reducer.js.map +1 -0
  163. package/lib/postOnboarding/reducer.test.d.ts +2 -0
  164. package/lib/postOnboarding/reducer.test.d.ts.map +1 -0
  165. package/lib/postOnboarding/reducer.test.js +251 -0
  166. package/lib/postOnboarding/reducer.test.js.map +1 -0
  167. package/package.json +8 -4
  168. package/src/bot/index.ts +11 -12
  169. package/src/bot/specs.ts +45 -0
  170. package/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap +1 -35
  171. package/src/currencies/support.ts +2 -1
  172. package/src/derivation.ts +2 -4
  173. package/src/env.ts +1 -1
  174. package/src/exchange/swap/getExchangeRates.ts +7 -3
  175. package/src/exchange/swap/getProviders.test.ts +450 -0
  176. package/src/exchange/swap/getProviders.ts +31 -8
  177. package/src/exchange/swap/hooks/useProviderRates.test.ts +43 -0
  178. package/src/exchange/swap/hooks/useProviderRates.ts +22 -3
  179. package/src/exchange/swap/hooks/useSwapTransaction.ts +3 -1
  180. package/src/exchange/swap/index.test.ts +36 -1
  181. package/src/exchange/swap/index.ts +15 -0
  182. package/src/exchange/swap/mock.ts +2 -2
  183. package/src/exchange/swap/types.ts +11 -0
  184. package/src/exchange/swap/utils/index.test.ts +19 -0
  185. package/src/exchange/swap/utils/index.ts +5 -0
  186. package/src/families/algorand/speculos-deviceActions.ts +4 -35
  187. package/src/families/bitcoin/specs.ts +0 -1
  188. package/src/families/bitcoin/speculos-deviceActions.ts +3 -26
  189. package/src/families/cardano/specs.ts +4 -3
  190. package/src/families/cardano/speculos-deviceActions.ts +3 -14
  191. package/src/families/celo/speculos-deviceActions.ts +6 -14
  192. package/src/families/elrond/speculos-deviceActions.ts +4 -9
  193. package/src/families/ethereum/speculos-deviceActions.ts +16 -46
  194. package/src/families/evm/specs.ts +12 -8
  195. package/src/families/evm/speculos-deviceActions.ts +7 -35
  196. package/src/families/filecoin/speculos-deviceActions.ts +7 -8
  197. package/src/families/polkadot/speculos-deviceActions.ts +5 -16
  198. package/src/families/ripple/speculos-deviceActions.ts +3 -28
  199. package/src/families/solana/speculos-deviceActions.ts +6 -8
  200. package/src/families/stellar/speculos-deviceActions.ts +5 -11
  201. package/src/families/tezos/speculos-deviceActions.ts +9 -16
  202. package/src/families/tron/speculos-deviceActions.ts +4 -12
  203. package/src/featureFlags/defaultFeatures.ts +3 -0
  204. package/src/hw/extractOnboardingState.test.ts +29 -12
  205. package/src/hw/extractOnboardingState.ts +17 -9
  206. package/src/postOnboarding/PostOnboardingProvider.tsx +49 -0
  207. package/src/postOnboarding/actions.ts +40 -0
  208. package/src/postOnboarding/hooks/index.ts +5 -0
  209. package/src/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.test.ts +82 -0
  210. package/src/postOnboarding/hooks/useAllPostOnboardingActionsCompleted.ts +12 -0
  211. package/src/postOnboarding/hooks/usePostOnboardingContext.ts +9 -0
  212. package/src/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.test.ts +83 -0
  213. package/src/postOnboarding/hooks/usePostOnboardingEntryPointVisibleOnWallet.ts +22 -0
  214. package/src/postOnboarding/hooks/usePostOnboardingHubState.test.ts +184 -0
  215. package/src/postOnboarding/hooks/usePostOnboardingHubState.ts +56 -0
  216. package/src/postOnboarding/hooks/useStartPostOnboardingCallback.ts +38 -0
  217. package/src/postOnboarding/mock.ts +53 -0
  218. package/src/postOnboarding/reducer.test.ts +247 -0
  219. package/src/postOnboarding/reducer.ts +105 -0
@@ -0,0 +1,184 @@
1
+ import { useFeatureFlags } from "../../featureFlags";
2
+ import { hubStateSelector } from "../reducer";
3
+ import { usePostOnboardingContext } from "./usePostOnboardingContext";
4
+ import { getPostOnboardingAction, mockedFeatureIdToTest } from "../mock";
5
+ import { renderHook } from "@testing-library/react-hooks";
6
+ import { DeviceModelId } from "@ledgerhq/types-devices";
7
+ import { PostOnboardingActionId } from "@ledgerhq/types-live";
8
+ import { usePostOnboardingHubState } from "./usePostOnboardingHubState";
9
+
10
+ jest.mock("react-redux", () => ({
11
+ useSelector: (val) => val(),
12
+ }));
13
+ jest.mock("../../featureFlags");
14
+ jest.mock("./usePostOnboardingContext");
15
+ jest.mock("../reducer");
16
+
17
+ const mockedUseFeatureFlags = jest.mocked(useFeatureFlags);
18
+ const mockedGetFeatureWithMockFeatureEnabled = (enabled) => ({
19
+ getFeature: (id) => {
20
+ if (id === mockedFeatureIdToTest) return { enabled };
21
+ return { enabled: true };
22
+ },
23
+ overrideFeature: () => {},
24
+ resetFeature: () => {},
25
+ });
26
+
27
+ const mockedUsePostOnboardingContext = jest.mocked(usePostOnboardingContext);
28
+
29
+ const mockedHubStateSelector = jest.mocked(hubStateSelector);
30
+
31
+ const defaultHubState = {
32
+ deviceModelId: DeviceModelId.nanoX,
33
+ actionsToComplete: [],
34
+ actionsCompleted: {},
35
+ lastActionCompleted: null,
36
+ };
37
+
38
+ const stateAllCompleted = {
39
+ deviceModelId: DeviceModelId.nanoX,
40
+ actionsToComplete: [
41
+ PostOnboardingActionId.claimMock,
42
+ PostOnboardingActionId.personalizeMock,
43
+ PostOnboardingActionId.migrateAssetsMock,
44
+ ],
45
+ actionsCompleted: {
46
+ [PostOnboardingActionId.claimMock]: true,
47
+ [PostOnboardingActionId.personalizeMock]: true,
48
+ [PostOnboardingActionId.migrateAssetsMock]: true,
49
+ },
50
+ lastActionCompleted: PostOnboardingActionId.personalizeMock,
51
+ };
52
+
53
+ const stateAllNotCompleted = {
54
+ deviceModelId: DeviceModelId.nanoX,
55
+ actionsToComplete: [
56
+ PostOnboardingActionId.claimMock,
57
+ PostOnboardingActionId.personalizeMock,
58
+ PostOnboardingActionId.migrateAssetsMock,
59
+ ],
60
+ actionsCompleted: {
61
+ [PostOnboardingActionId.claimMock]: false,
62
+ [PostOnboardingActionId.personalizeMock]: false,
63
+ [PostOnboardingActionId.migrateAssetsMock]: false,
64
+ },
65
+ lastActionCompleted: null,
66
+ };
67
+
68
+ describe("usePostOnboardingHubState", () => {
69
+ beforeEach(() => {
70
+ mockedUseFeatureFlags.mockReturnValue(
71
+ mockedGetFeatureWithMockFeatureEnabled(true)
72
+ );
73
+ mockedUsePostOnboardingContext.mockReturnValue({
74
+ getPostOnboardingActionsForDevice: () => [],
75
+ navigateToPostOnboardingHub: () => {},
76
+ getPostOnboardingAction,
77
+ });
78
+ });
79
+
80
+ it("should return an empty state if the context isn't properly set (`getPostOnboardingAction` missing)", () => {
81
+ const state = stateAllCompleted;
82
+ mockedHubStateSelector.mockReturnValue(state);
83
+ mockedUsePostOnboardingContext.mockReturnValue({
84
+ getPostOnboardingActionsForDevice: () => [],
85
+ navigateToPostOnboardingHub: () => {},
86
+ getPostOnboardingAction: undefined,
87
+ });
88
+
89
+ const { result } = renderHook(() => usePostOnboardingHubState());
90
+
91
+ expect(result.current).toEqual({
92
+ deviceModelId: state.deviceModelId,
93
+ lastActionCompleted: null,
94
+ actionsState: [],
95
+ });
96
+ });
97
+
98
+ it("should return an empty state if the store is in an empty state", () => {
99
+ const state = defaultHubState;
100
+ mockedHubStateSelector.mockReturnValue(state);
101
+
102
+ const { result } = renderHook(() => usePostOnboardingHubState());
103
+
104
+ expect(result.current).toEqual({
105
+ deviceModelId: state.deviceModelId,
106
+ lastActionCompleted: null,
107
+ actionsState: [],
108
+ });
109
+ });
110
+
111
+ it("should not return actions that have a disabled feature flag ", () => {
112
+ const state = stateAllCompleted;
113
+ mockedHubStateSelector.mockReturnValue(state);
114
+ mockedUseFeatureFlags.mockReturnValue(
115
+ mockedGetFeatureWithMockFeatureEnabled(false)
116
+ );
117
+
118
+ const {
119
+ result: {
120
+ current: { actionsState, lastActionCompleted },
121
+ },
122
+ } = renderHook(() => usePostOnboardingHubState());
123
+
124
+ expect(
125
+ actionsState.find(
126
+ (action) => action.featureFlagId === mockedFeatureIdToTest
127
+ )
128
+ ).toBe(undefined);
129
+ expect(lastActionCompleted).toBe(null);
130
+ });
131
+
132
+ it("should return actions that have a feature flag enabled", () => {
133
+ const state = stateAllCompleted;
134
+ mockedHubStateSelector.mockReturnValue(state);
135
+ mockedUseFeatureFlags.mockReturnValue(
136
+ mockedGetFeatureWithMockFeatureEnabled(true)
137
+ );
138
+
139
+ const {
140
+ result: {
141
+ current: { actionsState, lastActionCompleted },
142
+ },
143
+ } = renderHook(() => usePostOnboardingHubState());
144
+
145
+ expect(
146
+ actionsState.find(
147
+ (action) => action.featureFlagId === mockedFeatureIdToTest
148
+ )
149
+ ).toBeTruthy();
150
+ expect(lastActionCompleted).toBeTruthy();
151
+ });
152
+
153
+ it("should return actions in their correct state (all actions completed)", () => {
154
+ const state = stateAllCompleted;
155
+ mockedHubStateSelector.mockReturnValue(state);
156
+ mockedUseFeatureFlags.mockReturnValue(
157
+ mockedGetFeatureWithMockFeatureEnabled(true)
158
+ );
159
+
160
+ const {
161
+ result: {
162
+ current: { actionsState },
163
+ },
164
+ } = renderHook(() => usePostOnboardingHubState());
165
+
166
+ expect(actionsState.every((action) => action.completed)).toBe(true);
167
+ });
168
+
169
+ it("should return actions in their correct state (no actions completed)", () => {
170
+ const state = stateAllNotCompleted;
171
+ mockedHubStateSelector.mockReturnValue(state);
172
+ mockedUseFeatureFlags.mockReturnValue(
173
+ mockedGetFeatureWithMockFeatureEnabled(true)
174
+ );
175
+
176
+ const {
177
+ result: {
178
+ current: { actionsState },
179
+ },
180
+ } = renderHook(() => usePostOnboardingHubState());
181
+
182
+ expect(actionsState.every((action) => action.completed)).toBe(false);
183
+ });
184
+ });
@@ -0,0 +1,56 @@
1
+ import { useSelector } from "react-redux";
2
+ import { useMemo } from "react";
3
+ import { PostOnboardingHubState } from "@ledgerhq/types-live";
4
+ import { useFeatureFlags } from "../../featureFlags";
5
+ import { hubStateSelector } from "../reducer";
6
+ import { usePostOnboardingContext } from "./usePostOnboardingContext";
7
+
8
+ /**
9
+ * @returns an object representing the state that should be rendered on the post
10
+ * onboarding hub screen.
11
+ *
12
+ * This takes feature flagging into account so the logic is
13
+ * resistant to flags getting enabled/disabled over time (for a given disabled
14
+ * feature flag, the actions pointing to it will be excluded).
15
+ * TODO: unit test this
16
+ * */
17
+ export function usePostOnboardingHubState(): PostOnboardingHubState {
18
+ const hubState = useSelector(hubStateSelector);
19
+ const postOnboardingContext = usePostOnboardingContext();
20
+ const { getPostOnboardingAction } = postOnboardingContext;
21
+ const { getFeature } = useFeatureFlags();
22
+ return useMemo(() => {
23
+ if (!getPostOnboardingAction)
24
+ return {
25
+ deviceModelId: hubState.deviceModelId,
26
+ lastActionCompleted: null,
27
+ actionsState: [],
28
+ };
29
+ const actionsState = hubState.actionsToComplete
30
+ .map((actionId) => ({
31
+ ...getPostOnboardingAction(actionId),
32
+ completed: !!hubState.actionsCompleted[actionId],
33
+ }))
34
+ .filter(
35
+ (actionWithState) =>
36
+ !actionWithState.featureFlagId ||
37
+ getFeature(actionWithState.featureFlagId)?.enabled
38
+ );
39
+ const lastActionCompleted = hubState.lastActionCompleted
40
+ ? getPostOnboardingAction(hubState.lastActionCompleted)
41
+ : null;
42
+
43
+ const isLastActionCompletedEnabled =
44
+ lastActionCompleted &&
45
+ (!lastActionCompleted.featureFlagId ||
46
+ getFeature(lastActionCompleted.featureFlagId)?.enabled);
47
+
48
+ return {
49
+ deviceModelId: hubState.deviceModelId,
50
+ lastActionCompleted: isLastActionCompletedEnabled
51
+ ? lastActionCompleted
52
+ : null,
53
+ actionsState,
54
+ };
55
+ }, [getFeature, hubState, getPostOnboardingAction]);
56
+ }
@@ -0,0 +1,38 @@
1
+ import { useDispatch } from "react-redux";
2
+ import { DeviceModelId } from "@ledgerhq/types-devices";
3
+ import { usePostOnboardingContext } from "./usePostOnboardingContext";
4
+ import { useCallback, useMemo } from "react";
5
+ import { initPostOnboarding } from "../actions";
6
+
7
+ /**
8
+ * Use this to initialize AND navigate to the post onboarding hub for a given
9
+ * device model.
10
+ *
11
+ * @param deviceModelId
12
+ * @returns a function that can be called to initialize the post
13
+ * onboarding for the given device model and navigate to the post onboarding
14
+ * hub.
15
+ * TODO: unit test this
16
+ */
17
+ export function useStartPostOnboardingCallback(
18
+ deviceModelId: DeviceModelId,
19
+ mock = false
20
+ ): () => void {
21
+ const dispatch = useDispatch();
22
+ const { getPostOnboardingActionsForDevice, navigateToPostOnboardingHub } =
23
+ usePostOnboardingContext();
24
+ const actions = useMemo(
25
+ () => getPostOnboardingActionsForDevice(deviceModelId, mock),
26
+ [deviceModelId, mock, getPostOnboardingActionsForDevice]
27
+ );
28
+ return useCallback(() => {
29
+ dispatch(
30
+ initPostOnboarding({
31
+ deviceModelId,
32
+ actionsIds: actions.map((action) => action.id),
33
+ })
34
+ );
35
+ if (actions.length === 0) return;
36
+ navigateToPostOnboardingHub();
37
+ }, [actions, deviceModelId, dispatch, navigateToPostOnboardingHub]);
38
+ }
@@ -0,0 +1,53 @@
1
+ /** mocks for unit tests */
2
+
3
+ import {
4
+ FeatureId,
5
+ PostOnboardingAction,
6
+ PostOnboardingActionId,
7
+ } from "@ledgerhq/types-live";
8
+
9
+ const MockIcon = (): null => null;
10
+
11
+ export const mockedFeatureIdToTest: FeatureId = "mockFeature";
12
+
13
+ export const claimTestMock: PostOnboardingAction = {
14
+ id: PostOnboardingActionId.claimMock,
15
+ Icon: MockIcon,
16
+ title: "Claim my NFT",
17
+ description: "A special NFT for you.",
18
+ tagLabel: "Free",
19
+ actionCompletedPopupLabel: "NFT claimed",
20
+ actionCompletedHubTitle: "Kickstart your Web3 journey.",
21
+ navigationParams: [],
22
+ };
23
+
24
+ export const personalizeTestMock: PostOnboardingAction = {
25
+ id: PostOnboardingActionId.personalizeMock,
26
+ Icon: MockIcon,
27
+ featureFlagId: mockedFeatureIdToTest,
28
+ title: `Personalize my device`,
29
+ description: "By customizing the screen.",
30
+ actionCompletedPopupLabel: "Device personalized",
31
+ actionCompletedHubTitle: "That screen is looking neat.",
32
+ navigationParams: [],
33
+ };
34
+
35
+ export const migrateAssetsTestMock: PostOnboardingAction = {
36
+ id: PostOnboardingActionId.migrateAssetsMock,
37
+ Icon: MockIcon,
38
+ title: "Transfer assets to my Ledger",
39
+ description: "Easily secure assets from coinbase or another exchange.",
40
+ actionCompletedPopupLabel: "Assets transfered",
41
+ actionCompletedHubTitle: "Something about being a crypto pro.",
42
+ navigationParams: [],
43
+ };
44
+
45
+ export function getPostOnboardingAction(
46
+ id: PostOnboardingActionId
47
+ ): PostOnboardingAction {
48
+ return {
49
+ [PostOnboardingActionId.claimMock]: claimTestMock,
50
+ [PostOnboardingActionId.personalizeMock]: personalizeTestMock,
51
+ [PostOnboardingActionId.migrateAssetsMock]: migrateAssetsTestMock,
52
+ }[id];
53
+ }
@@ -0,0 +1,247 @@
1
+ import { DeviceModelId } from "@ledgerhq/types-devices";
2
+ import {
3
+ PostOnboardingActionId,
4
+ PostOnboardingState,
5
+ } from "@ledgerhq/types-live";
6
+ import reducer, { initialState } from "./reducer";
7
+
8
+ import {
9
+ importPostOnboardingState,
10
+ initPostOnboarding,
11
+ setPostOnboardingActionCompleted,
12
+ clearPostOnboardingLastActionCompleted,
13
+ hidePostOnboardingWalletEntryPoint,
14
+ } from "./actions";
15
+
16
+ const initializationParamsA: Parameters<typeof initPostOnboarding> = [
17
+ {
18
+ deviceModelId: DeviceModelId.nanoX,
19
+ actionsIds: [
20
+ PostOnboardingActionId.claimMock,
21
+ PostOnboardingActionId.migrateAssetsMock,
22
+ PostOnboardingActionId.personalizeMock,
23
+ ],
24
+ },
25
+ ];
26
+
27
+ // initialState -> importPostOnboardingState(...initializationParamsA)
28
+ const stateA0: PostOnboardingState = {
29
+ deviceModelId: DeviceModelId.nanoX,
30
+ walletEntryPointDismissed: false,
31
+ actionsToComplete: [
32
+ PostOnboardingActionId.claimMock,
33
+ PostOnboardingActionId.migrateAssetsMock,
34
+ PostOnboardingActionId.personalizeMock,
35
+ ],
36
+ actionsCompleted: {
37
+ [PostOnboardingActionId.claimMock]: false,
38
+ [PostOnboardingActionId.migrateAssetsMock]: false,
39
+ [PostOnboardingActionId.personalizeMock]: false,
40
+ },
41
+ lastActionCompleted: null,
42
+ };
43
+
44
+ // stateA0 -> setPostOnboardingActionCompleted(claimMock)
45
+ const stateA1: PostOnboardingState = {
46
+ deviceModelId: DeviceModelId.nanoX,
47
+ walletEntryPointDismissed: false,
48
+ actionsToComplete: [
49
+ PostOnboardingActionId.claimMock,
50
+ PostOnboardingActionId.migrateAssetsMock,
51
+ PostOnboardingActionId.personalizeMock,
52
+ ],
53
+ actionsCompleted: {
54
+ [PostOnboardingActionId.claimMock]: true, // stateA0 -> setPostOnboardingActionCompleted(claimMock)
55
+ [PostOnboardingActionId.migrateAssetsMock]: false,
56
+ [PostOnboardingActionId.personalizeMock]: false,
57
+ },
58
+ lastActionCompleted: PostOnboardingActionId.claimMock, // stateA0 -> setPostOnboardingActionCompleted(claimMock)
59
+ };
60
+
61
+ // stateA1 -> clearPostOnboardingLastActionCompleted()
62
+ const stateA2: PostOnboardingState = {
63
+ deviceModelId: DeviceModelId.nanoX,
64
+ walletEntryPointDismissed: false,
65
+ actionsToComplete: [
66
+ PostOnboardingActionId.claimMock,
67
+ PostOnboardingActionId.migrateAssetsMock,
68
+ PostOnboardingActionId.personalizeMock,
69
+ ],
70
+ actionsCompleted: {
71
+ [PostOnboardingActionId.claimMock]: true,
72
+ [PostOnboardingActionId.migrateAssetsMock]: false,
73
+ [PostOnboardingActionId.personalizeMock]: false,
74
+ },
75
+ lastActionCompleted: null, // stateA1 -> clearPostOnboardingLastActionCompleted()
76
+ };
77
+
78
+ // stateA2 -> setPostOnboardingActionCompleted(personalizeMock)
79
+ const stateA3: PostOnboardingState = {
80
+ deviceModelId: DeviceModelId.nanoX,
81
+ walletEntryPointDismissed: false,
82
+ actionsToComplete: [
83
+ PostOnboardingActionId.claimMock,
84
+ PostOnboardingActionId.migrateAssetsMock,
85
+ PostOnboardingActionId.personalizeMock,
86
+ ],
87
+ actionsCompleted: {
88
+ [PostOnboardingActionId.claimMock]: true,
89
+ [PostOnboardingActionId.migrateAssetsMock]: false,
90
+ [PostOnboardingActionId.personalizeMock]: true, // stateA2 -> setPostOnboardingActionCompleted(personalizeMock)
91
+ },
92
+ lastActionCompleted: PostOnboardingActionId.personalizeMock, // stateA2 -> setPostOnboardingActionCompleted(personalizeMock)
93
+ };
94
+
95
+ // stateA3 -> hidePostOnboardingWalletEntryPoint()
96
+ const stateA4: PostOnboardingState = {
97
+ deviceModelId: DeviceModelId.nanoX,
98
+ walletEntryPointDismissed: true, // stateA3 -> hidePostOnboardingWalletEntryPoint()
99
+ actionsToComplete: [
100
+ PostOnboardingActionId.claimMock,
101
+ PostOnboardingActionId.migrateAssetsMock,
102
+ PostOnboardingActionId.personalizeMock,
103
+ ],
104
+ actionsCompleted: {
105
+ [PostOnboardingActionId.claimMock]: true,
106
+ [PostOnboardingActionId.migrateAssetsMock]: false,
107
+ [PostOnboardingActionId.personalizeMock]: true,
108
+ },
109
+ lastActionCompleted: PostOnboardingActionId.personalizeMock,
110
+ };
111
+
112
+ const initializationParamsB: Parameters<typeof initPostOnboarding> = [
113
+ {
114
+ deviceModelId: DeviceModelId.nanoS,
115
+ actionsIds: [PostOnboardingActionId.claimMock],
116
+ },
117
+ ];
118
+
119
+ // initialState -> importPostOnboardingState(...initializationParamsB)
120
+ const stateB0 = {
121
+ deviceModelId: DeviceModelId.nanoS,
122
+ walletEntryPointDismissed: false,
123
+ actionsToComplete: [PostOnboardingActionId.claimMock],
124
+ actionsCompleted: { [PostOnboardingActionId.claimMock]: false },
125
+ lastActionCompleted: null,
126
+ };
127
+
128
+ // stateB0 -> setPostOnboardingActionCompleted(claimMock)
129
+ const stateB1 = {
130
+ deviceModelId: DeviceModelId.nanoS,
131
+ walletEntryPointDismissed: false,
132
+ actionsToComplete: [PostOnboardingActionId.claimMock],
133
+ actionsCompleted: { [PostOnboardingActionId.claimMock]: true },
134
+ lastActionCompleted: PostOnboardingActionId.claimMock,
135
+ };
136
+
137
+ const initializationParamsC: Parameters<typeof initPostOnboarding> = [
138
+ {
139
+ deviceModelId: DeviceModelId.nanoSP,
140
+ actionsIds: [],
141
+ },
142
+ ];
143
+
144
+ // initialState -> importPostOnboardingState(...initializationParamsC)
145
+ const stateC0 = {
146
+ deviceModelId: DeviceModelId.nanoSP,
147
+ walletEntryPointDismissed: false,
148
+ actionsToComplete: [],
149
+ actionsCompleted: {},
150
+ lastActionCompleted: null,
151
+ };
152
+
153
+ describe("postOnboarding reducer (& action creators)", () => {
154
+ let state;
155
+ beforeEach(() => {
156
+ // reset state to initial state;
157
+ state = reducer(undefined, {});
158
+ });
159
+
160
+ it("should initialize the state properly", () => {
161
+ expect(state).toEqual(initialState);
162
+ });
163
+
164
+ it("should handle importPostOnboardingState", () => {
165
+ state = reducer(state, importPostOnboardingState({ newState: stateA0 }));
166
+ expect(state).toEqual(stateA0);
167
+ state = reducer(state, importPostOnboardingState({ newState: stateA1 }));
168
+ expect(state).toEqual(stateA1);
169
+ });
170
+
171
+ it("it should handle initPostOnboarding", () => {
172
+ state = reducer(state, initPostOnboarding(...initializationParamsA));
173
+ expect(state).toEqual(stateA0);
174
+ });
175
+
176
+ it("it should handle setPostOnboardingActionCompleted", () => {
177
+ state = stateA0;
178
+ state = reducer(
179
+ state,
180
+ setPostOnboardingActionCompleted({
181
+ actionId: PostOnboardingActionId.claimMock,
182
+ })
183
+ );
184
+ expect(state).toEqual(stateA1);
185
+ });
186
+
187
+ it("it should handle clearPostOnboardingLastActionCompleted", () => {
188
+ state = stateA1;
189
+ state = reducer(state, clearPostOnboardingLastActionCompleted());
190
+ expect(state).toEqual(stateA2);
191
+ });
192
+
193
+ it("it should handle hidePostOnboardingWalletEntryPoint", () => {
194
+ state = stateA3;
195
+ state = reducer(state, hidePostOnboardingWalletEntryPoint());
196
+ expect(state).toEqual(stateA4);
197
+ });
198
+
199
+ it("it should handle successive actions properly", () => {
200
+ // initializing state with new device & set of actions
201
+ state = reducer(state, initPostOnboarding(...initializationParamsA));
202
+ expect(state).toEqual(stateA0);
203
+
204
+ // setting completed for claimMock
205
+ state = reducer(
206
+ state,
207
+ setPostOnboardingActionCompleted({
208
+ actionId: PostOnboardingActionId.claimMock,
209
+ })
210
+ );
211
+ expect(state).toEqual(stateA1);
212
+
213
+ // clearing last completed action
214
+ state = reducer(state, clearPostOnboardingLastActionCompleted());
215
+ expect(state).toEqual(stateA2);
216
+
217
+ // setting completed for personalizeMock
218
+ state = reducer(
219
+ state,
220
+ setPostOnboardingActionCompleted({
221
+ actionId: PostOnboardingActionId.personalizeMock,
222
+ })
223
+ );
224
+ expect(state).toEqual(stateA3);
225
+
226
+ // hiding wallet entrypoint
227
+ state = reducer(state, hidePostOnboardingWalletEntryPoint());
228
+ expect(state).toEqual(stateA4);
229
+
230
+ // initializing state with new device & set of actions
231
+ state = reducer(state, initPostOnboarding(...initializationParamsB));
232
+ expect(state).toEqual(stateB0);
233
+
234
+ // setting completed for claimMock
235
+ state = reducer(
236
+ state,
237
+ setPostOnboardingActionCompleted({
238
+ actionId: PostOnboardingActionId.claimMock,
239
+ })
240
+ );
241
+ expect(state).toEqual(stateB1);
242
+
243
+ // initializing state with new device & set of actions
244
+ state = reducer(state, initPostOnboarding(...initializationParamsC));
245
+ expect(state).toEqual(stateC0);
246
+ });
247
+ });
@@ -0,0 +1,105 @@
1
+ import { DeviceModelId } from "@ledgerhq/types-devices";
2
+ import {
3
+ PostOnboardingActionId,
4
+ PostOnboardingState,
5
+ } from "@ledgerhq/types-live";
6
+ import { handleActions } from "redux-actions";
7
+
8
+ export const initialState: PostOnboardingState = {
9
+ deviceModelId: null,
10
+ walletEntryPointDismissed: false,
11
+ actionsToComplete: [],
12
+ actionsCompleted: {},
13
+ lastActionCompleted: null,
14
+ };
15
+
16
+ const handlers: Record<
17
+ string,
18
+ (state: PostOnboardingState, action: any) => PostOnboardingState
19
+ > = {
20
+ POST_ONBOARDING_IMPORT_STATE: (
21
+ _,
22
+ action: { newState: Record<string, any> }
23
+ ): PostOnboardingState => action.newState as PostOnboardingState,
24
+ POST_ONBOARDING_INIT: (
25
+ _,
26
+ action: {
27
+ deviceModelId: DeviceModelId;
28
+ actionsIds: PostOnboardingActionId[];
29
+ }
30
+ ) => {
31
+ const { deviceModelId, actionsIds } = action;
32
+ return {
33
+ deviceModelId,
34
+ walletEntryPointDismissed: false,
35
+ actionsToComplete: actionsIds,
36
+ actionsCompleted: Object.fromEntries(actionsIds.map((id) => [id, false])),
37
+ lastActionCompleted: null,
38
+ };
39
+ },
40
+ POST_ONBOARDING_SET_ACTION_COMPLETED: (
41
+ state,
42
+ action: { actionId: PostOnboardingActionId }
43
+ ) => {
44
+ const { actionId } = action;
45
+ const actionsCompleted = { ...state.actionsCompleted, [actionId]: true };
46
+ return {
47
+ ...state,
48
+ actionsCompleted,
49
+ lastActionCompleted: actionId,
50
+ };
51
+ },
52
+ POST_ONBOARDING_CLEAR_LAST_ACTION_COMPLETED: (state) => ({
53
+ ...state,
54
+ lastActionCompleted: null,
55
+ }),
56
+ POST_ONBOARDING_HIDE_WALLET_ENTRY_POINT: (state) => ({
57
+ ...state,
58
+ walletEntryPointDismissed: true,
59
+ }),
60
+ };
61
+
62
+ export default handleActions(handlers, initialState);
63
+
64
+ export const postOnboardingSelector = ({
65
+ postOnboarding,
66
+ }: {
67
+ postOnboarding: PostOnboardingState;
68
+ }): PostOnboardingState => postOnboarding;
69
+
70
+ export const hubStateSelector = ({
71
+ postOnboarding,
72
+ }: {
73
+ postOnboarding: PostOnboardingState;
74
+ }): {
75
+ deviceModelId: PostOnboardingState["deviceModelId"];
76
+ actionsToComplete: PostOnboardingState["actionsToComplete"];
77
+ actionsCompleted: PostOnboardingState["actionsCompleted"];
78
+ lastActionCompleted: PostOnboardingState["lastActionCompleted"];
79
+ } => {
80
+ const {
81
+ deviceModelId,
82
+ actionsToComplete,
83
+ actionsCompleted,
84
+ lastActionCompleted,
85
+ } = postOnboarding;
86
+ return {
87
+ deviceModelId,
88
+ actionsToComplete,
89
+ actionsCompleted,
90
+ lastActionCompleted,
91
+ };
92
+ };
93
+
94
+ export const postOnboardingDeviceModelIdSelector = ({
95
+ postOnboarding,
96
+ }: {
97
+ postOnboarding: PostOnboardingState;
98
+ }): PostOnboardingState["deviceModelId"] => postOnboarding.deviceModelId;
99
+
100
+ export const walletPostOnboardingEntryPointDismissedSelector = ({
101
+ postOnboarding,
102
+ }: {
103
+ postOnboarding: PostOnboardingState;
104
+ }): PostOnboardingState["walletEntryPointDismissed"] =>
105
+ postOnboarding.walletEntryPointDismissed;