import withKlaviyoAndroid from '../plugin/withKlaviyoAndroid'; import { KlaviyoPluginAndroidProps } from '../plugin/types'; import { testIntegrationPluginFunction, testSimpleIntegration, createMockProps } from './utils/testHelpers'; // Mock file system operations jest.mock('fs', () => ({ existsSync: jest.fn(() => true), readFileSync: jest.fn(() => 'class MainActivity extends ReactActivity {}'), writeFileSync: jest.fn(), mkdirSync: jest.fn(), copyFileSync: jest.fn(), unlinkSync: jest.fn(), })); jest.mock('glob', () => ({ sync: jest.fn(() => ['MainActivity.java']), })); jest.mock('@expo/config-plugins/build/android/Paths', () => ({ getMainActivityAsync: jest.fn(() => Promise.resolve('/test/path/MainActivity.java')), })); describe('withKlaviyoAndroid Integration Tests', () => { describe('withAndroidManifestModifications', () => { it('should add log level meta-data with default value', () => { testIntegrationPluginFunction(withKlaviyoAndroid, ` `); }); it('should add log level meta-data with custom value', () => { testSimpleIntegration(withKlaviyoAndroid, { logLevel: 3 }); }); it('should replace existing log level meta-data', () => { testSimpleIntegration(withKlaviyoAndroid, { logLevel: 2 }); }); it('should add KlaviyoPushService to manifest', () => { testSimpleIntegration(withKlaviyoAndroid); }); it('should not duplicate KlaviyoPushService if already exists', () => { testSimpleIntegration(withKlaviyoAndroid); }); it('should create application tag if missing', () => { testIntegrationPluginFunction(withKlaviyoAndroid, ` `); }); it('should create meta-data array if missing', () => { testIntegrationPluginFunction(withKlaviyoAndroid, ` `); }); it('should handle existing meta-data array', () => { testIntegrationPluginFunction(withKlaviyoAndroid, ` `); }); it('should handle existing service array', () => { testIntegrationPluginFunction(withKlaviyoAndroid, ` `); }); }); describe('withNotificationManifest', () => { it('should add notification icon meta-data when icon path is provided', () => { testSimpleIntegration(withKlaviyoAndroid, { notificationIconFilePath: './assets/icon.png' }); }); it('should remove notification icon meta-data when icon path is not provided', () => { testSimpleIntegration(withKlaviyoAndroid, { notificationIconFilePath: undefined }); }); it('should add notification color meta-data when color is provided', () => { testSimpleIntegration(withKlaviyoAndroid, { notificationColor: '#FF0000' }); }); it('should remove notification color meta-data when color is not provided', () => { testSimpleIntegration(withKlaviyoAndroid, { notificationColor: undefined }); }); it('should not duplicate existing notification icon meta-data', () => { testSimpleIntegration(withKlaviyoAndroid, { notificationIconFilePath: './assets/icon.png' }); }); it('should handle existing notification icon meta-data', () => { testIntegrationPluginFunction(withKlaviyoAndroid, ` `, { notificationIconFilePath: './assets/icon.png' }); }); it('should handle existing notification color meta-data', () => { testIntegrationPluginFunction(withKlaviyoAndroid, ` `, { notificationColor: '#FF0000' }); }); }); describe('withKlaviyoPluginNameVersion', () => { it('should add plugin name and version string resources', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should update existing string resources', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should create string array if it does not exist', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); }); describe('File operations and validation', () => { it('should handle notification icon file validation', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps({ notificationIconFilePath: './assets/icon.png' }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle missing notification icon file', () => { const fs = require('fs'); fs.existsSync.mockReturnValue(false); const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps({ notificationIconFilePath: './notfound.png' }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle file copy operations', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps({ notificationIconFilePath: './assets/icon.png' }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle MainActivity file operations', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle MainActivity with package declaration', () => { const fs = require('fs'); fs.readFileSync.mockReturnValue(` package com.example.test; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { @Override protected String getMainComponentName() { return "main"; } } `); const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle Kotlin MainActivity', () => { const fs = require('fs'); fs.readFileSync.mockReturnValue(` package com.example.test import com.facebook.react.ReactActivity class MainActivity : ReactActivity() { override fun getMainComponentName(): String { return "main" } } `); const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle MainActivity without package declaration', () => { const fs = require('fs'); fs.readFileSync.mockReturnValue(` public class MainActivity extends ReactActivity { @Override protected String getMainComponentName() { return "main"; } } `); const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); }); describe('Error handling and edge cases', () => { it('should handle missing android config', () => { const config: any = { name: 'test-app', slug: 'test-app' }; const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle missing manifest config', () => { const config: any = { name: 'test-app', slug: 'test-app', android: {} }; const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle empty manifest contents', () => { const config: any = { name: 'test-app', slug: 'test-app', android: { manifest: { contents: '' } } }; const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle null config', () => { const props = createMockProps(); expect(() => withKlaviyoAndroid(null as any, props)).toThrow(); }); it('should handle undefined config', () => { const props = createMockProps(); expect(() => withKlaviyoAndroid(undefined as any, props)).toThrow(); }); it('should handle complex nested manifest structure', () => { const config: any = { name: 'test-app', slug: 'test-app', android: { manifest: { contents: ` ` } } }; const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle different log levels', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const logLevels = [0, 1, 2, 3, 4, 5, 6]; logLevels.forEach(logLevel => { const props = createMockProps({ logLevel }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); }); it('should handle different notification colors', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFFFFF', '#000000']; colors.forEach(color => { const props = createMockProps({ notificationColor: color }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); }); it('should handle invalid log level', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps({ logLevel: 999 as any }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle invalid notification color', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps({ notificationColor: 'invalid-color' as any }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle file system errors', () => { const fs = require('fs'); fs.existsSync.mockImplementation(() => { throw new Error('File system error'); }); const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps({ notificationIconFilePath: './assets/icon.png' }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle glob errors', () => { const glob = require('glob'); glob.sync.mockImplementation(() => { throw new Error('Glob error'); }); const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle getMainActivityAsync errors', async () => { const { getMainActivityAsync } = require('@expo/config-plugins/build/android/Paths'); getMainActivityAsync.mockRejectedValue(new Error('MainActivity error')); const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps(); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); }); describe('Plugin composition and execution', () => { it('should compose multiple plugins correctly', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = createMockProps({ logLevel: 2, notificationIconFilePath: './assets/icon.png', notificationColor: '#FF0000' }); const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle empty props', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = {} as KlaviyoPluginAndroidProps; const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle null props', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = null as any; const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle undefined props', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const props = undefined as any; const result = withKlaviyoAndroid(config, props); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); it('should handle all props combinations', () => { const config = testSimpleIntegration(withKlaviyoAndroid); const testCases = [ { openTracking: true, logLevel: 1 }, { openTracking: false, logLevel: 2 }, { openTracking: true, logLevel: 3, notificationColor: '#FF0000' }, { openTracking: false, notificationIconFilePath: './icon.png' }, { logLevel: 0, notificationColor: '#00FF00', notificationIconFilePath: './icon.png' }, ]; testCases.forEach((props) => { const result = withKlaviyoAndroid(config, props as KlaviyoPluginAndroidProps); expect(result).toBeDefined(); expect(typeof result).toBe('function'); }); }); }); });