---
description: Comprehensive testing workflow for dual-implementation changes
globs: []
alwaysApply: false
---

# Testing Workflow

When testing changes in the Mixpanel library, follow this comprehensive approach to ensure reliability across both implementation modes:

## Quick Test Commands

```bash
# Run all tests
npm test

# Run specific test file
npm test -- __tests__/core.test.js

# Run tests in watch mode (during development)
npm test -- --watch

# Run tests with coverage
npm test -- --coverage
```

## Unit Testing Workflow

### Step 1: Set Up Test Environment (REQUIRED)

```javascript
// __tests__/new-feature.test.js
import { jest } from "@jest/globals";

// Import required mocks (automatically loaded from jest_setup.js)
const { MixpanelQueueManager } = require("mixpanel-react-native/javascript/mixpanel-queue");
const { MixpanelNetwork } = require("mixpanel-react-native/javascript/mixpanel-network");

describe('New Feature Tests', () => {
  beforeEach(() => {
    // ALWAYS reset all mocks before each test
    jest.clearAllMocks();
    
    // Reset any internal state
    MixpanelQueueManager._queues = {};
    
    // Clear any timers
    jest.clearAllTimers();
  });

  afterEach(() => {
    // Clean up any remaining timers or async operations
    jest.clearAllTimers();
  });
});
```

### Step 2: Test Input Validation (MANDATORY)

EVERY public method MUST have comprehensive input validation tests:

```javascript
describe('Input Validation', () => {
  let mixpanel;

  beforeEach(() => {
    mixpanel = new Mixpanel('test-token', true);
  });

  // Test ALL invalid string inputs
  const invalidStrings = ['', null, undefined, '   ', 123, {}, [], Symbol('test')];
  
  invalidStrings.forEach((invalid) => {
    it(`should reject invalid string parameter: ${JSON.stringify(invalid)}`, () => {
      expect(() => {
        mixpanel.track(invalid, {});
      }).toThrow('eventName is not a valid string');
    });
  });

  // Test ALL invalid object inputs
  const invalidObjects = ['string', 123, true, () => {}, Symbol('test')];
  
  invalidObjects.forEach((invalid) => {
    it(`should reject invalid object parameter: ${typeof invalid}`, () => {
      expect(() => {
        mixpanel.track('event', invalid);
      }).toThrow('properties is not a valid json object');
    });
  });

  // Test edge cases
  it('should allow null properties', () => {
    expect(() => {
      mixpanel.track('event', null);
    }).not.toThrow();
  });

  it('should allow undefined properties', () => {
    expect(() => {
      mixpanel.track('event', undefined);
    }).not.toThrow();
  });

  it('should reject whitespace-only strings', () => {
    expect(() => {
      mixpanel.track('   ', {});
    }).toThrow('eventName is not a valid string');
  });
});
```

### Step 3: Test Native Mode Implementation (REQUIRED)

```javascript
describe('Native Mode', () => {
  let mixpanel;

  beforeEach(() => {
    // FORCE native mode
    mixpanel = new Mixpanel('test-token', true, true);
  });

  it('should call native implementation for tracking', () => {
    const eventName = 'Test Event';
    const properties = { testProp: 'testValue' };

    mixpanel.track(eventName, properties);

    expect(MixpanelReactNative.track).toHaveBeenCalledWith(
      'test-token',
      eventName,
      expect.objectContaining({
        ...properties,
        // Should include metadata
        mp_lib: 'react-native',
        $lib_version: expect.any(String)
      })
    );
  });

  it('should handle native implementation promises', async () => {
    // Mock native method to return promise
    MixpanelReactNative.identify.mockResolvedValue();

    await expect(
      mixpanel.identify('user123')
    ).resolves.toBeUndefined();

    expect(MixpanelReactNative.identify).toHaveBeenCalledWith(
      'test-token',
      'user123'
    );
  });

  it('should handle native implementation errors', async () => {
    // Mock native method to reject
    const error = new Error('Native error');
    MixpanelReactNative.identify.mockRejectedValue(error);

    await expect(
      mixpanel.identify('user123')
    ).rejects.toThrow('Native error');
  });

  it('should verify implementation type', () => {
    expect(mixpanel.mixpanelImpl).toBe(MixpanelReactNative);
  });
});
```

