@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
@@ -11,19 +11,37 @@ import * as Brightness from "expo-brightness";
11
11
  import { StatusBar } from "react-native";
12
12
  import { UseSmileIDCaptureScreen } from "../use_smile_id_capture_screen";
13
13
  import { CaptureType } from "../../../core/models/ml_config";
14
+ import { DocumentCaptureMode } from "../../../core/models/document_capture_mode";
15
+ import { mockedTakePhoto } from "../../../__tests__/setup_react_native";
14
16
  import { enStrings } from "../../../l10n/strings_en";
15
17
  import {
16
- ScanStateType,
18
+ UseSmileIDScanStateType,
17
19
  CaptureReadyEventKind,
18
20
  } from "@smileid/usesmileid_bridge";
19
21
  import type {
20
- ScanState,
22
+ UseSmileIDScanState,
21
23
  CaptureReadyEvent,
22
24
  FaceScanResult,
23
25
  } from "@smileid/usesmileid_bridge";
26
+ import { scriptedFaceScanResults } from "../../../testing/scripted_scan_results";
27
+ import { parseScriptedScenario } from "../../../testing/scenario_loader";
24
28
  import { isSessionBoundary } from "../helpers/capture_ui_state";
25
29
  import { UseSmileIDFeedbackDebouncer } from "../helpers/feedback_debouncer";
26
30
  import { UseSmileIDMotion } from "../../theme/use_smile_id_motion";
31
+ import { readFileSync } from "node:fs";
32
+ import { dirname, join } from "node:path";
33
+
34
+ // The shared scenario set lives at the repo root (e2e/scenarios/), outside this
35
+ // package's tsc rootDir (./src), so the test reads selfie-happy-path.json at run
36
+ // time. These minimal ambient decls keep the read typed without pulling
37
+ // @types/node in (which would collide with react-native's global timer types).
38
+ declare module "node:fs" {
39
+ export function readFileSync(path: string, encoding: "utf8"): string;
40
+ }
41
+ declare module "node:path" {
42
+ export function dirname(path: string): string;
43
+ export function join(...parts: string[]): string;
44
+ }
27
45
 
28
46
  // Capture the screen's per-frame `onResult` so tests can drive synthetic
29
47
  // sequences — the vision-camera worklet can't run in vitest, and `onResult`
@@ -195,6 +213,18 @@ describe("UseSmileIDCaptureScreen", () => {
195
213
  expect(mockedUseCameraDevice).toHaveBeenCalledWith("back");
196
214
  });
197
215
 
