@goliapkg/sentori-react-native 4.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/MIGRATION.md +131 -0
  2. package/README.md +40 -30
  3. package/android/build.gradle +21 -0
  4. package/android/src/main/java/com/sentori/SentoriCrashHandler.kt +29 -0
  5. package/android/src/main/java/com/sentori/SentoriModule.kt +8 -0
  6. package/android/src/main/java/com/sentori/SentoriScreenshotCapture.kt +36 -4
  7. package/android/src/test/java/com/sentori/SentoriCrashHandlerTest.kt +7 -2
  8. package/android/src/test/resources/robolectric.properties +10 -0
  9. package/ios/SentoriCrashHandler.swift +48 -4
  10. package/ios/SentoriModule.swift +12 -0
  11. package/ios/SentoriScreenshotCapture.swift +34 -3
  12. package/lib/config.d.ts +17 -47
  13. package/lib/config.d.ts.map +1 -1
  14. package/lib/config.js +3 -0
  15. package/lib/config.js.map +1 -1
  16. package/lib/device.d.ts +5 -0
  17. package/lib/device.d.ts.map +1 -0
  18. package/lib/device.js +29 -0
  19. package/lib/device.js.map +1 -0
  20. package/lib/emit-hooks.d.ts +7 -0
  21. package/lib/emit-hooks.d.ts.map +1 -0
  22. package/lib/emit-hooks.js +24 -0
  23. package/lib/emit-hooks.js.map +1 -0
  24. package/lib/error-boundary.d.ts.map +1 -1
  25. package/lib/error-boundary.js +1 -1
  26. package/lib/error-boundary.js.map +1 -1
  27. package/lib/feature-flags.d.ts.map +1 -1
  28. package/lib/handlers/dev-symbolicate.d.ts +1 -1
  29. package/lib/handlers/dev-symbolicate.d.ts.map +1 -1
  30. package/lib/handlers/dev-symbolicate.js +2 -0
  31. package/lib/handlers/dev-symbolicate.js.map +1 -1
  32. package/lib/handlers/global.js +1 -1
  33. package/lib/handlers/global.js.map +1 -1
  34. package/lib/handlers/lifecycle.js +3 -3
  35. package/lib/handlers/lifecycle.js.map +1 -1
  36. package/lib/handlers/network.d.ts +2 -2
  37. package/lib/handlers/network.d.ts.map +1 -1
  38. package/lib/handlers/network.js +69 -187
  39. package/lib/handlers/network.js.map +1 -1
  40. package/lib/handlers/promise.js +1 -1
  41. package/lib/handlers/promise.js.map +1 -1
  42. package/lib/handlers/screenshot.d.ts.map +1 -1
  43. package/lib/handlers/screenshot.js +3 -12
  44. package/lib/handlers/screenshot.js.map +1 -1
  45. package/lib/index.d.ts +32 -109
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/index.js +54 -103
  48. package/lib/index.js.map +1 -1
  49. package/lib/init.d.ts +3 -150
  50. package/lib/init.d.ts.map +1 -1
  51. package/lib/init.js +100 -261
  52. package/lib/init.js.map +1 -1
  53. package/lib/long-task-monitor.d.ts +1 -7
  54. package/lib/long-task-monitor.d.ts.map +1 -1
  55. package/lib/long-task-monitor.js +37 -58
  56. package/lib/long-task-monitor.js.map +1 -1
  57. package/lib/mask.d.ts +5 -13
  58. package/lib/mask.d.ts.map +1 -1
  59. package/lib/mask.js +29 -30
  60. package/lib/mask.js.map +1 -1
  61. package/lib/mobile-vitals.d.ts +3 -31
  62. package/lib/mobile-vitals.d.ts.map +1 -1
  63. package/lib/mobile-vitals.js +22 -61
  64. package/lib/mobile-vitals.js.map +1 -1
  65. package/lib/native-pending.d.ts +3 -0
  66. package/lib/native-pending.d.ts.map +1 -0
  67. package/lib/native-pending.js +64 -0
  68. package/lib/native-pending.js.map +1 -0
  69. package/lib/native.d.ts +4 -11
  70. package/lib/native.d.ts.map +1 -1
  71. package/lib/native.js +23 -0
  72. package/lib/native.js.map +1 -1
  73. package/lib/navigation.d.ts +3 -17
  74. package/lib/navigation.d.ts.map +1 -1
  75. package/lib/navigation.js +32 -133
  76. package/lib/navigation.js.map +1 -1
  77. package/lib/netinfo.d.ts +1 -1
  78. package/lib/netinfo.d.ts.map +1 -1
  79. package/lib/netinfo.js +2 -2
  80. package/lib/netinfo.js.map +1 -1
  81. package/lib/push.d.ts.map +1 -1
  82. package/lib/push.js +2 -4
  83. package/lib/push.js.map +1 -1
  84. package/lib/rage-tap.d.ts.map +1 -1
  85. package/lib/rage-tap.js +56 -23
  86. package/lib/rage-tap.js.map +1 -1
  87. package/lib/replay-screens.d.ts +15 -0
  88. package/lib/replay-screens.d.ts.map +1 -0
  89. package/lib/replay-screens.js +87 -0
  90. package/lib/replay-screens.js.map +1 -0
  91. package/lib/replay.d.ts.map +1 -1
  92. package/lib/replay.js +3 -24
  93. package/lib/replay.js.map +1 -1
  94. package/lib/responsiveness-detector.d.ts +18 -0
  95. package/lib/responsiveness-detector.d.ts.map +1 -0
  96. package/lib/responsiveness-detector.js +55 -0
  97. package/lib/responsiveness-detector.js.map +1 -0
  98. package/lib/scope.d.ts +7 -0
  99. package/lib/scope.d.ts.map +1 -0
  100. package/lib/scope.js +41 -0
  101. package/lib/scope.js.map +1 -0
  102. package/lib/transport.d.ts +19 -92
  103. package/lib/transport.d.ts.map +1 -1
  104. package/lib/transport.js +75 -288
  105. package/lib/transport.js.map +1 -1
  106. package/lib/verbs.d.ts +16 -0
  107. package/lib/verbs.d.ts.map +1 -0
  108. package/lib/verbs.js +153 -0
  109. package/lib/verbs.js.map +1 -0
  110. package/package.json +4 -3
  111. package/src/__tests__/iron-rule.test.ts +128 -0
  112. package/src/__tests__/replay-screens.test.ts +63 -0
  113. package/src/__tests__/responsiveness-detector.test.ts +74 -0
  114. package/src/__tests__/screenshot.test.ts +0 -2
  115. package/src/__tests__/transport.test.ts +66 -213
  116. package/src/__tests__/verbs.test.ts +91 -0
  117. package/src/config.ts +20 -47
  118. package/src/device.ts +36 -0
  119. package/src/emit-hooks.ts +30 -0
  120. package/src/error-boundary.tsx +1 -1
  121. package/src/handlers/dev-symbolicate.ts +2 -1
  122. package/src/handlers/global.ts +1 -1
  123. package/src/handlers/lifecycle.ts +3 -3
  124. package/src/handlers/network.ts +95 -211
  125. package/src/handlers/promise.ts +1 -1
  126. package/src/handlers/screenshot.ts +3 -11
  127. package/src/index.ts +84 -215
  128. package/src/init.ts +115 -449
  129. package/src/long-task-monitor.ts +42 -64
  130. package/src/mask.ts +29 -31
  131. package/src/mobile-vitals.ts +22 -82
  132. package/src/native-pending.ts +92 -0
  133. package/src/native.ts +33 -0
  134. package/src/navigation.ts +34 -142
  135. package/src/netinfo.ts +3 -3
  136. package/src/push.ts +2 -5
  137. package/src/rage-tap.tsx +72 -25
  138. package/src/replay-screens.ts +95 -0
  139. package/src/replay.ts +3 -20
  140. package/src/responsiveness-detector.ts +66 -0
  141. package/src/scope.ts +50 -0
  142. package/src/transport.ts +84 -371
  143. package/src/verbs.ts +198 -0
  144. package/lib/breadcrumbs.d.ts +0 -18
  145. package/lib/breadcrumbs.d.ts.map +0 -1
  146. package/lib/breadcrumbs.js +0 -42
  147. package/lib/breadcrumbs.js.map +0 -1
  148. package/lib/bundle-info.d.ts +0 -12
  149. package/lib/bundle-info.d.ts.map +0 -1
  150. package/lib/bundle-info.js +0 -73
  151. package/lib/bundle-info.js.map +0 -1
  152. package/lib/capture.d.ts +0 -140
  153. package/lib/capture.d.ts.map +0 -1
  154. package/lib/capture.js +0 -511
  155. package/lib/capture.js.map +0 -1
  156. package/lib/compat/sentry.d.ts +0 -126
  157. package/lib/compat/sentry.d.ts.map +0 -1
  158. package/lib/compat/sentry.js +0 -332
  159. package/lib/compat/sentry.js.map +0 -1
  160. package/lib/control-channel.d.ts +0 -6
  161. package/lib/control-channel.d.ts.map +0 -1
  162. package/lib/control-channel.js +0 -96
  163. package/lib/control-channel.js.map +0 -1
  164. package/lib/expo-compat.d.ts +0 -270
  165. package/lib/expo-compat.d.ts.map +0 -1
  166. package/lib/expo-compat.js +0 -500
  167. package/lib/expo-compat.js.map +0 -1
  168. package/lib/feedback-widget.d.ts +0 -35
  169. package/lib/feedback-widget.d.ts.map +0 -1
  170. package/lib/feedback-widget.js +0 -193
  171. package/lib/feedback-widget.js.map +0 -1
  172. package/lib/feedback.d.ts +0 -2
  173. package/lib/feedback.d.ts.map +0 -1
  174. package/lib/feedback.js +0 -17
  175. package/lib/feedback.js.map +0 -1
  176. package/lib/heartbeat.d.ts +0 -9
  177. package/lib/heartbeat.d.ts.map +0 -1
  178. package/lib/heartbeat.js +0 -138
  179. package/lib/heartbeat.js.map +0 -1
  180. package/lib/index.js.bak +0 -64
  181. package/lib/install-id.d.ts +0 -17
  182. package/lib/install-id.d.ts.map +0 -1
  183. package/lib/install-id.js +0 -125
  184. package/lib/install-id.js.map +0 -1
  185. package/lib/lifecycle.d.ts +0 -28
  186. package/lib/lifecycle.d.ts.map +0 -1
  187. package/lib/lifecycle.js +0 -73
  188. package/lib/lifecycle.js.map +0 -1
  189. package/lib/measure.d.ts +0 -4
  190. package/lib/measure.d.ts.map +0 -1
  191. package/lib/measure.js +0 -25
  192. package/lib/measure.js.map +0 -1
  193. package/lib/metrics.d.ts +0 -24
  194. package/lib/metrics.d.ts.map +0 -1
  195. package/lib/metrics.js +0 -81
  196. package/lib/metrics.js.map +0 -1
  197. package/lib/pre-crash-sentinel.d.ts +0 -12
  198. package/lib/pre-crash-sentinel.d.ts.map +0 -1
  199. package/lib/pre-crash-sentinel.js +0 -116
  200. package/lib/pre-crash-sentinel.js.map +0 -1
  201. package/lib/report-security.d.ts +0 -40
  202. package/lib/report-security.d.ts.map +0 -1
  203. package/lib/report-security.js +0 -159
  204. package/lib/report-security.js.map +0 -1
  205. package/lib/runtime-metrics-fps.d.ts +0 -19
  206. package/lib/runtime-metrics-fps.d.ts.map +0 -1
  207. package/lib/runtime-metrics-fps.js +0 -128
  208. package/lib/runtime-metrics-fps.js.map +0 -1
  209. package/lib/runtime-metrics-heap.d.ts +0 -13
  210. package/lib/runtime-metrics-heap.d.ts.map +0 -1
  211. package/lib/runtime-metrics-heap.js +0 -67
  212. package/lib/runtime-metrics-heap.js.map +0 -1
  213. package/lib/runtime-metrics-network.d.ts +0 -23
  214. package/lib/runtime-metrics-network.d.ts.map +0 -1
  215. package/lib/runtime-metrics-network.js +0 -99
  216. package/lib/runtime-metrics-network.js.map +0 -1
  217. package/lib/runtime-metrics.d.ts +0 -43
  218. package/lib/runtime-metrics.d.ts.map +0 -1
  219. package/lib/runtime-metrics.js +0 -115
  220. package/lib/runtime-metrics.js.map +0 -1
  221. package/lib/sample-profiler.d.ts +0 -21
  222. package/lib/sample-profiler.d.ts.map +0 -1
  223. package/lib/sample-profiler.js +0 -152
  224. package/lib/sample-profiler.js.map +0 -1
  225. package/lib/sentinel-context.d.ts +0 -4
  226. package/lib/sentinel-context.d.ts.map +0 -1
  227. package/lib/sentinel-context.js +0 -52
  228. package/lib/sentinel-context.js.map +0 -1
  229. package/lib/session-tracker.d.ts +0 -6
  230. package/lib/session-tracker.d.ts.map +0 -1
  231. package/lib/session-tracker.js +0 -50
  232. package/lib/session-tracker.js.map +0 -1
  233. package/lib/stack.d.ts +0 -3
  234. package/lib/stack.d.ts.map +0 -1
  235. package/lib/stack.js +0 -7
  236. package/lib/stack.js.map +0 -1
  237. package/lib/state-snapshots.d.ts +0 -33
  238. package/lib/state-snapshots.d.ts.map +0 -1
  239. package/lib/state-snapshots.js +0 -109
  240. package/lib/state-snapshots.js.map +0 -1
  241. package/lib/track.d.ts +0 -34
  242. package/lib/track.d.ts.map +0 -1
  243. package/lib/track.js +0 -107
  244. package/lib/track.js.map +0 -1
  245. package/lib/trail.d.ts +0 -12
  246. package/lib/trail.d.ts.map +0 -1
  247. package/lib/trail.js +0 -28
  248. package/lib/trail.js.map +0 -1
  249. package/lib/trust-score.d.ts +0 -20
  250. package/lib/trust-score.d.ts.map +0 -1
  251. package/lib/trust-score.js +0 -151
  252. package/lib/trust-score.js.map +0 -1
  253. package/lib/types.d.ts +0 -2
  254. package/lib/types.d.ts.map +0 -1
  255. package/lib/types.js +0 -4
  256. package/lib/types.js.map +0 -1
  257. package/lib/uuid.d.ts +0 -2
  258. package/lib/uuid.d.ts.map +0 -1
  259. package/lib/uuid.js +0 -3
  260. package/lib/uuid.js.map +0 -1
  261. package/src/__tests__/before-send.test.ts +0 -72
  262. package/src/__tests__/breadcrumbs.test.ts +0 -44
  263. package/src/__tests__/capture-replay.test.ts +0 -150
  264. package/src/__tests__/compat-sentry.test.ts +0 -121
  265. package/src/__tests__/control-channel.test.ts +0 -111
  266. package/src/__tests__/install-id.test.ts +0 -60
  267. package/src/__tests__/long-task-monitor.test.ts +0 -59
  268. package/src/__tests__/measure.test.ts +0 -45
  269. package/src/__tests__/navigation.test.ts +0 -175
  270. package/src/__tests__/push.test.ts +0 -238
  271. package/src/__tests__/report-security.test.ts +0 -106
  272. package/src/__tests__/runtime-metrics-instruments.test.ts +0 -109
  273. package/src/__tests__/runtime-metrics-network.test.ts +0 -78
  274. package/src/__tests__/sample-profiler.test.ts +0 -59
  275. package/src/__tests__/stack.test.ts +0 -45
  276. package/src/__tests__/state-snapshots.test.ts +0 -119
  277. package/src/__tests__/tracing.test.ts +0 -201
  278. package/src/__tests__/track.test.ts +0 -91
  279. package/src/__tests__/uuid.test.ts +0 -41
  280. package/src/breadcrumbs.ts +0 -57
  281. package/src/bundle-info.ts +0 -95
  282. package/src/capture.ts +0 -660
  283. package/src/compat/sentry.ts +0 -490
  284. package/src/control-channel.ts +0 -98
  285. package/src/expo-compat.ts +0 -698
  286. package/src/feedback-widget.tsx +0 -316
  287. package/src/feedback.ts +0 -21
  288. package/src/heartbeat.ts +0 -157
  289. package/src/install-id.ts +0 -146
  290. package/src/lifecycle.ts +0 -76
  291. package/src/measure.ts +0 -28
  292. package/src/metrics.ts +0 -94
  293. package/src/pre-crash-sentinel.ts +0 -140
  294. package/src/report-security.ts +0 -165
  295. package/src/runtime-metrics-fps.ts +0 -137
  296. package/src/runtime-metrics-heap.ts +0 -78
  297. package/src/runtime-metrics-network.ts +0 -103
  298. package/src/runtime-metrics.ts +0 -122
  299. package/src/sample-profiler.ts +0 -169
  300. package/src/sentinel-context.ts +0 -60
  301. package/src/session-tracker.ts +0 -53
  302. package/src/stack.ts +0 -10
  303. package/src/state-snapshots.ts +0 -133
  304. package/src/track.ts +0 -123
  305. package/src/trail.ts +0 -32
  306. package/src/trust-score.ts +0 -176
  307. package/src/types.ts +0 -21
  308. package/src/uuid.ts +0 -2