### Step 4: Test JavaScript Mode Implementation (REQUIRED)

```javascript
describe('JavaScript Mode', () => {
  let mixpanel;

  beforeEach(() => {
    // FORCE JavaScript mode
    mixpanel = new Mixpanel('test-token', true, false);
  });

  it('should use JavaScript implementation when native unavailable', () => {
    expect(mixpanel.mixpanelImpl).toBeInstanceOf(MixpanelMain);
    expect(mixpanel.mixpanelImpl).not.toBe(MixpanelReactNative);
  });

  it('should queue events for batch processing', async () => {
    const eventData = {
      event: 'Test Event',
      properties: { prop: 'value' }
    };

    mixpanel.track(eventData.event, eventData.properties);

    // Verify event was added to queue
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      expect.objectContaining({
        event: eventData.event,
        properties: expect.objectContaining(eventData.properties)
      })
    );
  });

  it('should respect opt-out status', async () => {
    // Mock opt-out status
    const mockPersistent = {
      getOptedOut: jest.fn().mockReturnValue(true)
    };
    MixpanelPersistent.getInstance.mockReturnValue(mockPersistent);

    mixpanel.track('Test Event', {});

    // Should not queue event when opted out
    expect(MixpanelQueueManager.enqueue).not.toHaveBeenCalled();
  });

  it('should enrich events with complete metadata', async () => {
    // Set up context
    await mixpanel.identify('user123');
    await mixpanel.registerSuperProperties({ segment: 'premium' });

    mixpanel.track('Purchase', { amount: 99.99 });

    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      expect.objectContaining({
        event: 'Purchase',
        properties: expect.objectContaining({
          // Event properties
          amount: 99.99,
          // Super properties
          segment: 'premium',
          // Identity properties
          distinct_id: 'user123',
          // Metadata
          mp_lib: 'react-native',
          $lib_version: expect.any(String),
          // Session metadata
          $mp_metadata: expect.objectContaining({
            $mp_session_id: expect.any(String),
            $mp_event_id: expect.any(String)
          })
        })
      })
    );
  });
});
```

### Step 5: Test Error Handling (CRITICAL)

```javascript
describe('Error Handling', () => {
  it('should handle storage failures gracefully', async () => {
    // Mock storage failure
    const storageError = new Error('Storage unavailable');
    AsyncStorage.setItem.mockRejectedValue(storageError);

    // Should not throw error
    await expect(
      mixpanel.registerSuperProperties({ prop: 'value' })
    ).resolves.toBeUndefined();

    // Should log error
    expect(console.error).toHaveBeenCalledWith(
      expect.stringContaining('error setting item in storage')
    );
  });

  it('should handle network failures with retry', async () => {
    // Mock network failure then success
    const networkError = new Error('Network timeout');
    global.fetch
      .mockRejectedValueOnce(networkError)
      .mockResolvedValueOnce({
        status: 200,
        json: () => Promise.resolve(1)
      });

    await MixpanelNetwork.sendRequest({
      token: 'test-token',
      endpoint: '/track/',
      data: [{ event: 'test' }],
      serverURL: 'https://api.mixpanel.com',
      useIPAddressForGeoLocation: true
    });

    // Should retry on failure
    expect(global.fetch).toHaveBeenCalledTimes(2);
  });

  it('should handle corrupted storage data', async () => {
    // Mock corrupted JSON in storage
    AsyncStorage.getItem.mockResolvedValue('invalid-json{');

    const superProps = await mixpanel.getSuperProperties();

    // Should return empty object as fallback
    expect(superProps).toEqual({});
    
    // Should log error
    expect(console.error).toHaveBeenCalledWith(
      expect.stringContaining('Failed to parse')
    );
  });

  it('should handle serialization errors', () => {
    const circularRef = {};
    circularRef.self = circularRef;

    expect(() => {
      mixpanel.track('Event', { circular: circularRef });
    }).toThrow(); // Should throw during validation, not silently fail
  });
});
```

