@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.
- package/MIGRATION.md +131 -0
- package/README.md +40 -30
- package/android/build.gradle +21 -0
- package/android/src/main/java/com/sentori/SentoriCrashHandler.kt +29 -0
- package/android/src/main/java/com/sentori/SentoriModule.kt +8 -0
- package/android/src/main/java/com/sentori/SentoriScreenshotCapture.kt +36 -4
- package/android/src/test/java/com/sentori/SentoriCrashHandlerTest.kt +7 -2
- package/android/src/test/resources/robolectric.properties +10 -0
- package/ios/SentoriCrashHandler.swift +48 -4
- package/ios/SentoriModule.swift +12 -0
- package/ios/SentoriScreenshotCapture.swift +34 -3
- package/lib/config.d.ts +17 -47
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +3 -0
- package/lib/config.js.map +1 -1
- package/lib/device.d.ts +5 -0
- package/lib/device.d.ts.map +1 -0
- package/lib/device.js +29 -0
- package/lib/device.js.map +1 -0
- package/lib/emit-hooks.d.ts +7 -0
- package/lib/emit-hooks.d.ts.map +1 -0
- package/lib/emit-hooks.js +24 -0
- package/lib/emit-hooks.js.map +1 -0
- package/lib/error-boundary.d.ts.map +1 -1
- package/lib/error-boundary.js +1 -1
- package/lib/error-boundary.js.map +1 -1
- package/lib/feature-flags.d.ts.map +1 -1
- package/lib/handlers/dev-symbolicate.d.ts +1 -1
- package/lib/handlers/dev-symbolicate.d.ts.map +1 -1
- package/lib/handlers/dev-symbolicate.js +2 -0
- package/lib/handlers/dev-symbolicate.js.map +1 -1
- package/lib/handlers/global.js +1 -1
- package/lib/handlers/global.js.map +1 -1
- package/lib/handlers/lifecycle.js +3 -3
- package/lib/handlers/lifecycle.js.map +1 -1
- package/lib/handlers/network.d.ts +2 -2
- package/lib/handlers/network.d.ts.map +1 -1
- package/lib/handlers/network.js +69 -187
- package/lib/handlers/network.js.map +1 -1
- package/lib/handlers/promise.js +1 -1
- package/lib/handlers/promise.js.map +1 -1
- package/lib/handlers/screenshot.d.ts.map +1 -1
- package/lib/handlers/screenshot.js +3 -12
- package/lib/handlers/screenshot.js.map +1 -1
- package/lib/index.d.ts +32 -109
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +54 -103
- package/lib/index.js.map +1 -1
- package/lib/init.d.ts +3 -150
- package/lib/init.d.ts.map +1 -1
- package/lib/init.js +100 -261
- package/lib/init.js.map +1 -1
- package/lib/long-task-monitor.d.ts +1 -7
- package/lib/long-task-monitor.d.ts.map +1 -1
- package/lib/long-task-monitor.js +37 -58
- package/lib/long-task-monitor.js.map +1 -1
- package/lib/mask.d.ts +5 -13
- package/lib/mask.d.ts.map +1 -1
- package/lib/mask.js +29 -30
- package/lib/mask.js.map +1 -1
- package/lib/mobile-vitals.d.ts +3 -31
- package/lib/mobile-vitals.d.ts.map +1 -1
- package/lib/mobile-vitals.js +22 -61
- package/lib/mobile-vitals.js.map +1 -1
- package/lib/native-pending.d.ts +3 -0
- package/lib/native-pending.d.ts.map +1 -0
- package/lib/native-pending.js +64 -0
- package/lib/native-pending.js.map +1 -0
- package/lib/native.d.ts +4 -11
- package/lib/native.d.ts.map +1 -1
- package/lib/native.js +23 -0
- package/lib/native.js.map +1 -1
- package/lib/navigation.d.ts +3 -17
- package/lib/navigation.d.ts.map +1 -1
- package/lib/navigation.js +32 -133
- package/lib/navigation.js.map +1 -1
- package/lib/netinfo.d.ts +1 -1
- package/lib/netinfo.d.ts.map +1 -1
- package/lib/netinfo.js +2 -2
- package/lib/netinfo.js.map +1 -1
- package/lib/push.d.ts.map +1 -1
- package/lib/push.js +2 -4
- package/lib/push.js.map +1 -1
- package/lib/rage-tap.d.ts.map +1 -1
- package/lib/rage-tap.js +56 -23
- package/lib/rage-tap.js.map +1 -1
- package/lib/replay-screens.d.ts +15 -0
- package/lib/replay-screens.d.ts.map +1 -0
- package/lib/replay-screens.js +87 -0
- package/lib/replay-screens.js.map +1 -0
- package/lib/replay.d.ts.map +1 -1
- package/lib/replay.js +3 -24
- package/lib/replay.js.map +1 -1
- package/lib/responsiveness-detector.d.ts +18 -0
- package/lib/responsiveness-detector.d.ts.map +1 -0
- package/lib/responsiveness-detector.js +55 -0
- package/lib/responsiveness-detector.js.map +1 -0
- package/lib/scope.d.ts +7 -0
- package/lib/scope.d.ts.map +1 -0
- package/lib/scope.js +41 -0
- package/lib/scope.js.map +1 -0
- package/lib/transport.d.ts +19 -92
- package/lib/transport.d.ts.map +1 -1
- package/lib/transport.js +75 -288
- package/lib/transport.js.map +1 -1
- package/lib/verbs.d.ts +16 -0
- package/lib/verbs.d.ts.map +1 -0
- package/lib/verbs.js +153 -0
- package/lib/verbs.js.map +1 -0
- package/package.json +4 -3
- package/src/__tests__/iron-rule.test.ts +128 -0
- package/src/__tests__/replay-screens.test.ts +63 -0
- package/src/__tests__/responsiveness-detector.test.ts +74 -0
- package/src/__tests__/screenshot.test.ts +0 -2
- package/src/__tests__/transport.test.ts +66 -213
- package/src/__tests__/verbs.test.ts +91 -0
- package/src/config.ts +20 -47
- package/src/device.ts +36 -0
- package/src/emit-hooks.ts +30 -0
- package/src/error-boundary.tsx +1 -1
- package/src/handlers/dev-symbolicate.ts +2 -1
- package/src/handlers/global.ts +1 -1
- package/src/handlers/lifecycle.ts +3 -3
- package/src/handlers/network.ts +95 -211
- package/src/handlers/promise.ts +1 -1
- package/src/handlers/screenshot.ts +3 -11
- package/src/index.ts +84 -215
- package/src/init.ts +115 -449
- package/src/long-task-monitor.ts +42 -64
- package/src/mask.ts +29 -31
- package/src/mobile-vitals.ts +22 -82
- package/src/native-pending.ts +92 -0
- package/src/native.ts +33 -0
- package/src/navigation.ts +34 -142
- package/src/netinfo.ts +3 -3
- package/src/push.ts +2 -5
- package/src/rage-tap.tsx +72 -25
- package/src/replay-screens.ts +95 -0
- package/src/replay.ts +3 -20
- package/src/responsiveness-detector.ts +66 -0
- package/src/scope.ts +50 -0
- package/src/transport.ts +84 -371
- package/src/verbs.ts +198 -0
- package/lib/breadcrumbs.d.ts +0 -18
- package/lib/breadcrumbs.d.ts.map +0 -1
- package/lib/breadcrumbs.js +0 -42
- package/lib/breadcrumbs.js.map +0 -1
- package/lib/bundle-info.d.ts +0 -12
- package/lib/bundle-info.d.ts.map +0 -1
- package/lib/bundle-info.js +0 -73
- package/lib/bundle-info.js.map +0 -1
- package/lib/capture.d.ts +0 -140
- package/lib/capture.d.ts.map +0 -1
- package/lib/capture.js +0 -511
- package/lib/capture.js.map +0 -1
- package/lib/compat/sentry.d.ts +0 -126
- package/lib/compat/sentry.d.ts.map +0 -1
- package/lib/compat/sentry.js +0 -332
- package/lib/compat/sentry.js.map +0 -1
- package/lib/control-channel.d.ts +0 -6
- package/lib/control-channel.d.ts.map +0 -1
- package/lib/control-channel.js +0 -96
- package/lib/control-channel.js.map +0 -1
- package/lib/expo-compat.d.ts +0 -270
- package/lib/expo-compat.d.ts.map +0 -1
- package/lib/expo-compat.js +0 -500
- package/lib/expo-compat.js.map +0 -1
- package/lib/feedback-widget.d.ts +0 -35
- package/lib/feedback-widget.d.ts.map +0 -1
- package/lib/feedback-widget.js +0 -193
- package/lib/feedback-widget.js.map +0 -1
- package/lib/feedback.d.ts +0 -2
- package/lib/feedback.d.ts.map +0 -1
- package/lib/feedback.js +0 -17
- package/lib/feedback.js.map +0 -1
- package/lib/heartbeat.d.ts +0 -9
- package/lib/heartbeat.d.ts.map +0 -1
- package/lib/heartbeat.js +0 -138
- package/lib/heartbeat.js.map +0 -1
- package/lib/index.js.bak +0 -64
- package/lib/install-id.d.ts +0 -17
- package/lib/install-id.d.ts.map +0 -1
- package/lib/install-id.js +0 -125
- package/lib/install-id.js.map +0 -1
- package/lib/lifecycle.d.ts +0 -28
- package/lib/lifecycle.d.ts.map +0 -1
- package/lib/lifecycle.js +0 -73
- package/lib/lifecycle.js.map +0 -1
- package/lib/measure.d.ts +0 -4
- package/lib/measure.d.ts.map +0 -1
- package/lib/measure.js +0 -25
- package/lib/measure.js.map +0 -1
- package/lib/metrics.d.ts +0 -24
- package/lib/metrics.d.ts.map +0 -1
- package/lib/metrics.js +0 -81
- package/lib/metrics.js.map +0 -1
- package/lib/pre-crash-sentinel.d.ts +0 -12
- package/lib/pre-crash-sentinel.d.ts.map +0 -1
- package/lib/pre-crash-sentinel.js +0 -116
- package/lib/pre-crash-sentinel.js.map +0 -1
- package/lib/report-security.d.ts +0 -40
- package/lib/report-security.d.ts.map +0 -1
- package/lib/report-security.js +0 -159
- package/lib/report-security.js.map +0 -1
- package/lib/runtime-metrics-fps.d.ts +0 -19
- package/lib/runtime-metrics-fps.d.ts.map +0 -1
- package/lib/runtime-metrics-fps.js +0 -128
- package/lib/runtime-metrics-fps.js.map +0 -1
- package/lib/runtime-metrics-heap.d.ts +0 -13
- package/lib/runtime-metrics-heap.d.ts.map +0 -1
- package/lib/runtime-metrics-heap.js +0 -67
- package/lib/runtime-metrics-heap.js.map +0 -1
- package/lib/runtime-metrics-network.d.ts +0 -23
- package/lib/runtime-metrics-network.d.ts.map +0 -1
- package/lib/runtime-metrics-network.js +0 -99
- package/lib/runtime-metrics-network.js.map +0 -1
- package/lib/runtime-metrics.d.ts +0 -43
- package/lib/runtime-metrics.d.ts.map +0 -1
- package/lib/runtime-metrics.js +0 -115
- package/lib/runtime-metrics.js.map +0 -1
- package/lib/sample-profiler.d.ts +0 -21
- package/lib/sample-profiler.d.ts.map +0 -1
- package/lib/sample-profiler.js +0 -152
- package/lib/sample-profiler.js.map +0 -1
- package/lib/sentinel-context.d.ts +0 -4
- package/lib/sentinel-context.d.ts.map +0 -1
- package/lib/sentinel-context.js +0 -52
- package/lib/sentinel-context.js.map +0 -1
- package/lib/session-tracker.d.ts +0 -6
- package/lib/session-tracker.d.ts.map +0 -1
- package/lib/session-tracker.js +0 -50
- package/lib/session-tracker.js.map +0 -1
- package/lib/stack.d.ts +0 -3
- package/lib/stack.d.ts.map +0 -1
- package/lib/stack.js +0 -7
- package/lib/stack.js.map +0 -1
- package/lib/state-snapshots.d.ts +0 -33
- package/lib/state-snapshots.d.ts.map +0 -1
- package/lib/state-snapshots.js +0 -109
- package/lib/state-snapshots.js.map +0 -1
- package/lib/track.d.ts +0 -34
- package/lib/track.d.ts.map +0 -1
- package/lib/track.js +0 -107
- package/lib/track.js.map +0 -1
- package/lib/trail.d.ts +0 -12
- package/lib/trail.d.ts.map +0 -1
- package/lib/trail.js +0 -28
- package/lib/trail.js.map +0 -1
- package/lib/trust-score.d.ts +0 -20
- package/lib/trust-score.d.ts.map +0 -1
- package/lib/trust-score.js +0 -151
- package/lib/trust-score.js.map +0 -1
- package/lib/types.d.ts +0 -2
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -4
- package/lib/types.js.map +0 -1
- package/lib/uuid.d.ts +0 -2
- package/lib/uuid.d.ts.map +0 -1
- package/lib/uuid.js +0 -3
- package/lib/uuid.js.map +0 -1
- package/src/__tests__/before-send.test.ts +0 -72
- package/src/__tests__/breadcrumbs.test.ts +0 -44
- package/src/__tests__/capture-replay.test.ts +0 -150
- package/src/__tests__/compat-sentry.test.ts +0 -121
- package/src/__tests__/control-channel.test.ts +0 -111
- package/src/__tests__/install-id.test.ts +0 -60
- package/src/__tests__/long-task-monitor.test.ts +0 -59
- package/src/__tests__/measure.test.ts +0 -45
- package/src/__tests__/navigation.test.ts +0 -175
- package/src/__tests__/push.test.ts +0 -238
- package/src/__tests__/report-security.test.ts +0 -106
- package/src/__tests__/runtime-metrics-instruments.test.ts +0 -109
- package/src/__tests__/runtime-metrics-network.test.ts +0 -78
- package/src/__tests__/sample-profiler.test.ts +0 -59
- package/src/__tests__/stack.test.ts +0 -45
- package/src/__tests__/state-snapshots.test.ts +0 -119
- package/src/__tests__/tracing.test.ts +0 -201
- package/src/__tests__/track.test.ts +0 -91
- package/src/__tests__/uuid.test.ts +0 -41
- package/src/breadcrumbs.ts +0 -57
- package/src/bundle-info.ts +0 -95
- package/src/capture.ts +0 -660
- package/src/compat/sentry.ts +0 -490
- package/src/control-channel.ts +0 -98
- package/src/expo-compat.ts +0 -698
- package/src/feedback-widget.tsx +0 -316
- package/src/feedback.ts +0 -21
- package/src/heartbeat.ts +0 -157
- package/src/install-id.ts +0 -146
- package/src/lifecycle.ts +0 -76
- package/src/measure.ts +0 -28
- package/src/metrics.ts +0 -94
- package/src/pre-crash-sentinel.ts +0 -140
- package/src/report-security.ts +0 -165
- package/src/runtime-metrics-fps.ts +0 -137
- package/src/runtime-metrics-heap.ts +0 -78
- package/src/runtime-metrics-network.ts +0 -103
- package/src/runtime-metrics.ts +0 -122
- package/src/sample-profiler.ts +0 -169
- package/src/sentinel-context.ts +0 -60
- package/src/session-tracker.ts +0 -53
- package/src/stack.ts +0 -10
- package/src/state-snapshots.ts +0 -133
- package/src/track.ts +0 -123
- package/src/trail.ts +0 -32
- package/src/trust-score.ts +0 -176
- package/src/types.ts +0 -21
- 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
|
-
});
|