@@ -1,106 +0,0 @@
1
- // v1.1 chunk S2 — reportSecurity + reportPinMismatch round-trip.
2
-
3
- import {
4
- afterEach,
5
- beforeEach,
6
- describe,
7
- expect,
8
- it,
9
- mock,
10
- } from 'bun:test';
11
-
12
- import { __resetForTests as resetConfig, setConfig } from '../config';
13
- import { __resetInstallIdForTests, __setInstallIdForTests } from '../install-id';
14
- import { setUser } from '../capture';
15
- import { reportPinMismatch, reportSecurity } from '../report-security';
16
-
17
- const originalFetch = globalThis.fetch;
18
-
19
- describe('reportSecurity', () => {
20
- beforeEach(() => {
21
- resetConfig();
22
- __resetInstallIdForTests();
23
- setConfig({
24
- token: 'st_pk_test',
25
- release: 'app@1.0.0+1',
26
- environment: 'test',
27
- ingestUrl: 'http://localhost:8080',
28
- enabled: true,
29
- });
30
- setUser({ id: 'u_demo' });
31
- __setInstallIdForTests('install-abc');
32
- });
33
-
34
- afterEach(() => {
35
- globalThis.fetch = originalFetch;
36
- setUser(null);
37
- });
38
-
39
- it('posts the report envelope and returns the server id', async () => {
40
- const calls: { body: string; url: string }[] = [];
41
- globalThis.fetch = mock(async (url: unknown, init: unknown) => {
42
- calls.push({
43
- body: String((init as { body?: unknown })?.body ?? ''),
44
- url: String(url),
45
- });
46
- return new Response(JSON.stringify({ id: 'sec-1' }), { status: 202 });
47
- }) as unknown as typeof fetch;
48
-
49
- const id = await reportSecurity('root.detected', { detector: 'rootbeer' });
50
- expect(id).toBe('sec-1');
51
- expect(calls.length).toBe(1);
52
- expect(calls[0].url).toBe('http://localhost:8080/v1/security:report');
53
- const parsed = JSON.parse(calls[0].body) as {
54
- data: Record<string, unknown>;
55
- installId: string;
56
- kind: string;
57
- release: string;
58
- userId: string;
59
- };
60
- expect(parsed.kind).toBe('root.detected');
61
- expect(parsed.data.detector).toBe('rootbeer');
62
- expect(parsed.installId).toBe('install-abc');
63
- expect(parsed.userId).toBe('u_demo');
64
- expect(parsed.release).toBe('app@1.0.0+1');
65
- });
66
-
67
- it('reportPinMismatch flattens to pin.mismatch with serverName', async () => {
68
- const calls: { body: string }[] = [];
69
- globalThis.fetch = mock(async (_url: unknown, init: unknown) => {
70
- calls.push({ body: String((init as { body?: unknown })?.body ?? '') });
71
- return new Response(JSON.stringify({ id: 'sec-2' }), { status: 202 });
72
- }) as unknown as typeof fetch;
73
-
74
- const id = await reportPinMismatch({
75
- expected: 'sha256/AAAA',
76
- observed: 'sha256/BBBB',
77
- serverName: 'api.example.com',
78
- });
79
- expect(id).toBe('sec-2');
80
- const parsed = JSON.parse(calls[0].body) as {
81
- data: { expected: string; observed: string };
82
- kind: string;
83
- serverName: string;
84
- };
85
- expect(parsed.kind).toBe('pin.mismatch');
86
- expect(parsed.serverName).toBe('api.example.com');
87
- expect(parsed.data.expected).toBe('sha256/AAAA');
88
- expect(parsed.data.observed).toBe('sha256/BBBB');
89
- });
90
-
91
- it('returns null on transport failure rather than throwing', async () => {
92
- globalThis.fetch = mock(async () => {
93
- throw new Error('offline');
94
- }) as unknown as typeof fetch;
95
-
96
- const id = await reportSecurity('arbitrary.kind');
97
- expect(id).toBe(null);
98
- });
99
-
100
- it('drops bad inputs silently', async () => {
101
- const id1 = await reportSecurity('');
102
- const id2 = await reportPinMismatch({ expected: 'a', observed: 'b', serverName: '' });
103
- expect(id1).toBe(null);
104
- expect(id2).toBe(null);
105
- });
106
- });
@@ -1,109 +0,0 @@
1
- import { afterEach, beforeEach, expect, test } from 'bun:test';
2
-
3
- import {
4
- __peekRuntimeMetricsSize,
5
- __resetRuntimeMetricsForTests,
6
- drainRuntimeMetricsForFlush,
7
- } from '@goliapkg/sentori-core';
8
-
9
- import {
10
- __forceEmitFpsForTests,
11
- __pushSampleForTests,
12
- __resetFpsInstrumentForTests,
13
- startFpsInstrument,
14
- stopFpsInstrument,
15
- } from '../runtime-metrics-fps';
16
- import {
17
- __forceHeapTickForTests,
18
- __peekHeapInstrumentState,
19
- startHeapInstrument,
20
- stopHeapInstrument,
21
- } from '../runtime-metrics-heap';
22
-
23
- beforeEach(() => {
24
- __resetRuntimeMetricsForTests();
25
- __resetFpsInstrumentForTests();
26
- });
27
-
28
- afterEach(() => {
29
- stopFpsInstrument();
30
- stopHeapInstrument();
31
- });
32
-
33
- test('fps: pushes p50 + p95 emits into the runtime metrics ring', () => {
34
- // 300 samples at a steady 16.67 ms cadence ≈ 60 fps.
35
- for (let i = 0; i < 300; i++) {
36
- __pushSampleForTests(16.67);
37
- }
38
- __forceEmitFpsForTests();
39
-
40
- const drained = drainRuntimeMetricsForFlush();
41
- expect(drained.length).toBe(2);
42
- const names = drained.map((m) => m.name).sort();
43
- expect(names).toEqual(['runtime.fps.p50', 'runtime.fps.p95']);
44
- // Both should land near 60 fps.
45
- const p50 = drained.find((m) => m.name === 'runtime.fps.p50')!;
46
- expect(p50.value).toBeGreaterThanOrEqual(58);
47
- expect(p50.value).toBeLessThanOrEqual(62);
48
- });
49
-
50
- test('fps: mixed-cadence run separates p50 from p95 sensibly', () => {
51
- // 270 fast frames (60 fps) + 30 stutters (30 fps). p95 should
52
- // land at the stutter zone.
53
- for (let i = 0; i < 270; i++) __pushSampleForTests(16.67);
54
- for (let i = 0; i < 30; i++) __pushSampleForTests(33.33);
55
- __forceEmitFpsForTests();
56
-
57
- const drained = drainRuntimeMetricsForFlush();
58
- const p50 = drained.find((m) => m.name === 'runtime.fps.p50')!.value;
59
- const p95 = drained.find((m) => m.name === 'runtime.fps.p95')!.value;
60
- // p50 stays at the smooth-frame zone.
61
- expect(p50).toBeGreaterThanOrEqual(58);
62
- // p95 (worst-95th of Δt → slowest 5%) should drop into the
63
- // 30 fps zone.
64
- expect(p95).toBeLessThanOrEqual(35);
65
- });
66
-
67
- test('fps: start is idempotent — second call is a no-op', () => {
68
- startFpsInstrument();
69
- // Calling start twice mustn't spin up a second rAF loop. We
70
- // don't directly observe that here (no two-loop probe), but
71
- // we assert start doesn't throw + state stays running once.
72
- expect(() => startFpsInstrument()).not.toThrow();
73
- stopFpsInstrument();
74
- });
75
-
76
- test('heap: start is a no-op on hosts without performance.memory', () => {
77
- // The default test runtime (bun) doesn't expose
78
- // performance.memory, so start should silently skip and
79
- // never schedule a timer.
80
- startHeapInstrument();
81
- expect(__peekHeapInstrumentState().running).toBe(false);
82
- });
83
-
84
- test('heap: force tick is a no-op on hosts without performance.memory', () => {
85
- __forceHeapTickForTests();
86
- // No metric should land in the ring.
87
- expect(__peekRuntimeMetricsSize()).toBe(0);
88
- });
89
-
90
- test('heap: emits when performance.memory is shimmed', () => {
91
- // Shim performance.memory so the read path exercises.
92
- const g = globalThis as {
93
- performance?: { memory?: { usedJSHeapSize?: number; totalJSHeapSize?: number } };
94
- };
95
- const before = g.performance;
96
- g.performance = {
97
- ...(before ?? {}),
98
- memory: { usedJSHeapSize: 12_345_678, totalJSHeapSize: 23_456_789 },
99
- };
100
- try {
101
- __forceHeapTickForTests();
102
- const drained = drainRuntimeMetricsForFlush();
103
- const names = drained.map((m) => m.name).sort();
104
- expect(names).toEqual(['runtime.heap.total_bytes', 'runtime.heap.used_bytes']);
105
- expect(drained.find((m) => m.name === 'runtime.heap.used_bytes')!.value).toBe(12_345_678);
106
- } finally {
107
- g.performance = before;
108
- }
109
- });
@@ -1,78 +0,0 @@
1
- import { afterEach, beforeEach, expect, test } from 'bun:test';
2
-
3
- import {
4
- __resetRuntimeMetricsForTests,
5
- drainRuntimeMetricsForFlush,
6
- } from '@goliapkg/sentori-core';
7
-
8
- import {
9
- __forceNetworkEmitForTests,
10
- __peekNetworkCountersForTests,
11
- __resetNetworkBytesForTests,
12
- estimateRequestBytes,
13
- estimateResponseBytes,
14
- recordNetworkBytes,
15
- } from '../runtime-metrics-network';
16
-
17
- beforeEach(() => {
18
- __resetRuntimeMetricsForTests();
19
- __resetNetworkBytesForTests();
20
- });
21
-
22
- afterEach(() => __resetNetworkBytesForTests());
23
-
24
- test('recordNetworkBytes: cumulates sent + received separately', () => {
25
- recordNetworkBytes(100, 200);
26
- recordNetworkBytes(50, 0);
27
- recordNetworkBytes(0, 25);
28
- const { sent, received } = __peekNetworkCountersForTests();
29
- expect(sent).toBe(150);
30
- expect(received).toBe(225);
31
- });
32
-
33
- test('force emit: flushes both counters into the ring + resets', () => {
34
- recordNetworkBytes(1234, 5678);
35
- __forceNetworkEmitForTests();
36
-
37
- const drained = drainRuntimeMetricsForFlush();
38
- expect(drained.length).toBe(2);
39
- const names = drained.map((m) => m.name).sort();
40
- expect(names).toEqual(['runtime.network.bytes_received', 'runtime.network.bytes_sent']);
41
- expect(drained.find((m) => m.name === 'runtime.network.bytes_sent')!.value).toBe(1234);
42
- expect(drained.find((m) => m.name === 'runtime.network.bytes_received')!.value).toBe(5678);
43
-
44
- const { sent, received } = __peekNetworkCountersForTests();
45
- expect(sent).toBe(0);
46
- expect(received).toBe(0);
47
- });
48
-
49
- test('force emit: skips zero counters (no empty metric emit)', () => {
50
- __forceNetworkEmitForTests();
51
- expect(drainRuntimeMetricsForFlush().length).toBe(0);
52
- });
53
-
54
- test('estimateRequestBytes: handles string body', () => {
55
- expect(estimateRequestBytes({ body: 'hello world' })).toBe(11);
56
- expect(estimateRequestBytes()).toBe(0);
57
- expect(estimateRequestBytes({})).toBe(0);
58
- });
59
-
60
- test('estimateRequestBytes: reads byteLength from ArrayBuffer-like body', () => {
61
- const buf = new Uint8Array([1, 2, 3, 4, 5]);
62
- expect(estimateRequestBytes({ body: buf })).toBe(5);
63
- });
64
-
65
- test('estimateResponseBytes: parses content-length header', () => {
66
- const h = new Headers({ 'content-length': '1024' });
67
- expect(estimateResponseBytes(h)).toBe(1024);
68
- });
69
-
70
- test('estimateResponseBytes: returns 0 when header missing (chunked / stripped)', () => {
71
- expect(estimateResponseBytes(new Headers())).toBe(0);
72
- expect(estimateResponseBytes(null)).toBe(0);
73
- });
74
-
75
- test('estimateResponseBytes: returns 0 on unparseable header (undercount-safe)', () => {
76
- const h = new Headers({ 'content-length': 'nope' });
77
- expect(estimateResponseBytes(h)).toBe(0);
78
- });
@@ -1,59 +0,0 @@
1
- // v1.1 #4 升级 — JS sample profiler unit tests.
2
-
3
- import { afterEach, describe, expect, test } from 'bun:test';
4
-
5
- import { clearSpans, drainSpans } from '@goliapkg/sentori-core';
6
-
7
- import {
8
- __resetSampleProfilerForTests,
9
- startSampleProfiler,
10
- stopSampleProfiler,
11
- } from '../sample-profiler';
12
-
13
- afterEach(() => {
14
- __resetSampleProfilerForTests();
15
- clearSpans();
16
- });
17
-
18
- describe('sample-profiler', () => {
19
- test('start with enabled=false is a no-op', () => {
20
- startSampleProfiler({ enabled: false });
21
- stopSampleProfiler();
22
- expect(drainSpans().length).toBe(0);
23
- });
24
-
25
- test('double start is idempotent (second call ignored)', () => {
26
- startSampleProfiler({ enabled: true, sampleMs: 30, flushMs: 5_000 });
27
- // A second start while the timer is live must not spin a second
28
- // interval — would double-count every sample.
29
- startSampleProfiler({ enabled: true, sampleMs: 30, flushMs: 5_000 });
30
- stopSampleProfiler();
31
- });
32
-
33
- test('stop clears state cleanly', async () => {
34
- startSampleProfiler({ enabled: true, sampleMs: 30, flushMs: 5_000 });
35
- // Let a few sampleTicks happen.
36
- await new Promise((r) => setTimeout(r, 120));
37
- stopSampleProfiler();
38
- // After stop, no further span should be emitted even past flushMs.
39
- await new Promise((r) => setTimeout(r, 80));
40
- // (Bun timers aren't faked here, so we don't wait the full flush
41
- // window; we just confirm stop returns cleanly and __reset works.)
42
- __resetSampleProfilerForTests();
43
- expect(true).toBe(true);
44
- });
45
-
46
- test('sample interval floor — sampleMs < 20 clamped up to 20', () => {
47
- // Internal: profile.duration_ms tag uses the *effective* window
48
- // length so we can't directly observe the chosen sampleMs, but we
49
- // *can* assert start doesn't throw with an absurd request, since
50
- // the clamp protects against pathological CPU burn.
51
- startSampleProfiler({ enabled: true, sampleMs: 1, flushMs: 5_000 });
52
- stopSampleProfiler();
53
- });
54
-
55
- test('flush interval floor — flushMs < 5000 clamped up to 5000', () => {
56
- startSampleProfiler({ enabled: true, sampleMs: 30, flushMs: 100 });
57
- stopSampleProfiler();
58
- });
59
- });
@@ -1,45 +0,0 @@
1
- import { describe, it, expect } from 'bun:test';
2
-
3
- import { parseStack } from '../stack';
4
-
5
- describe('parseStack', () => {
6
- it('returns [] for undefined', () => {
7
- expect(parseStack(undefined)).toEqual([]);
8
- });
9
-
10
- it('returns [] for empty string', () => {
11
- expect(parseStack('')).toEqual([]);
12
- });
13
-
14
- it('parses V8/Hermes-style frames', () => {
15
- const stack = `TypeError: Cannot read property 'foo' of undefined
16
- at handleSubmit (src/screens/Checkout.tsx:42:10)
17
- at onPress (src/components/Button.tsx:15:5)`;
18
- const frames = parseStack(stack);
19
- expect(frames).toHaveLength(2);
20
- // Phase 21: core's parseStack also populates absolutePath, so we
21
- // match the subset of fields rather than full equality.
22
- expect(frames[0]).toMatchObject({
23
- column: 10,
24
- file: 'src/screens/Checkout.tsx',
25
- function: 'handleSubmit',
26
- inApp: true,
27
- line: 42,
28
- });
29
- expect(frames[1]?.function).toBe('onPress');
30
- });
31
-
32
- it('marks node_modules frames as not inApp', () => {
33
- const stack = `Error
34
- at vendorFn (node_modules/react-native/Libraries/Foo.js:1:1)`;
35
- const frames = parseStack(stack);
36
- expect(frames[0]?.inApp).toBe(false);
37
- });
38
-
39
- it('marks http(s) urls as not inApp', () => {
40
- const stack = `Error
41
- at someFn (https://cdn.example.com/bundle.js:1:1)`;
42
- const frames = parseStack(stack);
43
- expect(frames[0]?.inApp).toBe(false);
44
- });
45
- });
@@ -1,119 +0,0 @@
1
- import { afterEach, describe, expect, test } from 'bun:test';
2
-
3
- import {
4
- __resetStateSnapshotsForTests,
5
- bindState,
6
- getStateSnapshots,
7
- recordState,
8
- shallowDiff,
9
- unbindState,
10
- } from '../state-snapshots';
11
-
12
- afterEach(() => {
13
- __resetStateSnapshotsForTests();
14
- });
15
-
16
- describe('shallowDiff', () => {
17
- test('emits only changed top-level keys', () => {
18
- const a = { foo: 1, bar: 2 };
19
- const b = { bar: 2, foo: 3 };
20
- expect(shallowDiff(a, b)).toEqual({ foo: 3 });
21
- });
22
-
23
- test('emits new keys from next', () => {
24
- expect(shallowDiff({ a: 1 }, { a: 1, b: 2 })).toEqual({ b: 2 });
25
- });
26
-
27
- test('treats deletion as undefined', () => {
28
- expect(shallowDiff({ a: 1, b: 2 }, { a: 1 })).toEqual({ b: undefined });
29
- });
30
-
31
- test('no change returns empty object', () => {
32
- expect(shallowDiff({ a: 1 }, { a: 1 })).toEqual({});
33
- });
34
-
35
- test('non-object prev → full clone of next', () => {
36
- expect(shallowDiff(undefined, { a: 1 })).toEqual({ a: 1 });
37
- });
38
- });
39
-
40
- describe('recordState', () => {
41
- test('appends snapshot with default source=manual', () => {
42
- recordState({ cart: 3 });
43
- const snaps = getStateSnapshots();
44
- expect(snaps.length).toBe(1);
45
- expect(snaps[0]!.source).toBe('manual');
46
- expect(snaps[0]!.diff).toEqual({ cart: 3 });
47
- });
48
-
49
- test('caps at 50 snapshots (ring buffer)', () => {
50
- for (let i = 0; i < 60; i++) recordState({ n: i });
51
- expect(getStateSnapshots().length).toBe(50);
52
- // oldest 10 dropped; first kept entry has n=10
53
- expect(getStateSnapshots()[0]!.diff).toEqual({ n: 10 });
54
- });
55
- });
56
-
57
- describe('bindState', () => {
58
- test('subscribes to a redux-like store and records diffs', () => {
59
- let state: { count: number } = { count: 0 };
60
- const listeners: (() => void)[] = [];
61
- const store = {
62
- getState: () => state,
63
- subscribe: (cb: () => void) => {
64
- listeners.push(cb);
65
- return () => {
66
- const i = listeners.indexOf(cb);
67
- if (i >= 0) listeners.splice(i, 1);
68
- };
69
- },
70
- };
71
- bindState({ redux: store });
72
- state = { count: 1 };
73
- listeners.forEach((l) => l());
74
- state = { count: 2 };
75
- listeners.forEach((l) => l());
76
-
77
- const snaps = getStateSnapshots();
78
- expect(snaps.length).toBe(2);
79
- expect(snaps[0]!.diff).toEqual({ count: 1 });
80
- expect(snaps[1]!.diff).toEqual({ count: 2 });
81
- expect(snaps[0]!.source).toBe('redux');
82
- });
83
-
84
- test('unbindState stops recording', () => {
85
- let state: { v: number } = { v: 0 };
86
- const listeners: (() => void)[] = [];
87
- const store = {
88
- getState: () => state,
89
- subscribe: (cb: () => void) => {
90
- listeners.push(cb);
91
- return () => listeners.splice(listeners.indexOf(cb), 1);
92
- },
93
- };
94
- bindState({ redux: store });
95
- state = { v: 1 };
96
- listeners.forEach((l) => l());
97
- unbindState();
98
- state = { v: 2 };
99
- listeners.forEach((l) => l());
100
-
101
- expect(getStateSnapshots().length).toBe(1);
102
- });
103
-
104
- test('custom stores produce labeled snapshots', () => {
105
- let auth = { user: null };
106
- const listeners: (() => void)[] = [];
107
- const store = {
108
- getState: () => auth,
109
- subscribe: (cb: () => void) => {
110
- listeners.push(cb);
111
- return () => listeners.splice(listeners.indexOf(cb), 1);
112
- },
113
- };
114
- bindState({ custom: { auth: store } });
115
- auth = { user: 'alice' as never };
116
- listeners.forEach((l) => l());
117
- expect(getStateSnapshots()[0]!.source).toBe('auth');
118
- });
119
- });