## Integration Testing Workflow

### End-to-End Flow Testing

```javascript
describe('Complete Tracking Flow', () => {
  let mixpanel;

  beforeEach(async () => {
    mixpanel = new Mixpanel('test-token', true, false); // JavaScript mode
    await mixpanel.init();
  });

  it('should track event through complete pipeline', async () => {
    // Set up user identity
    await mixpanel.identify('user123');
    
    // Add super properties
    await mixpanel.registerSuperProperties({ 
      user_segment: 'premium' 
    });

    // Track event
    mixpanel.track('Purchase', { 
      amount: 99.99, 
      currency: 'USD' 
    });

    // Verify complete event structure
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      expect.objectContaining({
        event: 'Purchase',
        properties: expect.objectContaining({
          // Event properties
          amount: 99.99,
          currency: 'USD',
          // Super properties
          user_segment: 'premium',
          // Identity properties
          distinct_id: 'user123',
          // Metadata
          mp_lib: 'react-native',
          $lib_version: expect.any(String)
        })
      })
    );
  });

  it('should handle queue processing and network requests', async () => {
    // Add multiple events to queue
    for (let i = 0; i < 5; i++) {
      mixpanel.track(`Event ${i}`, { index: i });
    }

    // Mock successful network response
    global.fetch.mockResolvedValue({
      status: 200,
      json: () => Promise.resolve(1)
    });

    // Trigger queue processing
    await mixpanel.flush();

    // Verify network request was made
    expect(global.fetch).toHaveBeenCalledWith(
      expect.stringContaining('https://api.mixpanel.com/track/'),
      expect.objectContaining({
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: expect.stringContaining('data=')
      })
    );

    // Verify queue was cleared after successful send
    expect(MixpanelQueueManager.spliceQueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      0,
      5 // All 5 events should be removed
    );
  });
});
```

### Multi-Token Testing

```javascript
describe('Multi-Token Support', () => {
  it('should isolate data between different tokens', async () => {
    const mixpanel1 = new Mixpanel('token1', true, false);
    const mixpanel2 = new Mixpanel('token2', true, false);

    await mixpanel1.init();
    await mixpanel2.init();

    // Set different properties for each token
    await mixpanel1.registerSuperProperties({ source: 'app1' });
    await mixpanel2.registerSuperProperties({ source: 'app2' });

    // Track events
    mixpanel1.track('Event1', {});
    mixpanel2.track('Event2', {});

    // Verify token isolation in queue calls
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'token1',
      MixpanelType.EVENTS,
      expect.objectContaining({
        properties: expect.objectContaining({ source: 'app1' })
      })
    );

    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'token2', 
      MixpanelType.EVENTS,
      expect.objectContaining({
        properties: expect.objectContaining({ source: 'app2' })
      })
    );
  });
});
```

## Manual Testing with Sample Apps

### SimpleMixpanel Sample Testing

```bash
# Navigate to sample app
cd Samples/SimpleMixpanel

# Install dependencies
npm install

# For iOS testing
cd ios && pod install && cd ..
npx react-native run-ios

# For Android testing  
npx react-native run-android
```

### Verify Native Integration

```javascript
// Test in sample app console
const testNativeIntegration = () => {
  // Test basic tracking
  mixpanel.track('Sample Test Event', {
    test_property: 'sample_value',
    timestamp: Date.now()
  });
  
  // Test identity
  mixpanel.identify('sample_user_' + Date.now());
  
  // Test super properties
  mixpanel.registerSuperProperties({
    app_version: '1.0.0',
    test_mode: true
  });
  
  // Test People Analytics
  mixpanel.getPeople().set('name', 'Sample User');
  
  console.log('Manual test completed');
};
```

