@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,461 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import * as React from "react";
3
+ import { create as createRenderer, act } from "react-test-renderer";
4
+ import { Pressable, View } from "react-native";
5
+ import * as VisionCamera from "react-native-vision-camera";
6
+ import { FrameProcessorPluginNotRegisteredException } from "@smileid/usesmileid_bridge";
7
+
8
+ import { UseSmileIDCameraPreview } from "../use_smile_id_camera_preview";
9
+ import { UseSmileIDCameraSystem } from "../use_smile_id_camera_system";
10
+ import { CameraLens } from "../camera_lens";
11
+ import { CameraDefaults } from "../camera_defaults";
12
+ import type { CameraSystemState } from "../camera_configuration";
13
+ import type { FocusState } from "../focus_state";
14
+
15
+ /**
16
+ * Component-level tests for `<UseSmileIDCameraPreview>` — the seam
17
+ * between the camera orchestrator and vision-camera. The native
18
+ * bindings are mocked in `setup_react_native.ts`; these tests drive
19
+ * the boundary the preview owns: ref attach/detach, subscription
20
+ * fan-out, plugin initialisation + the missing-plugin error, the
21
+ * frame-processor worklet body, layout-normalised tap-to-focus, and
22
+ * the function-as-children overlay contract.
23
+ */
24
+
25
+ const mockedUseCameraDevice = vi.mocked(VisionCamera.useCameraDevice);
26
+ const mockedUseCameraFormat = vi.mocked(VisionCamera.useCameraFormat);
27
+ const mockedUseFrameProcessor = vi.mocked(VisionCamera.useFrameProcessor);
28
+ const mockedInitPlugin = vi.mocked(
29
+ VisionCamera.VisionCameraProxy.initFrameProcessorPlugin,
30
+ );
31
+
32
+ type CameraDevice = ReturnType<typeof VisionCamera.useCameraDevice>;
33
+
34
+ const fakeDevice = {
35
+ id: "test-front",
36
+ position: "front" as const,
37
+ hasFlash: false,
38
+ hasTorch: false,
39
+ formats: [],
40
+ isMultiCam: false,
41
+ supportsRawCapture: false,
42
+ supportsLowLightBoost: false,
43
+ supportsFocus: true,
44
+ name: "Front Camera (test)",
45
+ } as unknown as CameraDevice;
46
+
47
+ function render(element: React.ReactElement) {
48
+ let tree!: ReturnType<typeof createRenderer>;
49
+ act(() => {
50
+ tree = createRenderer(element);
51
+ });
52
+ return tree;
53
+ }
54
+
55
+ type CameraProps = {
56
+ readonly isActive: boolean;
57
+ readonly fps: number;
58
+ readonly photo: boolean;
59
+ readonly device: unknown;
60
+ readonly outputOrientation: string;
61
+ readonly format?: unknown;
62
+ readonly frameProcessor?: unknown;
63
+ readonly onOutputOrientationChanged: (orientation: string) => void;
64
+ };
65
+
66
+ function cameraProps(root: ReturnType<typeof render>["root"]): CameraProps {
67
+ return root.findByType(VisionCamera.Camera).props as unknown as CameraProps;
68
+ }
69
+
70
+ function findCameras(root: ReturnType<typeof render>["root"]) {
71
+ return root.findAllByType(VisionCamera.Camera);
72
+ }
73
+
74
+ /** Drives `onLayout` on the preview's root `<View>` so taps normalise. */
75
+ function layout(
76
+ root: ReturnType<typeof render>["root"],
77
+ width: number,
78
+ height: number,
79
+ ) {
80
+ const view = root.findAllByType(View)[0];
81
+ act(() => {
82
+ (
83
+ view.props as {
84
+ onLayout: (event: {
85
+ nativeEvent: { layout: { width: number; height: number } };
86
+ }) => void;
87
+ }
88
+ ).onLayout({ nativeEvent: { layout: { width, height } } });
89
+ });
90
+ }
91
+
92
+ function tap(
93
+ root: ReturnType<typeof render>["root"],
94
+ locationX: number,
95
+ locationY: number,
96
+ ) {
97
+ const pressable = root.findAllByType(Pressable)[0];
98
+ act(() => {
99
+ (
100
+ pressable.props as {
101
+ onPress: (event: {
102
+ nativeEvent: { locationX: number; locationY: number };
103
+ }) => void;
104
+ }
105
+ ).onPress({ nativeEvent: { locationX, locationY } });
106
+ });
107
+ }
108
+
109
+ describe("UseSmileIDCameraPreview", () => {
110
+ beforeEach(() => {
111
+ mockedUseCameraDevice.mockReset();
112
+ mockedUseCameraDevice.mockReturnValue(fakeDevice);
113
+ mockedUseCameraFormat.mockReset();
114
+ mockedUseCameraFormat.mockReturnValue(
115
+ undefined as unknown as ReturnType<typeof VisionCamera.useCameraFormat>,
116
+ );
117
+ mockedUseFrameProcessor.mockReset();
118
+ mockedUseFrameProcessor.mockImplementation(
119
+ (worklet) =>
120
+ worklet as unknown as ReturnType<typeof VisionCamera.useFrameProcessor>,
121
+ );
122
+ mockedInitPlugin.mockReset();
123
+ mockedInitPlugin.mockReturnValue({
124
+ call: vi.fn(() => ({})),
125
+ } as unknown as ReturnType<
126
+ typeof VisionCamera.VisionCameraProxy.initFrameProcessorPlugin
127
+ >);
128
+ });
129
+
130
+ it("renders the camera for the system's current lens", () => {
131
+ const system = new UseSmileIDCameraSystem(CameraLens.back);
132
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
133
+
134
+ expect(mockedUseCameraDevice).toHaveBeenCalledWith(CameraLens.back);
135
+ expect(cameraProps(root).photo).toBe(true);
136
+ expect(cameraProps(root).outputOrientation).toBe("device");
137
+ });
138
+
139
+ it("omits the camera entirely when no device is available", () => {
140
+ mockedUseCameraDevice.mockReturnValue(
141
+ undefined as unknown as CameraDevice as CameraDevice,
142
+ );
143
+ const system = new UseSmileIDCameraSystem();
144
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
145
+
146
+ expect(findCameras(root)).toHaveLength(0);
147
+ // The tap surface still mounts so overlays render without a device.
148
+ expect(root.findAllByType(Pressable)).toHaveLength(1);
149
+ });
150
+
151
+ it("drives isActive off the system's streaming state", async () => {
152
+ const system = new UseSmileIDCameraSystem();
153
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
154
+ expect(cameraProps(root).isActive).toBe(false);
155
+
156
+ await act(async () => {
157
+ await system.start();
158
+ });
159
+ expect(cameraProps(root).isActive).toBe(true);
160
+
161
+ act(() => {
162
+ system.stop();
163
+ });
164
+ expect(cameraProps(root).isActive).toBe(false);
165
+ });
166
+
167
+ it("pins the default fps and honours a configured override", () => {
168
+ const system = new UseSmileIDCameraSystem();
169
+ const first = render(<UseSmileIDCameraPreview cameraSystem={system} />);
170
+ expect(cameraProps(first.root).fps).toBe(CameraDefaults.analysisTargetFps);
171
+
172
+ const configured = new UseSmileIDCameraSystem();
173
+ configured.configure({ fps: 24 });
174
+ const second = render(
175
+ <UseSmileIDCameraPreview cameraSystem={configured} />,
176
+ );
177
+ expect(cameraProps(second.root).fps).toBe(24);
178
+ });
179
+
180
+ it("requests a 4:3 30fps analysis format and forwards it when resolved", () => {
181
+ const format = { videoWidth: 640, videoHeight: 480 };
182
+ mockedUseCameraFormat.mockReturnValue(
183
+ format as unknown as ReturnType<typeof VisionCamera.useCameraFormat>,
184
+ );
185
+ const system = new UseSmileIDCameraSystem();
186
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
187
+
188
+ expect(mockedUseCameraFormat).toHaveBeenCalledWith(fakeDevice, [
189
+ { videoAspectRatio: 4 / 3 },
190
+ {
191
+ videoResolution: {
192
+ width: CameraDefaults.analysisTargetResolution.width,
193
+ height: CameraDefaults.analysisTargetResolution.height,
194
+ },
195
+ },
196
+ { fps: CameraDefaults.analysisTargetFps },
197
+ ]);
198
+ expect(cameraProps(root).format).toBe(format);
199
+ });
200
+
201
+ it("omits the format key entirely when none resolves", () => {
202
+ const system = new UseSmileIDCameraSystem();
203
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
204
+ expect("format" in cameraProps(root)).toBe(false);
205
+ });
206
+
207
+ it("attaches the camera ref on mount and detaches it on unmount", () => {
208
+ const system = new UseSmileIDCameraSystem();
209
+ const attachRef = vi.spyOn(system, "attachRef");
210
+ const tree = render(<UseSmileIDCameraPreview cameraSystem={system} />);
211
+
212
+ expect(attachRef).toHaveBeenCalledTimes(1);
213
+ expect(attachRef.mock.calls[0]?.[0]).not.toBeNull();
214
+
215
+ act(() => {
216
+ tree.unmount();
217
+ });
218
+ expect(attachRef).toHaveBeenLastCalledWith(null);
219
+ });
220
+
221
+ it("unsubscribes from the system on unmount", () => {
222
+ const system = new UseSmileIDCameraSystem();
223
+ const unsubscribe = vi.fn();
224
+ vi.spyOn(system, "subscribe").mockReturnValue(unsubscribe);
225
+
226
+ const tree = render(<UseSmileIDCameraPreview cameraSystem={system} />);
227
+ expect(unsubscribe).not.toHaveBeenCalled();
228
+
229
+ act(() => {
230
+ tree.unmount();
231
+ });
232
+ expect(unsubscribe).toHaveBeenCalledTimes(1);
233
+ });
234
+
235
+ it("passes live state and focus state to function-as-children overlays", async () => {
236
+ const system = new UseSmileIDCameraSystem();
237
+ const seen: Array<{ state: CameraSystemState; focusState: FocusState }> =
238
+ [];
239
+ render(
240
+ <UseSmileIDCameraPreview cameraSystem={system}>
241
+ {(args) => {
242
+ seen.push(args);
243
+ return null;
244
+ }}
245
+ </UseSmileIDCameraPreview>,
246
+ );
247
+
248
+ expect(seen[0]?.state.isStreaming).toBe(false);
249
+ expect(seen[0]?.focusState.kind).toBe("unspecified");
250
+
251
+ await act(async () => {
252
+ await system.start();
253
+ });
254
+ const latest = seen[seen.length - 1];
255
+ expect(latest?.state.isStreaming).toBe(true);
256
+ });
257
+
258
+ it("renders plain node children inside the tap surface", () => {
259
+ const system = new UseSmileIDCameraSystem();
260
+ const { root } = render(
261
+ <UseSmileIDCameraPreview cameraSystem={system}>
262
+ <View testID="si_overlay" />
263
+ </UseSmileIDCameraPreview>,
264
+ );
265
+ expect(
266
+ root.findAllByProps({ testID: "si_overlay" }).length,
267
+ ).toBeGreaterThan(0);
268
+ });
269
+
270
+ it("normalises a tap into [0..1] focus coordinates after layout", () => {
271
+ const system = new UseSmileIDCameraSystem();
272
+ const tapToFocus = vi
273
+ .spyOn(system, "tapToFocus")
274
+ .mockResolvedValue(undefined);
275
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
276
+
277
+ layout(root, 200, 400);
278
+ tap(root, 50, 100);
279
+
280
+ expect(tapToFocus).toHaveBeenCalledWith({ x: 0.25, y: 0.25 });
281
+ });
282
+
283
+ it("ignores taps before the preview has been laid out", () => {
284
+ const system = new UseSmileIDCameraSystem();
285
+ const tapToFocus = vi
286
+ .spyOn(system, "tapToFocus")
287
+ .mockResolvedValue(undefined);
288
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
289
+
290
+ tap(root, 50, 100);
291
+ expect(tapToFocus).not.toHaveBeenCalled();
292
+
293
+ // A zero-height layout is still degenerate — division would be Infinity.
294
+ layout(root, 200, 0);
295
+ tap(root, 50, 100);
296
+ expect(tapToFocus).not.toHaveBeenCalled();
297
+ });
298
+
299
+ it("skips plugin initialisation when no frame analysis is supplied", () => {
300
+ const system = new UseSmileIDCameraSystem();
301
+ const { root } = render(<UseSmileIDCameraPreview cameraSystem={system} />);
302
+
303
+ expect(mockedInitPlugin).not.toHaveBeenCalled();
304
+ expect("frameProcessor" in cameraProps(root)).toBe(false);
305
+ });
306
+
307
+ it("initialises the named plugin and attaches a frame processor", () => {
308
+ const system = new UseSmileIDCameraSystem();
309
+ const onResult = vi.fn();
310
+ const { root } = render(
311
+ <UseSmileIDCameraPreview
312
+ cameraSystem={system}
313
+ frameAnalysis={{ pluginName: "UseSmileIDFaceScan", onResult }}
314
+ />,
315
+ );
316
+
317
+ expect(mockedInitPlugin).toHaveBeenCalledWith("UseSmileIDFaceScan", {});
318
+ expect(cameraProps(root).frameProcessor).toBeTypeOf("function");
319
+ });
320
+
321
+ it("forwards each frame's raw result to onResult with the device rotation", () => {
322
+ const call = vi.fn(() => ({ state: { type: "initial" } }));
323
+ mockedInitPlugin.mockReturnValue({ call } as unknown as ReturnType<
324
+ typeof VisionCamera.VisionCameraProxy.initFrameProcessorPlugin
325
+ >);
326
+ const system = new UseSmileIDCameraSystem();
327
+ const onResult = vi.fn();
328
+ const { root } = render(
329
+ <UseSmileIDCameraPreview
330
+ cameraSystem={system}
331
+ frameAnalysis={{ pluginName: "UseSmileIDFaceScan", onResult }}
332
+ />,
333
+ );
334
+
335
+ act(() => {
336
+ cameraProps(root).onOutputOrientationChanged("landscape-left");
337
+ });
338
+
339
+ const frame = { width: 640, height: 480 };
340
+ const worklet = cameraProps(root).frameProcessor as (
341
+ frame: unknown,
342
+ ) => void;
343
+ worklet(frame);
344
+
345
+ expect(call).toHaveBeenCalledWith(frame, { rotation: 90 });
346
+ expect(onResult).toHaveBeenCalledWith({ state: { type: "initial" } });
347
+ });
348
+
349
+ it("frame processor is inert when the plugin failed to initialise", () => {
350
+ mockedInitPlugin.mockReturnValue(
351
+ null as unknown as ReturnType<
352
+ typeof VisionCamera.VisionCameraProxy.initFrameProcessorPlugin
353
+ >,
354
+ );
355
+ const system = new UseSmileIDCameraSystem();
356
+ const onResult = vi.fn();
357
+ render(
358
+ <UseSmileIDCameraPreview
359
+ cameraSystem={system}
360
+ frameAnalysis={{ pluginName: "UseSmileIDFaceScan", onResult }}
361
+ />,
362
+ );
363
+
364
+ const worklet = mockedUseFrameProcessor.mock.calls[0]?.[0] as (
365
+ frame: unknown,
366
+ ) => void;
367
+ expect(() => worklet({ width: 1, height: 1 })).not.toThrow();
368
+ expect(onResult).not.toHaveBeenCalled();
369
+ });
370
+
371
+ it("reports a missing native plugin exactly once through onError", () => {
372
+ mockedInitPlugin.mockReturnValue(
373
+ null as unknown as ReturnType<
374
+ typeof VisionCamera.VisionCameraProxy.initFrameProcessorPlugin
375
+ >,
376
+ );
377
+ const system = new UseSmileIDCameraSystem();
378
+ const onError = vi.fn();
379
+ const frameAnalysis = {
380
+ pluginName: "UseSmileIDMissingPlugin",
381
+ onResult: vi.fn(),
382
+ onError,
383
+ };
384
+ const tree = render(
385
+ <UseSmileIDCameraPreview
386
+ cameraSystem={system}
387
+ frameAnalysis={frameAnalysis}
388
+ />,
389
+ );
390
+
391
+ expect(onError).toHaveBeenCalledTimes(1);
392
+ expect(onError.mock.calls[0]?.[0]).toBeInstanceOf(
393
+ FrameProcessorPluginNotRegisteredException,
394
+ );
395
+
396
+ // A re-render with the same plugin name must not re-fire.
397
+ act(() => {
398
+ tree.update(
399
+ <UseSmileIDCameraPreview
400
+ cameraSystem={system}
401
+ frameAnalysis={frameAnalysis}
402
+ />,
403
+ );
404
+ });
405
+ expect(onError).toHaveBeenCalledTimes(1);
406
+ });
407
+
408
+ it("stays silent when the plugin resolves", () => {
409
+ const system = new UseSmileIDCameraSystem();
410
+ const onError = vi.fn();
411
+ render(
412
+ <UseSmileIDCameraPreview
413
+ cameraSystem={system}
414
+ frameAnalysis={{
415
+ pluginName: "UseSmileIDFaceScan",
416
+ onResult: vi.fn(),
417
+ onError,
418
+ }}
419
+ />,
420
+ );
421
+ expect(onError).not.toHaveBeenCalled();
422
+ });
423
+
424
+ it("picks up a swapped onError handler without re-firing the effect", () => {
425
+ mockedInitPlugin.mockReturnValue(
426
+ null as unknown as ReturnType<
427
+ typeof VisionCamera.VisionCameraProxy.initFrameProcessorPlugin
428
+ >,
429
+ );
430
+ const system = new UseSmileIDCameraSystem();
431
+ const first = vi.fn();
432
+ const second = vi.fn();
433
+ const onResult = vi.fn();
434
+ const tree = render(
435
+ <UseSmileIDCameraPreview
436
+ cameraSystem={system}
437
+ frameAnalysis={{
438
+ pluginName: "UseSmileIDMissingPlugin",
439
+ onResult,
440
+ onError: first,
441
+ }}
442
+ />,
443
+ );
444
+ expect(first).toHaveBeenCalledTimes(1);
445
+
446
+ act(() => {
447
+ tree.update(
448
+ <UseSmileIDCameraPreview
449
+ cameraSystem={system}
450
+ frameAnalysis={{
451
+ pluginName: "UseSmileIDMissingPlugin",
452
+ onResult,
453
+ onError: second,
454
+ }}
455
+ />,
456
+ );
457
+ });
458
+ expect(second).not.toHaveBeenCalled();
459
+ expect(first).toHaveBeenCalledTimes(1);
460
+ });
461
+ });
@@ -0,0 +1,103 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ /**
4
+ * Boundary characterization for `UseSmileIDCameraSystem`'s lifecycle state machine
5
+ * (permission gating, running/streaming flags, lens config, listener fan-out). The
6
+ * frame loop is a vision-camera worklet pinned separately by
7
+ * `use_smile_id_frame_stream.test.ts`. Must hold byte-identically across any
8
+ * camera-implementation swap.
9
+ */
10
+ const permissions = vi.hoisted(() => ({
11
+ getStatus: vi.fn(),
12
+ request: vi.fn(),
13
+ }));
14
+
15
+ vi.mock("../camera_permissions", () => ({
16
+ getCameraPermissionStatus: permissions.getStatus,
17
+ requestCameraPermission: permissions.request,
18
+ }));
19
+
20
+ import { CameraLens } from "../camera_lens";
21
+ import { UseSmileIDCameraSystem } from "../use_smile_id_camera_system";
22
+
23
+ describe("UseSmileIDCameraSystem — boundary characterization", () => {
24
+ beforeEach(() => {
25
+ permissions.getStatus.mockReset();
26
+ permissions.request.mockReset();
27
+ });
28
+
29
+ it("starts idle with the configured lens", () => {
30
+ const system = new UseSmileIDCameraSystem(CameraLens.back);
31
+ const state = system.getState();
32
+ expect(state.isCameraRunning).toBe(false);
33
+ expect(state.isStreaming).toBe(false);
34
+ expect(state.hasPermission).toBe(false);
35
+ expect(state.currentLens).toBe(CameraLens.back);
36
+ });
37
+
38
+ it("start() runs + streams when permission is already granted (no re-prompt)", async () => {
39
+ permissions.getStatus.mockResolvedValue("granted");
40
+ const system = new UseSmileIDCameraSystem();
41
+ await system.start();
42
+ const state = system.getState();
43
+ expect(state.isCameraRunning).toBe(true);
44
+ expect(state.isStreaming).toBe(true);
45
+ expect(state.hasPermission).toBe(true);
46
+ expect(permissions.request).not.toHaveBeenCalled();
47
+ });
48
+
49
+ it("start() prompts when undetermined, then runs on grant", async () => {
50
+ permissions.getStatus.mockResolvedValue("undetermined");
51
+ permissions.request.mockResolvedValue("granted");
52
+ const system = new UseSmileIDCameraSystem();
53
+ await system.start();
54
+ expect(permissions.request).toHaveBeenCalledTimes(1);
55
+ expect(system.getState().isCameraRunning).toBe(true);
56
+ });
57
+
58
+ it("start() stays off when permission is denied", async () => {
59
+ permissions.getStatus.mockResolvedValue("undetermined");
60
+ permissions.request.mockResolvedValue("denied");
61
+ const system = new UseSmileIDCameraSystem();
62
+ await system.start();
63
+ const state = system.getState();
64
+ expect(state.isCameraRunning).toBe(false);
65
+ expect(state.hasPermission).toBe(false);
66
+ });
67
+
68
+ it("start() after release() throws — a released system is unusable", async () => {
69
+ permissions.getStatus.mockResolvedValue("granted");
70
+ const system = new UseSmileIDCameraSystem();
71
+ system.release();
72
+ await expect(system.start()).rejects.toThrow(/after release/);
73
+ });
74
+
75
+ it("stop() pauses running + streaming without releasing (re-startable)", async () => {
76
+ permissions.getStatus.mockResolvedValue("granted");
77
+ const system = new UseSmileIDCameraSystem();
78
+ await system.start();
79
+ system.stop();
80
+ const state = system.getState();
81
+ expect(state.isCameraRunning).toBe(false);
82
+ expect(state.isStreaming).toBe(false);
83
+ });
84
+
85
+ it("configure() swaps the lens and notifies subscribers", () => {
86
+ const system = new UseSmileIDCameraSystem(CameraLens.front);
87
+ const seenLenses: CameraLens[] = [];
88
+ system.subscribe((state) => seenLenses.push(state.currentLens));
89
+ system.configure({ lens: CameraLens.back });
90
+ expect(system.getState().currentLens).toBe(CameraLens.back);
91
+ expect(seenLenses).toContain(CameraLens.back);
92
+ });
93
+
94
+ it("unsubscribe stops further notifications", async () => {
95
+ permissions.getStatus.mockResolvedValue("granted");
96
+ const system = new UseSmileIDCameraSystem();
97
+ const listener = vi.fn();
98
+ const unsubscribe = system.subscribe(listener);
99
+ unsubscribe();
100
+ await system.start();
101
+ expect(listener).not.toHaveBeenCalled();
102
+ });
103
+ });