* feat: implement floating banner system - Add FloatingBanner component with gesture support and keyboard awareness - Implement BannerManager singleton for banner lifecycle management - Create floating banner screen with SafeAreaProvider integration - Add comprehensive banner configuration types and positioning - Update Banner component to use modern gesture handling - Enhance BannerItem with improved typography and spacing (40px height) - Add ConnectionBanner improvements with better sizing - Remove ConnectionBanner from channel list (moved to floating system) - Update screens constants (remove FLOATING_BANNER - handled as overlay) - Add i18n support for limited network connection message The system provides: - Auto-hide functionality with customizable duration - Position-aware rendering (top/bottom with keyboard adjustment) - Tablet-specific offset handling - Swipe-to-dismiss with configurable thresholds - Custom component support alongside default banner items - Comprehensive test coverage with device-specific scenarios * docs: add floating banner system documentation and cleanup - Add comprehensive floating-banner.md with architecture diagrams - Remove incompatible connection_banner/index.ts file - Update device.ts hooks for better keyboard handling - Simplify screens/index.tsx floating banner registration - Update test/setup.ts to remove deprecated keyboard mocks - Clean up keyboard height logic and ESLint issues The documentation covers: - System architecture and component relationships - API reference and usage patterns - Performance considerations and best practices - Integration points and troubleshooting guide - Comprehensive testing strategy All tests now pass with the updated setup. * fix issue with translation file * some self cleanup. * renamed index.tsx => Banner.tsx * creaete meaningful tests for Banner component and all the hooks. * fix tests * cleanup based on initial review by AI * dismissible was set to true, changing to what was configured. * making title and message optional * feat(MM-65145): Network connectivity/performance observer * add MONITOR_NETWORK_PERFORMANCE * i18n stuff * remove unused props * Undo some AI changes that caught by another AI * network connectivity observation md * addressed some comments in PR * more fixes based on PR review. * added future enhancement * dismissOverlay will be awaited * delay dismissing overlay so we don't have to show a new one all the time * make the banner stackable * Fix issue with last banner dismissal delayed by 2s * update floating-banner test * put back an extra space * add a todo to use namespace * add comment on priority order for connectivity performance * use static const vs magic number * add a guard against adding performanceSubject when server has been removed * fix failing tests * clean-up based on review by @enahum * fix failing test * fix failiing tests * rename confusing var * update changes to types * fixed issue with swipe not working on android * performance and connectivity use the same id so that 2 banners won't appear * fix issue w/ android not registering touch events behind the overlay * fix failing test * hideBanner now needs id. * Connection status unknown added in en.json * update the doc * animate the banner moving up when bottom banner first appear. * removed unused functions and update tests * add useMemo and useCallback * update jsdoc to say dismissable is default true * fix failing test
536 lines
22 KiB
TypeScript
536 lines
22 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {testExports} from './network_performance_manager';
|
|
|
|
import type {ClientResponseMetrics} from '@mattermost/react-native-network-client';
|
|
import type {AppStateStatus} from 'react-native';
|
|
|
|
jest.mock('@utils/log', () => ({
|
|
logDebug: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('react-native', () => ({
|
|
AppState: {
|
|
addEventListener: jest.fn(() => ({
|
|
remove: jest.fn(),
|
|
})),
|
|
},
|
|
}));
|
|
|
|
const {
|
|
NetworkPerformanceManagerSingleton,
|
|
REQUEST_OUTCOME_WINDOW_SIZE,
|
|
MINIMUM_REQUESTS_FOR_INITIAL_DETECTION,
|
|
calculatePerformanceStateFromOutcomes,
|
|
} = testExports;
|
|
|
|
const createMockMetrics = (latency: number, size: number, compressedSize: number): ClientResponseMetrics => ({
|
|
latency,
|
|
size,
|
|
compressedSize,
|
|
startTime: Date.now(),
|
|
endTime: Date.now() + latency,
|
|
networkType: 'wifi',
|
|
tlsCipherSuite: 'TLS_AES_256_GCM_SHA384',
|
|
tlsVersion: 'TLSv1.3',
|
|
httpVersion: 'HTTP/2',
|
|
isCached: false,
|
|
connectionTime: 0,
|
|
speedInMbps: 0,
|
|
});
|
|
|
|
describe('Pure Functions', () => {
|
|
describe('calculatePerformanceStateFromOutcomes', () => {
|
|
describe('initial detection', () => {
|
|
it('should return normal when not enough requests for initial detection', () => {
|
|
const outcomes = [
|
|
{timestamp: Date.now(), isSlow: true, wasEarlyDetection: false},
|
|
{timestamp: Date.now(), isSlow: true, wasEarlyDetection: false},
|
|
];
|
|
|
|
const state = calculatePerformanceStateFromOutcomes(outcomes, true);
|
|
expect(state).toBe('normal');
|
|
});
|
|
|
|
it('should return slow when initial detection threshold is met', () => {
|
|
const outcomes = Array.from({length: MINIMUM_REQUESTS_FOR_INITIAL_DETECTION}, (_, i) => ({
|
|
timestamp: Date.now(),
|
|
isSlow: i < 3, // 3 out of 4 = 75%
|
|
wasEarlyDetection: false,
|
|
}));
|
|
|
|
const state = calculatePerformanceStateFromOutcomes(outcomes, true);
|
|
expect(state).toBe('slow');
|
|
});
|
|
|
|
it('should return normal when initial detection threshold is met but percentage is low', () => {
|
|
const outcomes = Array.from({length: MINIMUM_REQUESTS_FOR_INITIAL_DETECTION}, (_, i) => ({
|
|
timestamp: Date.now(),
|
|
isSlow: i < 2, // 2 out of 4 = 50%
|
|
wasEarlyDetection: false,
|
|
}));
|
|
|
|
const state = calculatePerformanceStateFromOutcomes(outcomes, true);
|
|
expect(state).toBe('normal');
|
|
});
|
|
});
|
|
|
|
describe('subsequent detection', () => {
|
|
it('should return slow even with fewer requests than subsequent threshold', () => {
|
|
const outcomes = Array.from({length: 6}, (_, i) => ({
|
|
timestamp: Date.now(),
|
|
isSlow: i < 5, // 5 out of 6 = 83%
|
|
wasEarlyDetection: false,
|
|
}));
|
|
|
|
const state = calculatePerformanceStateFromOutcomes(outcomes, false);
|
|
expect(state).toBe('slow');
|
|
});
|
|
|
|
it('should return normal when slow percentage is below threshold', () => {
|
|
const outcomes = Array.from({length: 10}, (_, i) => ({
|
|
timestamp: Date.now(),
|
|
isSlow: i < 3, // 3 out of 10 = 30%
|
|
wasEarlyDetection: false,
|
|
}));
|
|
|
|
const state = calculatePerformanceStateFromOutcomes(outcomes, false);
|
|
expect(state).toBe('normal');
|
|
});
|
|
|
|
it('should return slow when slow percentage meets threshold', () => {
|
|
const outcomes = Array.from({length: 10}, (_, i) => ({
|
|
timestamp: Date.now(),
|
|
isSlow: i < 7, // 7 out of 10 = 70%
|
|
wasEarlyDetection: false,
|
|
}));
|
|
|
|
const state = calculatePerformanceStateFromOutcomes(outcomes, false);
|
|
expect(state).toBe('slow');
|
|
});
|
|
|
|
it('should return slow when slow percentage exceeds threshold', () => {
|
|
const outcomes = Array.from({length: 10}, (_, i) => ({
|
|
timestamp: Date.now(),
|
|
isSlow: i < 8, // 8 out of 10 = 80%
|
|
wasEarlyDetection: false,
|
|
}));
|
|
|
|
const state = calculatePerformanceStateFromOutcomes(outcomes, false);
|
|
expect(state).toBe('slow');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('NetworkPerformanceManager', () => {
|
|
let performanceManager: InstanceType<typeof NetworkPerformanceManagerSingleton>;
|
|
const serverUrl = 'https://test-server.com';
|
|
|
|
beforeEach(() => {
|
|
performanceManager = new NetworkPerformanceManagerSingleton();
|
|
});
|
|
|
|
describe('request tracking lifecycle', () => {
|
|
it('should start and complete request tracking', () => {
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
const url = '/api/v4/users/me';
|
|
const metrics = createMockMetrics(500, 1000, 500);
|
|
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, url);
|
|
expect(typeof requestId).toBe('string');
|
|
expect(requestId).toContain('-');
|
|
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(1);
|
|
expect(stats.slowRequests).toBe(0);
|
|
});
|
|
|
|
it('should track slow requests correctly', () => {
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
const url = '/api/v4/posts';
|
|
const slowMetrics = createMockMetrics(3000, 1000, 500);
|
|
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, url);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, slowMetrics);
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(1);
|
|
expect(stats.slowRequests).toBe(1);
|
|
expect(stats.slowPercentage).toBe(1.0);
|
|
});
|
|
|
|
it('should cancel request tracking on failure', () => {
|
|
const url = '/api/v4/teams';
|
|
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, url);
|
|
performanceManager.cancelRequestTracking(serverUrl, requestId);
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('performance state calculation', () => {
|
|
it('should return normal when slow percentage is below threshold', () => {
|
|
const states: string[] = [];
|
|
const subscription = performanceManager.observePerformanceState(serverUrl).subscribe((state) => {
|
|
states.push(state);
|
|
});
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 3 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
subscription.unsubscribe();
|
|
expect(states).toContain('normal');
|
|
});
|
|
|
|
it('should return slow when slow percentage meets threshold', () => {
|
|
const states: string[] = [];
|
|
const subscription = performanceManager.observePerformanceState(serverUrl).subscribe((state) => {
|
|
states.push(state);
|
|
});
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 7 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
subscription.unsubscribe();
|
|
expect(states).toContain('slow');
|
|
});
|
|
|
|
it('should use initial detection threshold for first slow detection', () => {
|
|
const states: string[] = [];
|
|
const subscription = performanceManager.observePerformanceState(serverUrl).subscribe((state) => {
|
|
states.push(state);
|
|
});
|
|
|
|
for (let i = 0; i < MINIMUM_REQUESTS_FOR_INITIAL_DETECTION; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 3 ? 3000 : 500, 1000, 500); // 3 out of 4 = 75%
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
subscription.unsubscribe();
|
|
expect(states).toEqual(['normal', 'slow']);
|
|
});
|
|
|
|
it('should not return to normal when switching from initial to subsequent detection', () => {
|
|
const states: string[] = [];
|
|
const subscription = performanceManager.observePerformanceState(serverUrl).subscribe((state) => {
|
|
states.push(state);
|
|
});
|
|
|
|
for (let i = 0; i < MINIMUM_REQUESTS_FOR_INITIAL_DETECTION; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 3 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-extra-${i}`);
|
|
const metrics = createMockMetrics(i < 3 ? 3000 : 500, 1000, 500); // 3 more slow out of 4
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
subscription.unsubscribe();
|
|
|
|
expect(states.filter((s) => s === 'normal')).toHaveLength(1);
|
|
expect(states).toEqual(['normal', 'slow']);
|
|
});
|
|
});
|
|
|
|
describe('outcome statistics', () => {
|
|
it('should provide accurate request outcome statistics', () => {
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
for (let i = 0; i < 20; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 15 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(20);
|
|
expect(stats.slowRequests).toBe(15);
|
|
expect(stats.slowPercentage).toBe(0.75);
|
|
expect(stats.earlyDetectionCount).toBe(0);
|
|
});
|
|
|
|
it('should limit outcome window size', () => {
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
for (let i = 0; i < REQUEST_OUTCOME_WINDOW_SIZE + 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(REQUEST_OUTCOME_WINDOW_SIZE);
|
|
});
|
|
});
|
|
|
|
describe('server management', () => {
|
|
it('should handle multiple servers independently', () => {
|
|
const serverUrl2 = 'https://test-server-2.com';
|
|
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
performanceManager.observePerformanceState(serverUrl2);
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl2, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 8 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl2, requestId, metrics);
|
|
}
|
|
|
|
const state1 = performanceManager.getCurrentPerformanceState(serverUrl);
|
|
const state2 = performanceManager.getCurrentPerformanceState(serverUrl2);
|
|
|
|
expect(state1).toBe('normal');
|
|
expect(state2).toBe('slow');
|
|
|
|
performanceManager.removeServer(serverUrl2);
|
|
});
|
|
|
|
it('should clean up when server is removed', () => {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, '/api/test');
|
|
const metrics = createMockMetrics(500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
|
|
performanceManager.removeServer(serverUrl);
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(0);
|
|
});
|
|
|
|
it('should not recreate storage when request completes after server removal', () => {
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, '/api/test');
|
|
|
|
performanceManager.removeServer(serverUrl);
|
|
|
|
const metrics = createMockMetrics(3000, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(0);
|
|
|
|
const state = performanceManager.getCurrentPerformanceState(serverUrl);
|
|
expect(state).toBe('normal');
|
|
});
|
|
|
|
it('should not recreate storage when early detection timer fires after server removal', () => {
|
|
jest.useFakeTimers();
|
|
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
performanceManager.startRequestTracking(serverUrl, '/api/test');
|
|
|
|
performanceManager.removeServer(serverUrl);
|
|
|
|
jest.advanceTimersByTime(2000);
|
|
|
|
const stats = performanceManager.getRequestOutcomeStats(serverUrl);
|
|
expect(stats.totalRequests).toBe(0);
|
|
|
|
const state = performanceManager.getCurrentPerformanceState(serverUrl);
|
|
expect(state).toBe('normal');
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
});
|
|
|
|
describe('observable behavior', () => {
|
|
it('should emit state changes when performance changes', () => {
|
|
const states: string[] = [];
|
|
const subscription = performanceManager.observePerformanceState(serverUrl).subscribe((state) => {
|
|
states.push(state);
|
|
});
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 8 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
subscription.unsubscribe();
|
|
expect(states).toEqual(['normal', 'slow']);
|
|
});
|
|
});
|
|
|
|
describe('debug logging', () => {
|
|
const {logDebug} = require('@utils/log');
|
|
|
|
beforeEach(() => {
|
|
logDebug.mockClear();
|
|
});
|
|
|
|
it('should log when performance state degrades from normal to slow', () => {
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 8 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
expect(logDebug).toHaveBeenCalledWith(
|
|
`Network performance degraded for ${serverUrl}: normal -> slow`,
|
|
expect.objectContaining({
|
|
totalRequests: expect.any(Number),
|
|
slowRequests: expect.any(Number),
|
|
slowPercentage: expect.any(String),
|
|
earlyDetectionCount: expect.any(Number),
|
|
lastOutcome: expect.objectContaining({
|
|
isSlow: expect.any(Boolean),
|
|
wasEarlyDetection: expect.any(Boolean),
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should not log when performance state remains the same', () => {
|
|
for (let i = 0; i < 3; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
expect(logDebug).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should log with correct details when performance degrades', () => {
|
|
performanceManager.observePerformanceState(serverUrl);
|
|
|
|
for (let i = 0; i < MINIMUM_REQUESTS_FOR_INITIAL_DETECTION; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(3000, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
expect(logDebug).toHaveBeenCalledWith(
|
|
`Network performance degraded for ${serverUrl}: normal -> slow`,
|
|
expect.objectContaining({
|
|
totalRequests: MINIMUM_REQUESTS_FOR_INITIAL_DETECTION,
|
|
slowRequests: MINIMUM_REQUESTS_FOR_INITIAL_DETECTION,
|
|
slowPercentage: '100.0%',
|
|
earlyDetectionCount: 0,
|
|
currentTimestamp: expect.any(Number),
|
|
detectionDelayMs: expect.any(Number),
|
|
detectionDelaySeconds: expect.any(String),
|
|
lastOutcome: {
|
|
isSlow: true,
|
|
wasEarlyDetection: false,
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should not log when performance recovers from slow to normal', () => {
|
|
for (let i = 0; i < MINIMUM_REQUESTS_FOR_INITIAL_DETECTION; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(3000, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
logDebug.mockClear();
|
|
for (let i = 0; i < 20; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/fast-${i}`);
|
|
const metrics = createMockMetrics(500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
expect(logDebug).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('AppState monitoring', () => {
|
|
const {AppState} = require('react-native');
|
|
let appStateHandler: (nextAppState: AppStateStatus) => void;
|
|
|
|
beforeEach(() => {
|
|
AppState.addEventListener.mockImplementation((event: string, handler: (nextAppState: AppStateStatus) => void) => {
|
|
if (event === 'change') {
|
|
appStateHandler = handler;
|
|
}
|
|
return {remove: jest.fn()};
|
|
});
|
|
});
|
|
|
|
it('should setup AppState monitoring on construction', () => {
|
|
const manager = new NetworkPerformanceManagerSingleton();
|
|
expect(AppState.addEventListener).toHaveBeenCalledWith('change', expect.any(Function));
|
|
manager.destroy();
|
|
});
|
|
|
|
it('should clean up active requests when app goes to background', () => {
|
|
performanceManager.startRequestTracking(serverUrl, '/api/test');
|
|
|
|
expect(performanceManager.getActiveRequestCount(serverUrl)).toBe(1);
|
|
|
|
appStateHandler('background');
|
|
|
|
expect(performanceManager.getActiveRequestCount(serverUrl)).toBe(0);
|
|
});
|
|
|
|
it('should reset performance state to normal when app becomes inactive', () => {
|
|
const states: string[] = [];
|
|
const subscription = performanceManager.observePerformanceState(serverUrl).subscribe((state) => {
|
|
states.push(state);
|
|
});
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, `/api/request-${i}`);
|
|
const metrics = createMockMetrics(i < 8 ? 3000 : 500, 1000, 500);
|
|
performanceManager.completeRequestTracking(serverUrl, requestId, metrics);
|
|
}
|
|
|
|
expect(performanceManager.getCurrentPerformanceState(serverUrl)).toBe('slow');
|
|
|
|
appStateHandler('inactive');
|
|
|
|
expect(performanceManager.getCurrentPerformanceState(serverUrl)).toBe('normal');
|
|
subscription.unsubscribe();
|
|
});
|
|
|
|
it('should not clean up when app state is active', () => {
|
|
const requestId = performanceManager.startRequestTracking(serverUrl, '/api/test');
|
|
|
|
expect(performanceManager.getActiveRequestCount(serverUrl)).toBe(1);
|
|
|
|
appStateHandler('active');
|
|
|
|
expect(performanceManager.getActiveRequestCount(serverUrl)).toBe(1);
|
|
|
|
performanceManager.cancelRequestTracking(serverUrl, requestId);
|
|
});
|
|
|
|
it('should clean up AppState subscription on destroy', () => {
|
|
const mockRemove = jest.fn();
|
|
AppState.addEventListener.mockReturnValue({remove: mockRemove});
|
|
|
|
const manager = new NetworkPerformanceManagerSingleton();
|
|
manager.destroy();
|
|
|
|
expect(mockRemove).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|