### Test JavaScript Fallback

```javascript
// Force JavaScript mode for testing
const testJavaScriptMode = async () => {
  const mixpanel = new Mixpanel('test-token', true, false);
  await mixpanel.init();
  
  // Same tests as native mode
  mixpanel.track('JS Mode Test', { mode: 'javascript' });
  
  // Verify console logs show JavaScript mode
  console.log('Check logs for JavaScript mode indicators');
};
```

## Performance Testing

### Load Testing Pattern

```javascript
describe('Performance Tests', () => {
  it('should handle high-volume event tracking', async () => {
    const startTime = Date.now();
    
    // Track 1000 events
    for (let i = 0; i < 1000; i++) {
      mixpanel.track(`Event ${i}`, { index: i });
    }
    
    const endTime = Date.now();
    const duration = endTime - startTime;
    
    // Should complete within reasonable time
    expect(duration).toBeLessThan(5000); // 5 seconds
    
    // Verify all events were queued
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledTimes(1000);
  });

  it('should handle memory pressure', async () => {
    // Test with large objects
    const largeProperty = 'x'.repeat(100000);
    
    for (let i = 0; i < 100; i++) {
      mixpanel.track(`Large Event ${i}`, { 
        large_data: largeProperty,
        index: i 
      });
    }
    
    // Should complete without memory issues
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledTimes(100);
  });
});
```

## Debugging Test Issues

### Mock Verification

```javascript
// Verify mocks are working correctly
beforeEach(() => {
  // Ensure mocks are properly reset and functional
  expect(jest.isMockFunction(MixpanelReactNative.track)).toBe(true);
  expect(jest.isMockFunction(AsyncStorage.getItem)).toBe(true);
  expect(jest.isMockFunction(global.fetch)).toBe(true);
});

// Debug helper for test failures
const debugTestState = () => {
  console.log('Current mock state:', {
    nativeModuleCalls: MixpanelReactNative.track.mock.calls,
    queueManagerCalls: MixpanelQueueManager.enqueue.mock.calls,
    asyncStorageCalls: AsyncStorage.setItem.mock.calls
  });
};
```

## Testing Checklist

### Unit Testing
- [ ] Input validation for all parameters
- [ ] Error handling for invalid inputs
- [ ] Native mode implementation calls
- [ ] JavaScript mode implementation logic
- [ ] Opt-out behavior respect
- [ ] Promise handling (resolve/reject)
- [ ] Mock verification and reset

### Integration Testing  
- [ ] End-to-end tracking flow
- [ ] Queue management and processing
- [ ] Network request handling
- [ ] Multi-token isolation
- [ ] Storage persistence
- [ ] Error recovery scenarios

### Manual Testing
- [ ] Sample app functionality (iOS)
- [ ] Sample app functionality (Android)
- [ ] Native module integration
- [ ] JavaScript fallback mode
- [ ] Console logging verification
- [ ] Performance under load

### Platform Testing
- [ ] iOS pod install success
- [ ] Android gradle build success
- [ ] React Native autolinking
- [ ] TypeScript compilation
- [ ] Expo compatibility

### Edge Case Testing
- [ ] Storage failures
- [ ] Network failures
- [ ] Invalid JSON in storage
- [ ] Memory pressure scenarios
- [ ] App backgrounding/foregrounding
- [ ] Multiple simultaneous instances

## Test Maintenance

### Keep Tests Up to Date
- Update mocks when adding new native methods
- Test both modes for every new feature
- Verify error handling for all failure scenarios
- Test with real devices, not just simulators

### Performance Monitoring
- Monitor test execution time
- Check for memory leaks in tests
- Verify mock cleanup between tests
- Test with production-like data volumes

This comprehensive testing workflow ensures all changes maintain the library's reliability and compatibility across both native and JavaScript implementations.