216
+ it("uses the back lens for an agent-mode selfie capture", () => {
217
+ const { root } = render(
218
+ <UseSmileIDCaptureScreen
219
+ analyzer={fakeAnalyzer}
220
+ captureType={CaptureType.selfie}
221
+ allowAgentMode
222
+ />,
223
+ );
224
+ expect(findByTestID(root, "si_capture_camera_preview")).not.toBeNull();
225
+ expect(mockedUseCameraDevice).toHaveBeenCalledWith("back");
226
+ });
227
+
198
228
  it("shows a placeholder (not the camera) when permission is undetermined", () => {
199
229
  const requestPermission = vi.fn(async () => true);
200
230
  mockedUseCameraPermission.mockReturnValue({
@@ -278,9 +308,9 @@ describe("UseSmileIDCaptureScreen", () => {
278
308
  // NOTE: the legacy `overlayState` / `captureProgress` prop-driven tests
279
309
  // were removed in Phase 3 of the per-frame migration. The capture
280
310
  // screen now owns its overlay state internally (derived from the
281
- // analyzer's per-frame ScanState), so there's no external prop to
311
+ // analyzer's per-frame UseSmileIDScanState), so there's no external prop to
282
312
  // pin against. Phase 4 follow-up re-authors these as analyzer-driven
283
- // tests that feed synthetic ScanState transitions through a mock
313
+ // tests that feed synthetic UseSmileIDScanState transitions through a mock
284
314
  // analyzer + frame stream.
285
315
 
286
316
  it("forwards the back tap to onBack when the back button is supplied", () => {
@@ -331,7 +361,7 @@ describe("UseSmileIDCaptureScreen", () => {
331
361
  });
332
362
 
333
363
  // Removed in Phase 3 — feedback text is now driven by the analyzer's
334
- // ScanState rather than a partner-supplied overlayState prop. Phase 4
364
+ // UseSmileIDScanState rather than a partner-supplied overlayState prop. Phase 4
335
365
  // re-authors this against a mock analyzer that yields a synthetic
336
366
  // "found + feedback=si_capture_low_light" result through the
337
367
  // frame stream.
@@ -446,6 +476,18 @@ describe("UseSmileIDCaptureScreen", () => {
446
476
  expect(mockedSetBrightness).toHaveBeenCalledWith(1);
447
477
  });
448
478
 
479
+ it("asks for dark status-bar content, matching the white capture scrim", () => {
480
+ const { root } = render(
481
+ <UseSmileIDCaptureScreen
482
+ analyzer={fakeAnalyzer}
483
+ captureType={CaptureType.selfie}
484
+ />,
485
+ );
486
+ expect(root.findAllByType(StatusBar)[0]!.props.barStyle).toBe(
487
+ "dark-content",
488
+ );
489
+ });
490
+
449
491
  it("renders a translucent status bar so the preview is edge-to-edge", () => {
450
492
  // Translucent status bar = full-bleed window = full-screen oval reference.
451
493
  const { root } = render(
@@ -507,22 +549,22 @@ describe("UseSmileIDCaptureScreen", () => {
507
549
  // Stale events → unsatisfied reset (drops them) → fresh events → finished.
508
550
  emit({
509
551
  output,
510
- state: { type: ScanStateType.Found },
552
+ state: { type: UseSmileIDScanStateType.Found },
511
553
  capturedEvents: [staleSelfie, staleLiveness],
512
554
  });
513
555
  emit({
514
556
  output,
515
- state: { type: ScanStateType.Unsatisfied },
557
+ state: { type: UseSmileIDScanStateType.Unsatisfied },
516
558
  capturedEvents: [],
517
559
  });
518
560
  emit({
519
561
  output,
520
- state: { type: ScanStateType.Found },
562
+ state: { type: UseSmileIDScanStateType.Found },
521
563
  capturedEvents: [freshSelfie, freshLiveness],
522
564
  });
523
565
  emit({
524
566
  output,
525
- state: { type: ScanStateType.Finished },
567
+ state: { type: UseSmileIDScanStateType.Finished },
526
568
  capturedEvents: [],
527
569
  });
528
570
 
@@ -541,6 +583,58 @@ describe("UseSmileIDCaptureScreen", () => {
541
583
  expect.objectContaining({ retries: 1 }),
542
584
  );
543
585
  });
586
+
587
+ it("replays the shared selfie scenario to a full capture completion", async () => {
588
+ // Drives the capture gap headlessly through the frame stream's onResult
589
+ // seam: the shared scenario replayed to `finished` must reach
590
+ // onCaptureComplete with the scripted selfie + liveness events.
591
+ const onCaptureComplete = vi.fn();
592
+ const analyzer = makeFakeAnalyzer();
593
+ capturedOnResult = null;
594
+ render(
595
+ <UseSmileIDCaptureScreen
596
+ analyzer={analyzer}
597
+ captureType={CaptureType.selfie}
598
+ onCaptureComplete={onCaptureComplete}
599
+ />,
600
+ );
601
+ expect(capturedOnResult).not.toBeNull();
602
+
603
+ // Consume the real shared scenario (not a hand-written copy) so a drift in
604
+ // the canonical script is caught here rather than silently diverging.
605
+ const scenarioDir = dirname(import.meta.url.replace(/^file:\/\//, ""));
606
+ const scenario = parseScriptedScenario(
607
+ JSON.parse(
608
+ readFileSync(
609
+ join(
610
+ scenarioDir,
611
+ "../../../../../e2e/scenarios/selfie-happy-path.json",
612
+ ),
613
+ "utf8",
614
+ ),
615
+ ),
616
+ );
617
+ for (const result of scriptedFaceScanResults(scenario, {
618
+ livenessCount: 2,
619
+ })) {
620
+ act(() => {
621
+ capturedOnResult!(result);
622
+ });
623
+ }
624
+ await act(async () => {
625
+ await Promise.resolve();
626
+ await Promise.resolve();
627
+ });
628
+
629
+ expect(onCaptureComplete).toHaveBeenCalledTimes(1);
630
+ const events = onCaptureComplete.mock.calls[0][0] as CaptureReadyEvent[];
631
+ expect(
632
+ events.filter((e) => e.kind === CaptureReadyEventKind.Selfie),
633
+ ).toHaveLength(1);
634
+ expect(
635
+ events.filter((e) => e.kind === CaptureReadyEventKind.Liveness),
636
+ ).toHaveLength(2);
637
+ });
544
638
  });
545
639
 
546
640
  /**
@@ -566,9 +660,9 @@ describe("session-boundary epoch reset — debouncer regression", () => {
566
660
  isImmediate: () => false,
567
661
  });
568
662
 
569
- let previousScanState: ScanState | null = null;
663
+ let previousScanState: UseSmileIDScanState | null = null;
570
664
  const processTransition = (
571
- nextScanState: ScanState,
665
+ nextScanState: UseSmileIDScanState,
572
666
  nextFeedbackKey: string,
573
667
  ) => {
574
668
  const boundary =
@@ -585,7 +679,7 @@ describe("session-boundary epoch reset — debouncer regression", () => {
585
679
 
586
680
  // found — smile key settles after the debounce window.
587
681
  processTransition(
588
- { type: ScanStateType.Found, feedback: "si_capture_smile" },
682
+ { type: UseSmileIDScanStateType.Found, feedback: "si_capture_smile" },
589
683
  "si_capture_smile",
590
684
  );
591
685
  vi.advanceTimersByTime(UseSmileIDMotion.feedbackDebounce);
@@ -593,7 +687,7 @@ describe("session-boundary epoch reset — debouncer regression", () => {
593
687
 
594
688
  // Entering unsatisfied is not a boundary — no reset.
595
689
  processTransition(
596
- { type: ScanStateType.Unsatisfied },
690
+ { type: UseSmileIDScanStateType.Unsatisfied },
597
691
  "si_selfie_capture_unsatisfied",
598
692
  );
599
693
  vi.advanceTimersByTime(UseSmileIDMotion.feedbackDebounce);
@@ -605,7 +699,7 @@ describe("session-boundary epoch reset — debouncer regression", () => {
605
699
  // Leaving unsatisfied is a boundary — without the reset the repeated
606
700
  // smile key would be suppressed as a duplicate.
607
701
  processTransition(
608
- { type: ScanStateType.Found, feedback: "si_capture_smile" },
702
+ { type: UseSmileIDScanStateType.Found, feedback: "si_capture_smile" },
609
703
  "si_capture_smile",
610
704
  );
611
705
  // seed() emits synchronously; no timer advance needed.
@@ -615,4 +709,420 @@ describe("session-boundary epoch reset — debouncer regression", () => {
615
709
  "si_capture_smile",
616
710
  ]);
617
711
  });
712
+
713
+ // Document capture has no Start button, so a scan timeout has to leave the
714
+ // user a way forward — the sibling SDKs surface the manual shutter as the
715
+ // recovery affordance (their shared scan state sets `showCaptureButton` on
716
+ // the timeout state). Without these, a timed-out flow is a dead end: only
717
+ // Back and ✕ remain.
718
+ describe("document capture timeout recovery", () => {
719
+ function renderDocumentScreen(props: {
720
+ readonly captureMode: ReturnType<typeof DocumentCaptureMode.AutoCapture>;
721
+ readonly allowGalleryUpload?: boolean;
722
+ }) {
723
+ const tree = render(
724
+ <UseSmileIDCaptureScreen
725
+ analyzer={fakeAnalyzer}
726
+ captureType={CaptureType.document}
727
+ captureMode={props.captureMode}
728
+ allowGalleryUpload={props.allowGalleryUpload ?? true}
729
+ />,
730
+ );
731
+ // The overlay and its controls only mount once the canvas is measured;
732
+ // `onLayout` never fires under the test renderer, so drive it (landscape,
733
+ // as a document capture screen is).
734
+ act(() => {
735
+ findByTestID(tree.root, "si_capture_screen")!.props.onLayout({
736
+ nativeEvent: { layout: { width: 800, height: 400 } },
737
+ });
738
+ });
739
+ return tree;
740
+ }
741
+
742
+ function emitTimeout() {
743
+ expect(capturedOnResult).not.toBeNull();
744
+ act(() => {
745
+ capturedOnResult!({
746
+ output: { faces: [], brightness: 0 },
747
+ state: { type: UseSmileIDScanStateType.Timeout },
748
+ capturedEvents: [],
749
+ });
750
+ });
751
+ }
752
+
753
+ it("offers the manual shutter after a timeout, in every capture mode", () => {
754
+ const { root } = renderDocumentScreen({
755
+ captureMode: DocumentCaptureMode.AutoCapture,
756
+ });
757
+
758
+ emitTimeout();
759
+
760
+ expect(findByTestID(root, "si_document_manual_shutter")).not.toBeNull();
761
+ });
762
+
763
+ it("keeps the gallery button available after a timeout", () => {
764
+ const { root } = renderDocumentScreen({
765
+ captureMode: DocumentCaptureMode.AutoCaptureWithManualFallback({
766
+ activateManualAfterMs: 3000,
767
+ }),
768
+ });
769
+
770
+ emitTimeout();
771
+
772
+ expect(findByTestID(root, "si_document_gallery_upload")).not.toBeNull();
773
+ });
774
+
775
+ it("leaves the manual shutter usable after a timeout", () => {
776
+ const { root } = renderDocumentScreen({
777
+ captureMode: DocumentCaptureMode.ManualCapture,
778
+ });
779
+
780
+ emitTimeout();
781
+
782
+ const shutter = findByTestID(root, "si_document_manual_shutter");
783
+ expect(shutter).not.toBeNull();
784
+ // The timeout must not latch the one-shot delivery gate, or the button
785
+ // renders but does nothing.
786
+ act(() => {
787
+ shutter!.props.onPress();
788
+ });
789
+ expect(mockedTakePhoto).toHaveBeenCalledTimes(1);
790
+ });
791
+ });
792
+
793
+ describe("selfie capture timeout recovery", () => {
794
+ function renderSelfieScreen() {
795
+ return render(
796
+ <UseSmileIDCaptureScreen
797
+ analyzer={fakeAnalyzer}
798
+ captureType={CaptureType.selfie}
799
+ />,
800
+ );
801
+ }
802
+
803
+ function startCapture(root: ReactTestInstance) {
804
+ act(() => {
805
+ findByTestID(root, "si_capture_start_button")!.props.onClick();
806
+ });
807
+ }
808
+
809
+ function emit(state: UseSmileIDScanState) {
810
+ expect(capturedOnResult).not.toBeNull();
811
+ act(() => {
812
+ capturedOnResult!({
813
+ output: { faces: [], brightness: 0 },
814
+ state,
815
+ capturedEvents: [],
816
+ });
817
+ });
818
+ }
819
+
820
+ it("offers Start again after a timeout", () => {
821
+ const { root } = renderSelfieScreen();
822
+ startCapture(root);
823
+
824
+ emit({ type: UseSmileIDScanStateType.Timeout });
825
+
826
+ const start = findByTestID(root, "si_capture_start_button");
827
+ expect(start).not.toBeNull();
828
+ expect(start!.props.enabled).toBe(true);
829
+ });
830
+
831
+ it("resets the native session when Start is tapped after a timeout", async () => {
832
+ const { root } = renderSelfieScreen();
833
+ startCapture(root);
834
+ emit({ type: UseSmileIDScanStateType.Timeout });
835
+ fakeAnalyzer.reset.mockClear();
836
+
837
+ startCapture(root);
838
+ await act(async () => {
839
+ await Promise.resolve();
840
+ });
841
+
842
+ // The native session is terminal after a timeout — without a reset the
843
+ // first retry frame re-reports Timeout and re-latches the error.
844
+ expect(fakeAnalyzer.reset).toHaveBeenCalledTimes(1);
845
+ });
846
+
847
+ it("restarts the session when Start is tapped after a timeout", () => {
848
+ const onStartCapture = vi.fn();
849
+ const { root } = render(
850
+ <UseSmileIDCaptureScreen
851
+ analyzer={fakeAnalyzer}
852
+ captureType={CaptureType.selfie}
853
+ onStartCapture={onStartCapture}
854
+ />,
855
+ );
856
+ startCapture(root);
857
+ emit({ type: UseSmileIDScanStateType.Timeout });
858
+
859
+ startCapture(root);
860
+
861
+ expect(onStartCapture).toHaveBeenCalledTimes(2);
862
+ });
863
+
864
+ it("still delivers a capture that completes on the retry", async () => {
865
+ const onCaptureComplete = vi.fn();
866
+ const { root } = render(
867
+ <UseSmileIDCaptureScreen
868
+ analyzer={fakeAnalyzer}
869
+ captureType={CaptureType.selfie}
870
+ onCaptureComplete={onCaptureComplete}
871
+ />,
872
+ );
873
+ startCapture(root);
874
+ emit({ type: UseSmileIDScanStateType.Timeout });
875
+ startCapture(root);
876
+
877
+ emit({ type: UseSmileIDScanStateType.Finished });
878
+ await act(async () => {
879
+ await Promise.resolve();
880
+ await Promise.resolve();
881
+ });
882
+
883
+ expect(onCaptureComplete).toHaveBeenCalledTimes(1);
884
+ });
885
+
886
+ it("drops a capture that lands after the timeout was already reported", async () => {
887
+ const onCaptureComplete = vi.fn();
888
+ const onCaptureTimeout = vi.fn();
889
+ const { root } = render(
890
+ <UseSmileIDCaptureScreen
891
+ analyzer={fakeAnalyzer}
892
+ captureType={CaptureType.selfie}
893
+ onCaptureComplete={onCaptureComplete}
894
+ onCaptureTimeout={onCaptureTimeout}
895
+ />,
896
+ );
897
+ startCapture(root);
898
+
899
+ emit({ type: UseSmileIDScanStateType.Timeout });
900
+ emit({ type: UseSmileIDScanStateType.Finished });
901
+ await act(async () => {
902
+ await Promise.resolve();
903
+ await Promise.resolve();
904
+ });
905
+
906
+ expect(onCaptureTimeout).toHaveBeenCalledTimes(1);
907
+ expect(onCaptureComplete).not.toHaveBeenCalled();
908
+ });
909
+
910
+ it("does not pad the retry with frames banked before the timeout", async () => {
911
+ const onCaptureComplete = vi.fn();
912
+ const { root } = render(
913
+ <UseSmileIDCaptureScreen
914
+ analyzer={fakeAnalyzer}
915
+ captureType={CaptureType.selfie}
916
+ onCaptureComplete={onCaptureComplete}
917
+ />,
918
+ );
919
+ startCapture(root);
920
+ act(() => {
921
+ capturedOnResult!({
922
+ output: { faces: [], brightness: 0 },
923
+ state: { type: UseSmileIDScanStateType.Found },
924
+ capturedEvents: [
925
+ {
926
+ kind: CaptureReadyEventKind.Liveness,
927
+ captureIndex: 0,
928
+ url: "file:///stale.jpg",
929
+ },
930
+ ] as CaptureReadyEvent[],
931
+ });
932
+ });
933
+ emit({ type: UseSmileIDScanStateType.Timeout });
934
+
935
+ startCapture(root);
936
+ act(() => {
937
+ capturedOnResult!({
938
+ output: { faces: [], brightness: 0 },
939
+ state: { type: UseSmileIDScanStateType.Found },
940
+ capturedEvents: [
941
+ {
942
+ kind: CaptureReadyEventKind.Selfie,
943
+ captureIndex: 0,
944
+ url: "file:///fresh.jpg",
945
+ },
946
+ ] as CaptureReadyEvent[],
947
+ });
948
+ });
949
+ emit({ type: UseSmileIDScanStateType.Finished });
950
+ await act(async () => {
951
+ await Promise.resolve();
952
+ await Promise.resolve();
953
+ });
954
+
955
+ expect(onCaptureComplete).toHaveBeenCalledTimes(1);
956
+ const [delivered] = onCaptureComplete.mock.calls[0] as [
957
+ readonly CaptureReadyEvent[],
958
+ ];
959
+ expect(delivered.map((event) => event.url)).toEqual([
960
+ "file:///fresh.jpg",
961
+ ]);
962
+ });
963
+
964
+ it("reports the timeout to the partner exactly once", () => {
965
+ const onCaptureTimeout = vi.fn();
966
+ const { root } = render(
967
+ <UseSmileIDCaptureScreen
968
+ analyzer={fakeAnalyzer}
969
+ captureType={CaptureType.selfie}
970
+ onCaptureTimeout={onCaptureTimeout}
971
+ />,
972
+ );
973
+ startCapture(root);
974
+
975
+ emit({ type: UseSmileIDScanStateType.Timeout });
976
+ emit({ type: UseSmileIDScanStateType.Timeout });
977
+
978
+ expect(onCaptureTimeout).toHaveBeenCalledTimes(1);
979
+ });
980
+ });
981
+
982
+ describe("no camera frames at all", () => {
983
+ beforeEach(() => {
984
+ vi.useFakeTimers();
985
+ });
986
+
987
+ afterEach(() => {
988
+ vi.useRealTimers();
989
+ });
990
+
991
+ function renderAndStart() {
992
+ const tree = render(
993
+ <UseSmileIDCaptureScreen
994
+ analyzer={fakeAnalyzer}
995
+ captureType={CaptureType.selfie}
996
+ />,
997
+ );
998
+ act(() => {
999
+ findByTestID(tree.root, "si_capture_start_button")!.props.onClick();
1000
+ });
1001
+ return tree;
1002
+ }
1003
+
1004
+ function elapseFirstFrameWait() {
1005
+ act(() => {
1006
+ vi.advanceTimersByTime(10_000);
1007
+ });
1008
+ }
1009
+
1010
+ it("names the cause instead of sitting on the initial instruction", () => {
1011
+ const { root } = renderAndStart();
1012
+
1013
+ elapseFirstFrameWait();
1014
+
1015
+ expect(collectText(findByTestID(root, "si_capture_feedback_text")!)).toBe(
1016
+ enStrings.si_capture_camera_unavailable,
1017
+ );
1018
+ });
1019
+
1020
+ it("leaves the user a way to retry", () => {
1021
+ const { root } = renderAndStart();
1022
+
1023
+ elapseFirstFrameWait();
1024
+
1025
+ const start = findByTestID(root, "si_capture_start_button");
1026
+ expect(start).not.toBeNull();
1027
+ expect(start!.props.enabled).toBe(true);
1028
+ });
1029
+
1030
+ it("reports a terminal timeout so the flow is not left waiting", () => {
1031
+ const onCaptureTimeout = vi.fn();
1032
+ const tree = render(
1033
+ <UseSmileIDCaptureScreen
1034
+ analyzer={fakeAnalyzer}
1035
+ captureType={CaptureType.selfie}
1036
+ onCaptureTimeout={onCaptureTimeout}
1037
+ />,
1038
+ );
1039
+ act(() => {
1040
+ findByTestID(tree.root, "si_capture_start_button")!.props.onClick();
1041
+ });
1042
+
1043
+ elapseFirstFrameWait();
1044
+
1045
+ expect(onCaptureTimeout).toHaveBeenCalledTimes(1);
1046
+ });
1047
+
1048
+ it("drops the stall copy once a frame finally arrives", () => {
1049
+ const { root } = renderAndStart();
1050
+ elapseFirstFrameWait();
1051
+ expect(collectText(findByTestID(root, "si_capture_feedback_text")!)).toBe(
1052
+ enStrings.si_capture_camera_unavailable,
1053
+ );
1054
+
1055
+ act(() => {
1056
+ capturedOnResult!({
1057
+ output: { faces: [], brightness: 0 },
1058
+ state: { type: UseSmileIDScanStateType.Initial },
1059
+ capturedEvents: [],
1060
+ });
1061
+ });
1062
+
1063
+ expect(
1064
+ collectText(findByTestID(root, "si_capture_feedback_text")!),
1065
+ ).not.toBe(enStrings.si_capture_camera_unavailable);
1066
+ });
1067
+
1068
+ it("still fires when the partner passes an inline callback", () => {
1069
+ const onCaptureTimeout = vi.fn();
1070
+ const screen = (
1071
+ <UseSmileIDCaptureScreen
1072
+ analyzer={fakeAnalyzer}
1073
+ captureType={CaptureType.selfie}
1074
+ onCaptureTimeout={(state) => onCaptureTimeout(state)}
1075
+ />
1076
+ );
1077
+ const tree = render(screen);
1078
+ act(() => {
1079
+ findByTestID(tree.root, "si_capture_start_button")!.props.onClick();
1080
+ });
1081
+
1082
+ for (let step = 0; step < 4; step += 1) {
1083
+ act(() => {
1084
+ tree.update(
1085
+ <UseSmileIDCaptureScreen
1086
+ analyzer={fakeAnalyzer}
1087
+ captureType={CaptureType.selfie}
1088
+ onCaptureTimeout={(state) => onCaptureTimeout(state)}
1089
+ />,
1090
+ );
1091
+ });
1092
+ act(() => {
1093
+ vi.advanceTimersByTime(3_000);
1094
+ });
1095
+ }
1096
+
1097
+ expect(onCaptureTimeout).toHaveBeenCalledTimes(1);
1098
+ });
1099
+
1100
+ it("stays quiet once a frame has been analyzed", () => {
1101
+ const onCaptureTimeout = vi.fn();
1102
+ const tree = render(
1103
+ <UseSmileIDCaptureScreen
1104
+ analyzer={fakeAnalyzer}
1105
+ captureType={CaptureType.selfie}
1106
+ onCaptureTimeout={onCaptureTimeout}
1107
+ />,
1108
+ );
1109
+ act(() => {
1110
+ findByTestID(tree.root, "si_capture_start_button")!.props.onClick();
1111
+ });
1112
+ act(() => {
1113
+ capturedOnResult!({
1114
+ output: { faces: [], brightness: 0 },
1115
+ state: { type: UseSmileIDScanStateType.Initial },
1116
+ capturedEvents: [],
1117
+ });
1118
+ });
1119
+
1120
+ elapseFirstFrameWait();
1121
+
1122
+ expect(onCaptureTimeout).not.toHaveBeenCalled();
1123
+ expect(
1124
+ collectText(findByTestID(tree.root, "si_capture_feedback_text")!),
1125
+ ).not.toBe(enStrings.si_capture_camera_unavailable);
1126
+ });
1127
+ });
618
1128
  });
@@ -62,7 +62,9 @@ describe("UseSmileIDDocumentBackInstructions", () => {
62
62
  />,
63
63
  );
64
64
  expect(findByTestID(root, "si_document_back_instructions")).not.toBeNull();
65
- expect(findTextNode(root, "Flip your Green Book")).not.toBeNull();
65
+ expect(
66
+ findTextNode(root, "Flip your \u2068Green Book\u2069"),
67
+ ).not.toBeNull();
66
68
  });
67
69
 
68
70
  it("invokes onCaptureBack from the primary button", () => {
@@ -104,7 +106,7 @@ describe("UseSmileIDDocumentBackInstructions", () => {
104
106
  const skip = findByTestID(root, "si_document_skip_back_button");
105
107
  expect(skip).not.toBeNull();
106
108
  act(() => {
107
- (skip!.props.onPress as () => void)();
109
+ (skip!.props.onClick as () => void)();
108
110
  });
109
111
  expect(onSkip).toHaveBeenCalledTimes(1);
110
112
  });