@smileid/usesmileid 12.0.0 → 12.0.2

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 (365) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/dist/camera/camera_defaults.d.ts +2 -0
  4. package/dist/camera/camera_defaults.d.ts.map +1 -1
  5. package/dist/camera/camera_defaults.js +2 -0
  6. package/dist/camera/camera_defaults.js.map +1 -1
  7. package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.d.ts +11 -0
  8. package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.d.ts.map +1 -0
  9. package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.js +17 -0
  10. package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.js.map +1 -0
  11. package/dist/core/exception/capture_validation_exceptions.d.ts +1 -0
  12. package/dist/core/exception/capture_validation_exceptions.d.ts.map +1 -1
  13. package/dist/core/exception/capture_validation_exceptions.js +1 -0
  14. package/dist/core/exception/capture_validation_exceptions.js.map +1 -1
  15. package/dist/core/exception/flow_exceptions.d.ts +4 -2
  16. package/dist/core/exception/flow_exceptions.d.ts.map +1 -1
  17. package/dist/core/exception/flow_exceptions.js +3 -2
  18. package/dist/core/exception/flow_exceptions.js.map +1 -1
  19. package/dist/core/exception/index.d.ts +1 -0
  20. package/dist/core/exception/index.d.ts.map +1 -1
  21. package/dist/core/exception/index.js +1 -0
  22. package/dist/core/exception/index.js.map +1 -1
  23. package/dist/core/exception/missing_job_type_exception.d.ts +1 -3
  24. package/dist/core/exception/missing_job_type_exception.d.ts.map +1 -1
  25. package/dist/core/exception/missing_job_type_exception.js +2 -4
  26. package/dist/core/exception/missing_job_type_exception.js.map +1 -1
  27. package/dist/core/exception/use_smile_id_error_code.d.ts +1 -0
  28. package/dist/core/exception/use_smile_id_error_code.d.ts.map +1 -1
  29. package/dist/core/exception/use_smile_id_error_code.js +1 -0
  30. package/dist/core/exception/use_smile_id_error_code.js.map +1 -1
  31. package/dist/core/models/config.d.ts +19 -0
  32. package/dist/core/models/config.d.ts.map +1 -1
  33. package/dist/core/models/document_type.d.ts +1 -1
  34. package/dist/core/models/document_type.d.ts.map +1 -1
  35. package/dist/core/models/document_type.js +2 -2
  36. package/dist/core/models/document_type.js.map +1 -1
  37. package/dist/core/models/global_privacy_control.d.ts +36 -0
  38. package/dist/core/models/global_privacy_control.d.ts.map +1 -0
  39. package/dist/core/models/global_privacy_control.js +39 -0
  40. package/dist/core/models/global_privacy_control.js.map +1 -0
  41. package/dist/core/models/network_config.d.ts +2 -2
  42. package/dist/core/models/network_config.js +2 -2
  43. package/dist/data/api/interceptors/smile_auth_interceptor.d.ts.map +1 -1
  44. package/dist/data/api/interceptors/smile_auth_interceptor.js +3 -17
  45. package/dist/data/api/interceptors/smile_auth_interceptor.js.map +1 -1
  46. package/dist/data/api/smile_id_api.d.ts +5 -1
  47. package/dist/data/api/smile_id_api.d.ts.map +1 -1
  48. package/dist/data/api/smile_id_api.js +30 -27
  49. package/dist/data/api/smile_id_api.js.map +1 -1
  50. package/dist/data/dsl/builder/retry_config_builder.d.ts +4 -4
  51. package/dist/data/dsl/builder/retry_config_builder.d.ts.map +1 -1
  52. package/dist/data/dsl/builder/retry_config_builder.js +6 -6
  53. package/dist/data/dsl/builder/retry_config_builder.js.map +1 -1
  54. package/dist/data/model/job_request.d.ts +28 -12
  55. package/dist/data/model/job_request.d.ts.map +1 -1
  56. package/dist/data/model/token_payload.d.ts +78 -0
  57. package/dist/data/model/token_payload.d.ts.map +1 -0
  58. package/dist/data/model/token_payload.js +132 -0
  59. package/dist/data/model/token_payload.js.map +1 -0
  60. package/dist/data/model/user_details_payload.d.ts +39 -0
  61. package/dist/data/model/user_details_payload.d.ts.map +1 -0
  62. package/dist/data/model/user_details_payload.js +53 -0
  63. package/dist/data/model/user_details_payload.js.map +1 -0
  64. package/dist/data/smile_id_urls.d.ts +1 -1
  65. package/dist/data/smile_id_urls.js +1 -1
  66. package/dist/data/smile_id_urls.js.map +1 -1
  67. package/dist/data/util/retry_util.d.ts +1 -1
  68. package/dist/data/util/retry_util.d.ts.map +1 -1
  69. package/dist/data/util/retry_util.js +5 -0
  70. package/dist/data/util/retry_util.js.map +1 -1
  71. package/dist/data/util/use_smile_id_token_decoder.d.ts +15 -3
  72. package/dist/data/util/use_smile_id_token_decoder.d.ts.map +1 -1
  73. package/dist/data/util/use_smile_id_token_decoder.js +20 -8
  74. package/dist/data/util/use_smile_id_token_decoder.js.map +1 -1
  75. package/dist/index.d.ts +2 -2
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +2 -2
  78. package/dist/index.js.map +1 -1
  79. package/dist/l10n/strings_en.d.ts +4 -1
  80. package/dist/l10n/strings_en.d.ts.map +1 -1
  81. package/dist/l10n/strings_en.js +4 -1
  82. package/dist/l10n/strings_en.js.map +1 -1
  83. package/dist/l10n/use_smile_id_localizations.d.ts +10 -0
  84. package/dist/l10n/use_smile_id_localizations.d.ts.map +1 -1
  85. package/dist/l10n/use_smile_id_localizations.js +59 -3
  86. package/dist/l10n/use_smile_id_localizations.js.map +1 -1
  87. package/dist/metadata/builder_configuration.d.ts +5 -0
  88. package/dist/metadata/builder_configuration.d.ts.map +1 -1
  89. package/dist/metadata/builder_configuration.js +1 -0
  90. package/dist/metadata/builder_configuration.js.map +1 -1
  91. package/dist/metadata/metadata_configuration_mappers.d.ts +2 -3
  92. package/dist/metadata/metadata_configuration_mappers.d.ts.map +1 -1
  93. package/dist/metadata/metadata_configuration_mappers.js +13 -6
  94. package/dist/metadata/metadata_configuration_mappers.js.map +1 -1
  95. package/dist/metadata/use_smile_id_metadata_factory.d.ts.map +1 -1
  96. package/dist/metadata/use_smile_id_metadata_factory.js +14 -5
  97. package/dist/metadata/use_smile_id_metadata_factory.js.map +1 -1
  98. package/dist/presentation/components/use_smile_id_button.d.ts.map +1 -1
  99. package/dist/presentation/components/use_smile_id_button.js +8 -2
  100. package/dist/presentation/components/use_smile_id_button.js.map +1 -1
  101. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.d.ts +5 -2
  102. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.d.ts.map +1 -1
  103. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js +8 -3
  104. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js.map +1 -1
  105. package/dist/presentation/components/use_smile_id_instructions_hero.d.ts.map +1 -1
  106. package/dist/presentation/components/use_smile_id_instructions_hero.js +8 -3
  107. package/dist/presentation/components/use_smile_id_instructions_hero.js.map +1 -1
  108. package/dist/presentation/flow/dsl/builder/config_builder.d.ts +16 -0
  109. package/dist/presentation/flow/dsl/builder/config_builder.d.ts.map +1 -1
  110. package/dist/presentation/flow/dsl/builder/config_builder.js +18 -0
  111. package/dist/presentation/flow/dsl/builder/config_builder.js.map +1 -1
  112. package/dist/presentation/flow/dsl/builder/screens_builder.d.ts +6 -12
  113. package/dist/presentation/flow/dsl/builder/screens_builder.d.ts.map +1 -1
  114. package/dist/presentation/flow/dsl/builder/screens_builder.js +7 -12
  115. package/dist/presentation/flow/dsl/builder/screens_builder.js.map +1 -1
  116. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts.map +1 -1
  117. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js +23 -1
  118. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js.map +1 -1
  119. package/dist/presentation/flow/flow_navigation_manager.d.ts +22 -3
  120. package/dist/presentation/flow/flow_navigation_manager.d.ts.map +1 -1
  121. package/dist/presentation/flow/flow_navigation_manager.js +84 -46
  122. package/dist/presentation/flow/flow_navigation_manager.js.map +1 -1
  123. package/dist/presentation/flow/model/instructions_defaults.d.ts.map +1 -1
  124. package/dist/presentation/flow/model/instructions_defaults.js +1 -1
  125. package/dist/presentation/flow/model/instructions_defaults.js.map +1 -1
  126. package/dist/presentation/flow/screen/orchestrated_capture_screen.d.ts.map +1 -1
  127. package/dist/presentation/flow/screen/orchestrated_capture_screen.js +13 -3
  128. package/dist/presentation/flow/screen/orchestrated_capture_screen.js.map +1 -1
  129. package/dist/presentation/flow/screen/orchestrated_consent_screen.d.ts.map +1 -1
  130. package/dist/presentation/flow/screen/orchestrated_consent_screen.js +3 -1
  131. package/dist/presentation/flow/screen/orchestrated_consent_screen.js.map +1 -1
  132. package/dist/presentation/flow/screen/orchestrated_preview_screen.d.ts.map +1 -1
  133. package/dist/presentation/flow/screen/orchestrated_preview_screen.js +0 -8
  134. package/dist/presentation/flow/screen/orchestrated_preview_screen.js.map +1 -1
  135. package/dist/presentation/flow/screen/orchestrated_processing_screen.d.ts.map +1 -1
  136. package/dist/presentation/flow/screen/orchestrated_processing_screen.js +25 -5
  137. package/dist/presentation/flow/screen/orchestrated_processing_screen.js.map +1 -1
  138. package/dist/presentation/flow/use_smile_id_flow.d.ts.map +1 -1
  139. package/dist/presentation/flow/use_smile_id_flow.js +45 -12
  140. package/dist/presentation/flow/use_smile_id_flow.js.map +1 -1
  141. package/dist/presentation/flow/validation/flow_validator.d.ts +16 -2
  142. package/dist/presentation/flow/validation/flow_validator.d.ts.map +1 -1
  143. package/dist/presentation/flow/validation/flow_validator.js +61 -47
  144. package/dist/presentation/flow/validation/flow_validator.js.map +1 -1
  145. package/dist/presentation/flow/validation/job_type_validator.d.ts +7 -0
  146. package/dist/presentation/flow/validation/job_type_validator.d.ts.map +1 -1
  147. package/dist/presentation/flow/validation/job_type_validator.js +52 -27
  148. package/dist/presentation/flow/validation/job_type_validator.js.map +1 -1
  149. package/dist/presentation/flow/validation/user_details_validator.d.ts +9 -0
  150. package/dist/presentation/flow/validation/user_details_validator.d.ts.map +1 -0
  151. package/dist/presentation/flow/validation/user_details_validator.js +40 -0
  152. package/dist/presentation/flow/validation/user_details_validator.js.map +1 -0
  153. package/dist/presentation/screen/helpers/capture_screen_helpers.d.ts.map +1 -1
  154. package/dist/presentation/screen/helpers/capture_screen_helpers.js +1 -0
  155. package/dist/presentation/screen/helpers/capture_screen_helpers.js.map +1 -1
  156. package/dist/presentation/screen/helpers/capture_ui_state.d.ts +6 -6
  157. package/dist/presentation/screen/helpers/capture_ui_state.d.ts.map +1 -1
  158. package/dist/presentation/screen/helpers/capture_ui_state.js +19 -19
  159. package/dist/presentation/screen/helpers/capture_ui_state.js.map +1 -1
  160. package/dist/presentation/screen/helpers/ess_turn_haptics.d.ts +2 -2
  161. package/dist/presentation/screen/helpers/ess_turn_haptics.d.ts.map +1 -1
  162. package/dist/presentation/screen/helpers/ess_turn_haptics.js +6 -6
  163. package/dist/presentation/screen/helpers/ess_turn_haptics.js.map +1 -1
  164. package/dist/presentation/screen/helpers/scan_state_to_overlay_state.d.ts +5 -5
  165. package/dist/presentation/screen/helpers/scan_state_to_overlay_state.d.ts.map +1 -1
  166. package/dist/presentation/screen/helpers/scan_state_to_overlay_state.js +11 -11
  167. package/dist/presentation/screen/helpers/scan_state_to_overlay_state.js.map +1 -1
  168. package/dist/presentation/screen/helpers/use_capture_orientation_lock.d.ts +13 -3
  169. package/dist/presentation/screen/helpers/use_capture_orientation_lock.d.ts.map +1 -1
  170. package/dist/presentation/screen/helpers/use_capture_orientation_lock.js +48 -6
  171. package/dist/presentation/screen/helpers/use_capture_orientation_lock.js.map +1 -1
  172. package/dist/presentation/screen/use_smile_id_capture_screen.d.ts +4 -2
  173. package/dist/presentation/screen/use_smile_id_capture_screen.d.ts.map +1 -1
  174. package/dist/presentation/screen/use_smile_id_capture_screen.js +90 -23
  175. package/dist/presentation/screen/use_smile_id_capture_screen.js.map +1 -1
  176. package/dist/presentation/screen/use_smile_id_consent_screen.d.ts +7 -0
  177. package/dist/presentation/screen/use_smile_id_consent_screen.d.ts.map +1 -1
  178. package/dist/presentation/screen/use_smile_id_consent_screen.js +6 -5
  179. package/dist/presentation/screen/use_smile_id_consent_screen.js.map +1 -1
  180. package/dist/presentation/screen/use_smile_id_document_back_instructions.d.ts.map +1 -1
  181. package/dist/presentation/screen/use_smile_id_document_back_instructions.js +5 -13
  182. package/dist/presentation/screen/use_smile_id_document_back_instructions.js.map +1 -1
  183. package/dist/presentation/screen/use_smile_id_document_preview_screen.js +1 -1
  184. package/dist/presentation/screen/use_smile_id_document_preview_screen.js.map +1 -1
  185. package/dist/presentation/screen/use_smile_id_preview_screen.d.ts +1 -21
  186. package/dist/presentation/screen/use_smile_id_preview_screen.d.ts.map +1 -1
  187. package/dist/presentation/screen/use_smile_id_preview_screen.js +3 -14
  188. package/dist/presentation/screen/use_smile_id_preview_screen.js.map +1 -1
  189. package/dist/presentation/screen/use_smile_id_processing_screen.js +1 -1
  190. package/dist/presentation/screen/use_smile_id_processing_screen.js.map +1 -1
  191. package/dist/presentation/utils/locale_utils.d.ts +10 -4
  192. package/dist/presentation/utils/locale_utils.d.ts.map +1 -1
  193. package/dist/presentation/utils/locale_utils.js +13 -5
  194. package/dist/presentation/utils/locale_utils.js.map +1 -1
  195. package/dist/testing/fake_face_analyzer.d.ts +43 -0
  196. package/dist/testing/fake_face_analyzer.d.ts.map +1 -0
  197. package/dist/testing/fake_face_analyzer.js +52 -0
  198. package/dist/testing/fake_face_analyzer.js.map +1 -0
  199. package/dist/testing/index.d.ts +5 -0
  200. package/dist/testing/index.d.ts.map +1 -0
  201. package/dist/testing/index.js +7 -0
  202. package/dist/testing/index.js.map +1 -0
  203. package/dist/testing/scenario_expectations.d.ts +28 -0
  204. package/dist/testing/scenario_expectations.d.ts.map +1 -0
  205. package/dist/testing/scenario_expectations.js +65 -0
  206. package/dist/testing/scenario_expectations.js.map +1 -0
  207. package/dist/testing/scenario_loader.d.ts +11 -0
  208. package/dist/testing/scenario_loader.d.ts.map +1 -0
  209. package/dist/testing/scenario_loader.js +64 -0
  210. package/dist/testing/scenario_loader.js.map +1 -0
  211. package/dist/testing/scripted_scan_results.d.ts +17 -0
  212. package/dist/testing/scripted_scan_results.d.ts.map +1 -0
  213. package/dist/testing/scripted_scan_results.js +52 -0
  214. package/dist/testing/scripted_scan_results.js.map +1 -0
  215. package/package.json +11 -4
  216. package/src/__tests__/dsl.test.ts +51 -1
  217. package/src/__tests__/react_native_mock.tsx +22 -0
  218. package/src/__tests__/setup_react_native.ts +17 -2
  219. package/src/__tests__/snapshots/__snapshots__/screens_structural.test.tsx.snap +5267 -0
  220. package/src/__tests__/snapshots/screens_structural.test.tsx +230 -0
  221. package/src/camera/__tests__/camera_permissions.test.ts +72 -0
  222. package/src/camera/__tests__/camera_system_focus.test.ts +173 -0
  223. package/src/camera/__tests__/use_smile_id_camera_preview.test.tsx +461 -0
  224. package/src/camera/__tests__/use_smile_id_camera_system.test.ts +103 -0
  225. package/src/camera/__tests__/use_smile_id_frame_stream_hook.test.tsx +229 -0
  226. package/src/camera/camera_defaults.ts +3 -0
  227. package/src/core/exception/__tests__/builder_validation_exceptions.test.ts +81 -0
  228. package/src/core/exception/agent_mode_with_enhanced_liveness_exception.ts +20 -0
  229. package/src/core/exception/capture_validation_exceptions.ts +1 -0
  230. package/src/core/exception/flow_exceptions.test.ts +10 -0
  231. package/src/core/exception/flow_exceptions.ts +3 -2
  232. package/src/core/exception/index.ts +1 -0
  233. package/src/core/exception/missing_job_type_exception.ts +2 -4
  234. package/src/core/exception/use_smile_id_error_code.ts +1 -0
  235. package/src/core/models/__tests__/document_type.test.ts +2 -2
  236. package/src/core/models/__tests__/flow_models.test.ts +26 -0
  237. package/src/core/models/config.ts +22 -0
  238. package/src/core/models/document_type.ts +2 -2
  239. package/src/core/models/global_privacy_control.ts +45 -0
  240. package/src/core/models/network_config.ts +2 -2
  241. package/src/data/api/__tests__/smile_id_api.test.ts +275 -1
  242. package/src/data/api/interceptors/__tests__/credential_header_redaction.test.ts +144 -0
  243. package/src/data/api/interceptors/__tests__/gzip_interceptor.test.ts +83 -0
  244. package/src/data/api/interceptors/smile_auth_interceptor.ts +3 -18
  245. package/src/data/api/smile_id_api.ts +36 -49
  246. package/src/data/compression/__tests__/payload_compressor_real_jpeg.test.ts +6 -2
  247. package/src/data/dsl/__tests__/use_smile_id_network_builder.test.ts +157 -0
  248. package/src/data/dsl/builder/__tests__/config_builders.test.ts +62 -0
  249. package/src/data/dsl/builder/retry_config_builder.ts +6 -6
  250. package/src/data/factory/__tests__/retry_gzip_mac_composition.test.ts +163 -0
  251. package/src/data/factory/__tests__/signed_job_submission_headers.test.ts +1 -1
  252. package/src/data/model/__tests__/token_payload.test.ts +224 -0
  253. package/src/data/model/__tests__/user_details_payload.test.ts +100 -0
  254. package/src/data/model/job_request.ts +27 -12
  255. package/src/data/model/token_payload.ts +182 -0
  256. package/src/data/model/user_details_payload.ts +84 -0
  257. package/src/data/repository/__tests__/network_retries.test.ts +1 -1
  258. package/src/data/repository/__tests__/repository_dispatch.test.ts +366 -0
  259. package/src/data/repository/__tests__/use_smile_id_repository.test.ts +1 -1
  260. package/src/data/security/__tests__/request_canonicalizer.test.ts +132 -0
  261. package/src/data/security/__tests__/signing_crypto.test.ts +203 -0
  262. package/src/data/smile_id_urls.ts +1 -1
  263. package/src/data/util/__tests__/retry_util.test.ts +25 -0
  264. package/src/data/util/__tests__/use_smile_id_token_decoder.test.ts +179 -0
  265. package/src/data/util/retry_util.ts +9 -1
  266. package/src/data/util/use_smile_id_token_decoder.ts +29 -8
  267. package/src/index.ts +6 -4
  268. package/src/l10n/__tests__/use_smile_id_localizations.test.ts +122 -5
  269. package/src/l10n/__tests__/use_smile_id_localizations_provider.test.tsx +112 -0
  270. package/src/l10n/__tests__/use_smile_id_localizations_surface.test.ts +231 -0
  271. package/src/l10n/strings_en.ts +5 -1
  272. package/src/l10n/use_smile_id_localizations.ts +80 -3
  273. package/src/metadata/__tests__/device_orientation_sampling.test.ts +165 -0
  274. package/src/metadata/__tests__/geolocation.test.ts +150 -0
  275. package/src/metadata/__tests__/metadata_configuration_mappers.test.ts +39 -0
  276. package/src/metadata/__tests__/metadata_configuration_objects.test.ts +2 -1
  277. package/src/metadata/__tests__/metadata_device_context.test.ts +3 -3
  278. package/src/metadata/__tests__/metadata_device_os_android.test.ts +18 -0
  279. package/src/metadata/__tests__/metadata_golden_fixture.json +1 -1
  280. package/src/metadata/__tests__/metadata_payload_golden.test.ts +4 -4
  281. package/src/metadata/__tests__/optional_native_modules.test.ts +234 -0
  282. package/src/metadata/__tests__/use_smile_id_metadata.test.ts +1 -1
  283. package/src/metadata/__tests__/use_smile_id_metadata_factory.test.ts +615 -0
  284. package/src/metadata/builder_configuration.ts +6 -0
  285. package/src/metadata/metadata_configuration_mappers.ts +21 -7
  286. package/src/metadata/use_smile_id_metadata_factory.ts +17 -6
  287. package/src/presentation/assets/si_document_green_book_hero_animation.json +1 -4668
  288. package/src/presentation/assets/si_document_hero_animation.json +1 -4799
  289. package/src/presentation/assets/si_document_hero_back_animation.json +1 -2039
  290. package/src/presentation/assets/si_document_passport_hero_animation.json +1 -14491
  291. package/src/presentation/assets/si_ess_all_directions_hero_animation.json +1 -0
  292. package/src/presentation/assets/si_ess_animation_all_directions.json +1 -6589
  293. package/src/presentation/assets/si_selfie_hero_animation.json +1 -1439
  294. package/src/presentation/assets/si_smile_to_capture_animation.json +1 -1439
  295. package/src/presentation/components/__tests__/use_smile_id_content_card.test.tsx +112 -0
  296. package/src/presentation/components/__tests__/use_smile_id_enhanced_smart_selfie_clip.test.tsx +9 -4
  297. package/src/presentation/components/__tests__/use_smile_id_face_oval_image.test.tsx +190 -0
  298. package/src/presentation/components/__tests__/use_smile_id_instructions_hero.test.tsx +44 -0
  299. package/src/presentation/components/__tests__/use_smile_id_learn_more_content.test.tsx +159 -0
  300. package/src/presentation/components/__tests__/use_smile_id_processing_center_indicator.test.tsx +88 -0
  301. package/src/presentation/components/use_smile_id_button.tsx +7 -2
  302. package/src/presentation/components/use_smile_id_enhanced_smart_selfie_clip.tsx +9 -3
  303. package/src/presentation/components/use_smile_id_instructions_hero.tsx +10 -0
  304. package/src/presentation/flow/__tests__/document_flow_navigation.test.ts +3 -3
  305. package/src/presentation/flow/__tests__/flow_navigation_manager.test.ts +372 -1
  306. package/src/presentation/flow/__tests__/use_smile_id_flow.test.tsx +651 -0
  307. package/src/presentation/flow/dsl/__tests__/use_smile_id_builder_component.test.tsx +120 -0
  308. package/src/presentation/flow/dsl/builder/__tests__/config_builder.test.ts +25 -0
  309. package/src/presentation/flow/dsl/builder/config_builder.ts +21 -0
  310. package/src/presentation/flow/dsl/builder/screens_builder.ts +7 -12
  311. package/src/presentation/flow/dsl/use_smile_id_flow_builder.ts +27 -1
  312. package/src/presentation/flow/flow_navigation_manager.ts +108 -93
  313. package/src/presentation/flow/graph/__tests__/screen_renderer.test.tsx +102 -0
  314. package/src/presentation/flow/model/instructions_defaults.ts +1 -1
  315. package/src/presentation/flow/screen/__tests__/orchestrated_capture_screen.test.tsx +836 -0
  316. package/src/presentation/flow/screen/__tests__/orchestrated_consent_screen.test.tsx +165 -0
  317. package/src/presentation/flow/screen/__tests__/orchestrated_instructions_screen.test.tsx +163 -0
  318. package/src/presentation/flow/screen/__tests__/orchestrated_preview_screen.test.tsx +224 -0
  319. package/src/presentation/flow/screen/__tests__/orchestrated_processing_screen.test.tsx +338 -0
  320. package/src/presentation/flow/screen/orchestrated_capture_screen.tsx +18 -3
  321. package/src/presentation/flow/screen/orchestrated_consent_screen.tsx +5 -0
  322. package/src/presentation/flow/screen/orchestrated_preview_screen.tsx +0 -12
  323. package/src/presentation/flow/screen/orchestrated_processing_screen.tsx +42 -5
  324. package/src/presentation/flow/use_smile_id_flow.tsx +63 -12
  325. package/src/presentation/flow/validation/__tests__/flow_validator.test.ts +219 -1
  326. package/src/presentation/flow/validation/__tests__/job_type_validator.test.ts +200 -0
  327. package/src/presentation/flow/validation/flow_validator.ts +85 -57
  328. package/src/presentation/flow/validation/job_type_validator.ts +73 -39
  329. package/src/presentation/flow/validation/user_details_validator.ts +53 -0
  330. package/src/presentation/screen/__tests__/capture_ui_state.test.ts +66 -47
  331. package/src/presentation/screen/__tests__/ess_turn_haptics.test.ts +15 -11
  332. package/src/presentation/screen/__tests__/feedback_copy_table.test.ts +145 -0
  333. package/src/presentation/screen/__tests__/scan_state_to_overlay_state.test.ts +47 -27
  334. package/src/presentation/screen/__tests__/use_smile_id_capture_screen.test.tsx +524 -14
  335. package/src/presentation/screen/__tests__/use_smile_id_document_back_instructions.test.tsx +4 -2
  336. package/src/presentation/screen/__tests__/use_smile_id_permission_screen.test.tsx +68 -0
  337. package/src/presentation/screen/__tests__/use_smile_id_preview_screen.test.tsx +10 -40
  338. package/src/presentation/screen/helpers/__tests__/use_capture_orientation_lock.test.tsx +212 -0
  339. package/src/presentation/screen/helpers/capture_screen_helpers.ts +1 -0
  340. package/src/presentation/screen/helpers/capture_ui_state.ts +27 -27
  341. package/src/presentation/screen/helpers/ess_turn_haptics.ts +9 -9
  342. package/src/presentation/screen/helpers/scan_state_to_overlay_state.ts +13 -13
  343. package/src/presentation/screen/helpers/use_capture_orientation_lock.ts +69 -7
  344. package/src/presentation/screen/use_smile_id_capture_screen.tsx +107 -28
  345. package/src/presentation/screen/use_smile_id_consent_screen.tsx +16 -5
  346. package/src/presentation/screen/use_smile_id_document_back_instructions.tsx +14 -23
  347. package/src/presentation/screen/use_smile_id_document_preview_screen.tsx +1 -1
  348. package/src/presentation/screen/use_smile_id_preview_screen.tsx +13 -45
  349. package/src/presentation/screen/use_smile_id_processing_screen.tsx +1 -1
  350. package/src/presentation/theme/__tests__/use_use_smile_id_fonts.test.tsx +166 -0
  351. package/src/presentation/utils/__tests__/locale_utils.test.ts +46 -5
  352. package/src/presentation/utils/locale_utils.ts +13 -4
  353. package/src/testing/__tests__/fake_face_analyzer.test.ts +42 -0
  354. package/src/testing/__tests__/scenario_expectations.test.ts +110 -0
  355. package/src/testing/__tests__/scenario_loader.test.ts +64 -0
  356. package/src/testing/fake_face_analyzer.ts +82 -0
  357. package/src/testing/index.ts +15 -0
  358. package/src/testing/scenario_expectations.ts +87 -0
  359. package/src/testing/scenario_loader.ts +83 -0
  360. package/src/testing/scripted_scan_results.ts +66 -0
  361. package/dist/presentation/components/use_smile_id_face_silhouette_overlay.d.ts +0 -15
  362. package/dist/presentation/components/use_smile_id_face_silhouette_overlay.d.ts.map +0 -1
  363. package/dist/presentation/components/use_smile_id_face_silhouette_overlay.js +0 -19
  364. package/dist/presentation/components/use_smile_id_face_silhouette_overlay.js.map +0 -1
  365. package/src/presentation/components/use_smile_id_face_silhouette_overlay.tsx +0 -27
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as React from "react";
3
+ import { create as createRenderer, act } from "react-test-renderer";
4
+ import { Text, View } from "react-native";
5
+ import { UseSmileIDContentCard } from "../use_smile_id_content_card";
6
+ import { UseSmileIDThemeProvider } from "../../theme/theme_context";
7
+ import { defaultDarkTheme, defaultLightTheme } from "../../theme/theme";
8
+
9
+ function render(element: React.ReactElement) {
10
+ let tree!: ReturnType<typeof createRenderer>;
11
+ act(() => {
12
+ tree = createRenderer(element);
13
+ });
14
+ return tree;
15
+ }
16
+
17
+ /** Flatten the style array the card composes so a single key can be read. */
18
+ function surfaceStyle(
19
+ tree: ReturnType<typeof createRenderer>,
20
+ ): Record<string, unknown> {
21
+ const surface = tree.root.findAllByType(View)[0];
22
+ if (!surface) throw new Error("card surface did not render");
23
+ const style = surface.props.style as unknown;
24
+ const parts = Array.isArray(style) ? style : [style];
25
+ return Object.assign(
26
+ {},
27
+ ...parts.filter((part): part is Record<string, unknown> => Boolean(part)),
28
+ ) as Record<string, unknown>;
29
+ }
30
+
31
+ describe("UseSmileIDContentCard", () => {
32
+ it("renders its children inside the card surface", () => {
33
+ const tree = render(
34
+ <UseSmileIDContentCard theme={defaultLightTheme}>
35
+ <Text testID="si_child">Body</Text>
36
+ </UseSmileIDContentCard>,
37
+ );
38
+
39
+ expect(tree.root.findAllByProps({ testID: "si_child" })).not.toHaveLength(
40
+ 0,
41
+ );
42
+ });
43
+
44
+ it("uses the card surface colour and radius by default", () => {
45
+ const tree = render(
46
+ <UseSmileIDContentCard theme={defaultLightTheme}>
47
+ <Text>Body</Text>
48
+ </UseSmileIDContentCard>,
49
+ );
50
+
51
+ const style = surfaceStyle(tree);
52
+ expect(style.backgroundColor).toBe(defaultLightTheme.colors.cardBackground);
53
+ expect(style.borderRadius).toBe(defaultLightTheme.shapes.cardRadius);
54
+ expect(style.padding).toBe(defaultLightTheme.spacing.medium);
55
+ });
56
+
57
+ it("switches to the instruction tint for the instruction variant", () => {
58
+ const tree = render(
59
+ <UseSmileIDContentCard variant="instruction" theme={defaultLightTheme}>
60
+ <Text>Body</Text>
61
+ </UseSmileIDContentCard>,
62
+ );
63
+
64
+ expect(surfaceStyle(tree).backgroundColor).toBe(
65
+ defaultLightTheme.colors.instructionCardBackground,
66
+ );
67
+ });
68
+
69
+ it("falls back to the theme from context when no theme prop is given", () => {
70
+ const tree = render(
71
+ <UseSmileIDThemeProvider value={defaultDarkTheme}>
72
+ <UseSmileIDContentCard>
73
+ <Text>Body</Text>
74
+ </UseSmileIDContentCard>
75
+ </UseSmileIDThemeProvider>,
76
+ );
77
+
78
+ expect(surfaceStyle(tree).backgroundColor).toBe(
79
+ defaultDarkTheme.colors.cardBackground,
80
+ );
81
+ });
82
+
83
+ it("lets a caller-supplied style override the computed card style", () => {
84
+ const tree = render(
85
+ <UseSmileIDContentCard
86
+ theme={defaultLightTheme}
87
+ style={{ backgroundColor: "#123456", borderRadius: 99 }}
88
+ >
89
+ <Text>Body</Text>
90
+ </UseSmileIDContentCard>,
91
+ );
92
+
93
+ const style = surfaceStyle(tree);
94
+ expect(style.backgroundColor).toBe("#123456");
95
+ expect(style.borderRadius).toBe(99);
96
+ });
97
+
98
+ it("carries exactly one platform shadow treatment, never both", () => {
99
+ const tree = render(
100
+ <UseSmileIDContentCard theme={defaultLightTheme}>
101
+ <Text>Body</Text>
102
+ </UseSmileIDContentCard>,
103
+ );
104
+
105
+ const style = surfaceStyle(tree);
106
+ // Setting `elevation` and the iOS shadow* keys together double-draws the
107
+ // shadow on Android, so exactly one family must be present.
108
+ const hasAndroidShadow = "elevation" in style;
109
+ const hasIosShadow = "shadowOpacity" in style;
110
+ expect(hasAndroidShadow !== hasIosShadow).toBe(true);
111
+ });
112
+ });
@@ -8,8 +8,9 @@ import {
8
8
  import { AccessibilityInfo } from "react-native";
9
9
  import {
10
10
  UseSmileIDEnhancedSmartSelfieClip,
11
- ENHANCED_SMART_SELFIE_ANIMATION_SIZE,
11
+ ENHANCED_SMART_SELFIE_ARROW_FIT,
12
12
  } from "../use_smile_id_enhanced_smart_selfie_clip";
13
+ import { FaceOvalDefaults } from "../face_oval_defaults";
13
14
  import {
14
15
  ENHANCED_SMART_SELFIE_ANIMATION_SPEED,
15
16
  ENHANCED_SMART_SELFIE_TOTAL_FRAMES,
@@ -195,7 +196,7 @@ describe("UseSmileIDEnhancedSmartSelfieClip", () => {
195
196
  });
196
197
 
197
198
  describe("positioning", () => {
198
- it("produces a container sized to ENHANCED_SMART_SELFIE_ANIMATION_SIZE", async () => {
199
+ it("sizes the container to the oval width times the arrow-fit factor", async () => {
199
200
  const tree = renderClip({ pingPong: false });
200
201
  await act(async () => {
201
202
  await Promise.resolve();
@@ -209,8 +210,12 @@ describe("UseSmileIDEnhancedSmartSelfieClip", () => {
209
210
  );
210
211
  expect(container).toBeDefined();
211
212
  const style = container!.props.style as { width: number; height: number };
212
- expect(style.width).toBe(ENHANCED_SMART_SELFIE_ANIMATION_SIZE);
213
- expect(style.height).toBe(ENHANCED_SMART_SELFIE_ANIMATION_SIZE);
213
+ const expectedSize =
214
+ BASE_CONTAINER_WIDTH *
215
+ FaceOvalDefaults.ovalWidthRatio *
216
+ ENHANCED_SMART_SELFIE_ARROW_FIT;
217
+ expect(style.width).toBe(expectedSize);
218
+ expect(style.height).toBe(expectedSize);
214
219
  });
215
220
  });
216
221
 
@@ -0,0 +1,190 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import * as React from "react";
3
+ import {
4
+ create as createRenderer,
5
+ act,
6
+ type ReactTestInstance,
7
+ } from "react-test-renderer";
8
+
9
+ // The shared setup's `react-native-svg` stub omits `Image`, which this
10
+ // component needs. Re-stub locally (same host-forwarding shape) so the SVG
11
+ // tree is inspectable without touching the workspace-wide setup.
12
+ vi.mock("react-native-svg", async () => {
13
+ const React = await import("react");
14
+ function SvgHost(props: Record<string, unknown>) {
15
+ const { children, ...rest } = props as {
16
+ children?: React.ReactNode;
17
+ } & Record<string, unknown>;
18
+ return React.createElement("Svg", rest, children);
19
+ }
20
+ return {
21
+ __esModule: true,
22
+ default: SvgHost,
23
+ Svg: SvgHost,
24
+ Defs: SvgHost,
25
+ ClipPath: SvgHost,
26
+ Ellipse: SvgHost,
27
+ Image: SvgHost,
28
+ };
29
+ });
30
+
31
+ import { UseSmileIDFaceOvalImage } from "../use_smile_id_face_oval_image";
32
+ import { computeFaceOvalGeometry } from "../face_oval_defaults";
33
+
34
+ function render(element: React.ReactElement) {
35
+ let tree!: ReturnType<typeof createRenderer>;
36
+ act(() => {
37
+ tree = createRenderer(element);
38
+ });
39
+ return tree;
40
+ }
41
+
42
+ /** The host (string-typed) instance carrying `testID`, not the composite. */
43
+ function host(
44
+ tree: ReturnType<typeof createRenderer>,
45
+ testID: string,
46
+ ): ReactTestInstance {
47
+ const match = tree.root
48
+ .findAllByProps({ testID })
49
+ .find((instance) => typeof instance.type === "string");
50
+ if (!match) throw new Error(`no host element with testID ${testID}`);
51
+ return match;
52
+ }
53
+
54
+ function flatStyle(instance: ReactTestInstance): Record<string, unknown> {
55
+ const style = instance.props.style as unknown;
56
+ const parts = Array.isArray(style) ? style : [style];
57
+ return Object.assign(
58
+ {},
59
+ ...parts.filter((part): part is Record<string, unknown> => Boolean(part)),
60
+ ) as Record<string, unknown>;
61
+ }
62
+
63
+ const URI = "file:///selfie.jpg";
64
+
65
+ describe("UseSmileIDFaceOvalImage", () => {
66
+ it("renders nothing until the canvas has measured", () => {
67
+ const tree = render(
68
+ <UseSmileIDFaceOvalImage
69
+ uri={URI}
70
+ containerWidth={0}
71
+ containerHeight={0}
72
+ />,
73
+ );
74
+
75
+ expect(tree.toJSON()).toBeNull();
76
+ });
77
+
78
+ it("renders nothing when only one dimension has measured", () => {
79
+ const tree = render(
80
+ <UseSmileIDFaceOvalImage
81
+ uri={URI}
82
+ containerWidth={320}
83
+ containerHeight={0}
84
+ />,
85
+ );
86
+
87
+ expect(tree.toJSON()).toBeNull();
88
+ });
89
+
90
+ it("renders nothing when there is no captured file yet", () => {
91
+ const tree = render(
92
+ <UseSmileIDFaceOvalImage
93
+ uri=""
94
+ containerWidth={320}
95
+ containerHeight={480}
96
+ />,
97
+ );
98
+
99
+ expect(tree.toJSON()).toBeNull();
100
+ });
101
+
102
+ it("positions the oval at the canonical face-oval rect", () => {
103
+ const geometry = computeFaceOvalGeometry(320, 480);
104
+ const tree = render(
105
+ <UseSmileIDFaceOvalImage
106
+ uri={URI}
107
+ containerWidth={320}
108
+ containerHeight={480}
109
+ testID="si_face_oval_image"
110
+ />,
111
+ );
112
+
113
+ const style = flatStyle(host(tree, "si_face_oval_image"));
114
+ expect(style.width).toBeCloseTo(geometry.ovalWidth, 5);
115
+ expect(style.height).toBeCloseTo(geometry.ovalHeight, 5);
116
+ expect(style.left).toBeCloseTo(geometry.ovalLeft, 5);
117
+ expect(style.top).toBeCloseTo(geometry.ovalTop, 5);
118
+ expect(style.position).toBe("absolute");
119
+ });
120
+
121
+ it("draws the captured file clipped to the ellipse", () => {
122
+ const geometry = computeFaceOvalGeometry(320, 480);
123
+ const tree = render(
124
+ <UseSmileIDFaceOvalImage
125
+ uri={URI}
126
+ containerWidth={320}
127
+ containerHeight={480}
128
+ />,
129
+ );
130
+
131
+ const json = JSON.stringify(tree.toJSON());
132
+ expect(json).toContain(URI);
133
+ // The clip id ties the <Image> to the <ClipPath><Ellipse>; a mismatch
134
+ // renders an unclipped rectangle over the preview.
135
+ expect(json).toContain("url(#si_face_oval_image_clip)");
136
+ expect(json).toContain(`0 0 ${geometry.ovalWidth} ${geometry.ovalHeight}`);
137
+ });
138
+
139
+ it("sizes the ellipse to the full oval so the crop is not inset", () => {
140
+ const geometry = computeFaceOvalGeometry(320, 480);
141
+ const tree = render(
142
+ <UseSmileIDFaceOvalImage
143
+ uri={URI}
144
+ containerWidth={320}
145
+ containerHeight={480}
146
+ />,
147
+ );
148
+
149
+ const ellipse = tree.root
150
+ .findAllByType("Svg" as unknown as React.ComponentType)
151
+ .find((instance) => instance.props.rx !== undefined);
152
+ expect(ellipse?.props.rx).toBeCloseTo(geometry.ovalWidth / 2, 5);
153
+ expect(ellipse?.props.ry).toBeCloseTo(geometry.ovalHeight / 2, 5);
154
+ expect(ellipse?.props.cx).toBeCloseTo(geometry.ovalWidth / 2, 5);
155
+ expect(ellipse?.props.cy).toBeCloseTo(geometry.ovalHeight / 2, 5);
156
+ });
157
+
158
+ it("rescales the oval when the canvas resizes", () => {
159
+ const small = flatStyle(
160
+ host(
161
+ render(
162
+ <UseSmileIDFaceOvalImage
163
+ uri={URI}
164
+ containerWidth={320}
165
+ containerHeight={480}
166
+ testID="si_face_oval_image"
167
+ />,
168
+ ),
169
+ "si_face_oval_image",
170
+ ),
171
+ );
172
+ const large = flatStyle(
173
+ host(
174
+ render(
175
+ <UseSmileIDFaceOvalImage
176
+ uri={URI}
177
+ containerWidth={640}
178
+ containerHeight={960}
179
+ testID="si_face_oval_image"
180
+ />,
181
+ ),
182
+ "si_face_oval_image",
183
+ ),
184
+ );
185
+
186
+ expect(large.width as number).toBeCloseTo((small.width as number) * 2, 5);
187
+ expect(large.height as number).toBeCloseTo((small.height as number) * 2, 5);
188
+ expect(large.top as number).toBeCloseTo((small.top as number) * 2, 5);
189
+ });
190
+ });
@@ -78,6 +78,50 @@ describe("UseSmileIDInstructionsHero", () => {
78
78
  }
79
79
  });
80
80
 
81
+ it("pins oval-less heroes to the software Lottie engine on iOS", () => {
82
+ // lottie-ios's CA engine mis-renders the doc heroes' track mattes, so
83
+ // oval-less heroes must pin renderMode SOFTWARE on iOS.
84
+ const docTree = render(
85
+ <UseSmileIDInstructionsHero
86
+ source={SELFIE_HERO_ANIMATION}
87
+ showOval={false}
88
+ showOvalProgress={false}
89
+ />,
90
+ );
91
+ const docHero = docTree.root.findByProps({
92
+ testID: "si_instructions_hero",
93
+ });
94
+ act(() => {
95
+ docHero.props.onLayout({
96
+ nativeEvent: { layout: { width: 390, height: 600 } },
97
+ });
98
+ });
99
+ expect(docTree.root.findByType(AnimatedLottieView).props.renderMode).toBe(
100
+ "SOFTWARE",
101
+ );
102
+ act(() => docTree.unmount());
103
+
104
+ const ovalTree = render(
105
+ <UseSmileIDInstructionsHero
106
+ source={SELFIE_HERO_ANIMATION}
107
+ showOval
108
+ showOvalProgress={false}
109
+ />,
110
+ );
111
+ const ovalHero = ovalTree.root.findByProps({
112
+ testID: "si_instructions_hero",
113
+ });
114
+ act(() => {
115
+ ovalHero.props.onLayout({
116
+ nativeEvent: { layout: { width: 390, height: 600 } },
117
+ });
118
+ });
119
+ expect(
120
+ ovalTree.root.findByType(AnimatedLottieView).props.renderMode,
121
+ ).toBeUndefined();
122
+ act(() => ovalTree.unmount());
123
+ });
124
+
81
125
  it("gives the document card hero explicit dimensions (no intrinsic-size flash)", () => {
82
126
  const tree = render(
83
127
  <UseSmileIDInstructionsHero
@@ -0,0 +1,159 @@
1
+ import { describe, expect, it, beforeEach, vi } from "vitest";
2
+ import * as React from "react";
3
+ import {
4
+ create as createRenderer,
5
+ act,
6
+ type ReactTestInstance,
7
+ } from "react-test-renderer";
8
+ import { Linking, Text } from "react-native";
9
+ import { UseSmileIDCrashReporting } from "@smileid/usesmileid_bridge";
10
+
11
+ import { UseSmileIDLearnMoreContent } from "../use_smile_id_learn_more_content";
12
+ import { enStrings } from "../../../l10n/strings_en";
13
+
14
+ /**
15
+ * The consent screen's expandable privacy section. Two things must
16
+ * hold: the `{smile_id}` / `{partner}` / `{retention}` tokens are all
17
+ * substituted (a leaked token ships raw template text to a user), and
18
+ * a device with no browser must surface through crash reporting rather
19
+ * than presenting as a dead tap.
20
+ */
21
+
22
+ const SMILE_ID_PRIVACY_URL = "https://usesmileid.com/privacy-policy/";
23
+
24
+ function render(element: React.ReactElement) {
25
+ let tree!: ReturnType<typeof createRenderer>;
26
+ act(() => {
27
+ tree = createRenderer(element);
28
+ });
29
+ return tree;
30
+ }
31
+
32
+ function collectText(node: ReactTestInstance): string {
33
+ return node.children
34
+ .map((child) =>
35
+ typeof child === "string"
36
+ ? child
37
+ : collectText(child as ReactTestInstance),
38
+ )
39
+ .join("");
40
+ }
41
+
42
+ /** The pressable inline links, in render order: DSAR, Smile ID, partner. */
43
+ function links(root: ReactTestInstance): ReactTestInstance[] {
44
+ return root.findAll(
45
+ (node) => node.type === Text && typeof node.props.onPress === "function",
46
+ { deep: true },
47
+ );
48
+ }
49
+
50
+ describe("UseSmileIDLearnMoreContent", () => {
51
+ beforeEach(() => {
52
+ vi.restoreAllMocks();
53
+ });
54
+
55
+ it("substitutes every token in the description template", () => {
56
+ const { root } = render(
57
+ <UseSmileIDLearnMoreContent
58
+ partnerName="Acme Bank"
59
+ testID="si_learn_more"
60
+ />,
61
+ );
62
+
63
+ const text = collectText(root.findByProps({ testID: "si_learn_more" }));
64
+ expect(text).toContain("Acme Bank");
65
+ expect(text).toContain(
66
+ enStrings.si_consent_learn_more_description_smile_id,
67
+ );
68
+ expect(text).toContain(
69
+ enStrings.si_consent_learn_more_description_retention,
70
+ );
71
+ expect(text).not.toContain("{smile_id}");
72
+ expect(text).not.toContain("{partner}");
73
+ expect(text).not.toContain("{retention}");
74
+ });
75
+
76
+ it("opens the Smile ID privacy policy from the DSAR and privacy links", async () => {
77
+ const openURL = vi.spyOn(Linking, "openURL").mockResolvedValue(undefined);
78
+ const { root } = render(
79
+ <UseSmileIDLearnMoreContent partnerName="Acme Bank" />,
80
+ );
81
+
82
+ await act(async () => {
83
+ links(root)[0]?.props.onPress();
84
+ });
85
+
86
+ expect(openURL).toHaveBeenCalledWith(SMILE_ID_PRIVACY_URL);
87
+ });
88
+
89
+ it("opens the partner's own policy when one is configured", async () => {
90
+ const openURL = vi.spyOn(Linking, "openURL").mockResolvedValue(undefined);
91
+ const { root } = render(
92
+ <UseSmileIDLearnMoreContent
93
+ partnerName="Acme Bank"
94
+ partnerPrivacyPolicyUrl="https://acme.test/privacy"
95
+ />,
96
+ );
97
+
98
+ const partnerLink = links(root).at(-1);
99
+ await act(async () => {
100
+ partnerLink?.props.onPress();
101
+ });
102
+
103
+ expect(openURL).toHaveBeenCalledWith("https://acme.test/privacy");
104
+ });
105
+
106
+ it("is a silent no-op when the partner supplied no policy URL", async () => {
107
+ const openURL = vi.spyOn(Linking, "openURL").mockResolvedValue(undefined);
108
+ const { root } = render(
109
+ <UseSmileIDLearnMoreContent partnerName="Acme Bank" />,
110
+ );
111
+
112
+ const partnerLink = links(root).at(-1);
113
+ await act(async () => {
114
+ partnerLink?.props.onPress();
115
+ });
116
+
117
+ expect(openURL).not.toHaveBeenCalled();
118
+ });
119
+
120
+ it("reports a failed Smile ID link open to crash reporting", async () => {
121
+ const failure = new Error("no browser registered");
122
+ vi.spyOn(Linking, "openURL").mockRejectedValue(failure);
123
+ const captureException = vi
124
+ .spyOn(UseSmileIDCrashReporting, "captureException")
125
+ .mockImplementation(() => {});
126
+ const { root } = render(
127
+ <UseSmileIDLearnMoreContent partnerName="Acme Bank" />,
128
+ );
129
+
130
+ await act(async () => {
131
+ links(root)[0]?.props.onPress();
132
+ await Promise.resolve();
133
+ });
134
+
135
+ expect(captureException).toHaveBeenCalledWith(failure);
136
+ });
137
+
138
+ it("reports a failed partner link open to crash reporting", async () => {
139
+ const failure = new Error("malformed partner url");
140
+ vi.spyOn(Linking, "openURL").mockRejectedValue(failure);
141
+ const captureException = vi
142
+ .spyOn(UseSmileIDCrashReporting, "captureException")
143
+ .mockImplementation(() => {});
144
+ const { root } = render(
145
+ <UseSmileIDLearnMoreContent
146
+ partnerName="Acme Bank"
147
+ partnerPrivacyPolicyUrl="not-a-url"
148
+ />,
149
+ );
150
+
151
+ const partnerLink = links(root).at(-1);
152
+ await act(async () => {
153
+ partnerLink?.props.onPress();
154
+ await Promise.resolve();
155
+ });
156
+
157
+ expect(captureException).toHaveBeenCalledWith(failure);
158
+ });
159
+ });
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as React from "react";
3
+ import { create as createRenderer, act } from "react-test-renderer";
4
+ import { UseSmileIDProcessingCenterIndicator } from "../use_smile_id_processing_center_indicator";
5
+ import { UseSmileIDLoadingIndicator } from "../use_smile_id_loading_indicator";
6
+ import { UseSmileIDStatusIndicator } from "../use_smile_id_status_indicator";
7
+ import { computeFaceOvalGeometry } from "../face_oval_defaults";
8
+ import { defaultLightTheme } from "../../theme/theme";
9
+ import type { ProcessingState } from "../../state/processing_state";
10
+
11
+ function render(state: ProcessingState, width = 320, height = 480) {
12
+ let tree!: ReturnType<typeof createRenderer>;
13
+ act(() => {
14
+ tree = createRenderer(
15
+ <UseSmileIDProcessingCenterIndicator
16
+ state={state}
17
+ containerWidth={width}
18
+ containerHeight={height}
19
+ theme={defaultLightTheme}
20
+ testID="si_center_indicator"
21
+ />,
22
+ );
23
+ });
24
+ return tree;
25
+ }
26
+
27
+ describe("UseSmileIDProcessingCenterIndicator", () => {
28
+ it("renders nothing until the canvas has measured", () => {
29
+ expect(render({ kind: "loading" }, 0, 0).toJSON()).toBeNull();
30
+ });
31
+
32
+ it("renders nothing when only the height has measured", () => {
33
+ expect(render({ kind: "loading" }, 0, 480).toJSON()).toBeNull();
34
+ });
35
+
36
+ it("anchors to the same rect the captured image occupies", () => {
37
+ const geometry = computeFaceOvalGeometry(320, 480);
38
+ const tree = render({ kind: "loading" });
39
+
40
+ const container = tree.root
41
+ .findAllByProps({ testID: "si_center_indicator" })
42
+ .find((instance) => typeof instance.type === "string");
43
+ const style = Object.assign(
44
+ {},
45
+ ...(container!.props.style as Record<string, unknown>[]),
46
+ ) as Record<string, unknown>;
47
+
48
+ expect(style.width).toBeCloseTo(geometry.ovalWidth, 5);
49
+ expect(style.height).toBeCloseTo(geometry.ovalHeight, 5);
50
+ expect(style.left).toBeCloseTo(geometry.ovalLeft, 5);
51
+ expect(style.top).toBeCloseTo(geometry.ovalTop, 5);
52
+ expect(style.position).toBe("absolute");
53
+ expect(style.alignItems).toBe("center");
54
+ expect(style.justifyContent).toBe("center");
55
+ });
56
+
57
+ it("shows the spinner while the job is in flight", () => {
58
+ const tree = render({ kind: "loading" });
59
+
60
+ expect(tree.root.findAllByType(UseSmileIDLoadingIndicator)).toHaveLength(1);
61
+ expect(tree.root.findAllByType(UseSmileIDStatusIndicator)).toHaveLength(0);
62
+ });
63
+
64
+ it("shows the success status when the server accepts", () => {
65
+ const tree = render({ kind: "success", message: "approved" });
66
+
67
+ const status = tree.root.findAllByType(UseSmileIDStatusIndicator);
68
+ expect(status).toHaveLength(1);
69
+ expect(status[0]!.props.state).toBe("success");
70
+ expect(tree.root.findAllByType(UseSmileIDLoadingIndicator)).toHaveLength(0);
71
+ });
72
+
73
+ it("shows the failure status when the job is rejected", () => {
74
+ const tree = render({ kind: "failed", errorMessage: "declined" });
75
+
76
+ const status = tree.root.findAllByType(UseSmileIDStatusIndicator);
77
+ expect(status).toHaveLength(1);
78
+ expect(status[0]!.props.state).toBe("failure");
79
+ });
80
+
81
+ it("passes the resolved theme down to the child indicator", () => {
82
+ const tree = render({ kind: "loading" });
83
+
84
+ expect(tree.root.findByType(UseSmileIDLoadingIndicator).props.theme).toBe(
85
+ defaultLightTheme,
86
+ );
87
+ });
88
+ });
@@ -65,7 +65,10 @@ export function UseSmileIDButton(props: UseSmileIDButtonProps) {
65
65
  ]}
66
66
  >
67
67
  <Text
68
- numberOfLines={1}
68
+ // Shrink rather than truncate: a longer localized label must stay fully readable.
69
+ numberOfLines={2}
70
+ adjustsFontSizeToFit
71
+ minimumFontScale={0.75}
69
72
  style={{
70
73
  ...theme.typography.button,
71
74
  color: disabled
@@ -84,7 +87,9 @@ export function UseSmileIDButton(props: UseSmileIDButtonProps) {
84
87
 
85
88
  const styles = StyleSheet.create({
86
89
  base: {
87
- height: 48,
90
+ // `minHeight`, not `height`: the pill grows only for a label that has to wrap.
91
+ minHeight: 48,
92
+ paddingVertical: 8,
88
93
  paddingHorizontal: 24,
89
94
  alignItems: "center",
90
95
  justifyContent: "center",
@@ -13,8 +13,11 @@ import {
13
13
  export const ESS_ALL_DIRECTIONS_ANIMATION =
14
14
  require("../assets/si_ess_animation_all_directions.json") as AnimationObject;
15
15
 
16
- /** Square box the Enhanced Smart Selfie cues render in, centred on the oval. */
17
- export const ENHANCED_SMART_SELFIE_ANIMATION_SIZE = 383;
16
+ /**
17
+ * Box width as a multiple of the oval width; the baked arrow tips touch the
18
+ * oval boundary at ≈1.19× — keep this factor below that.
19
+ */
20
+ export const ENHANCED_SMART_SELFIE_ARROW_FIT = 1.15;
18
21
  /** Nudge so the composition's face lands on the oval centre. */
19
22
  export const VERTICAL_ALIGNMENT_OFFSET = -15;
20
23
 
@@ -46,7 +49,10 @@ export function UseSmileIDEnhancedSmartSelfieClip(
46
49
  return null;
47
50
  }
48
51
 
49
- const size = ENHANCED_SMART_SELFIE_ANIMATION_SIZE;
52
+ const size =
53
+ containerWidth *
54
+ FaceOvalDefaults.ovalWidthRatio *
55
+ ENHANCED_SMART_SELFIE_ARROW_FIT;
50
56
  const ovalCenterY = containerHeight * FaceOvalDefaults.ovalCenterYRatio;
51
57
 
52
58
  const containerStyle = {