import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Expose } from './expose'; describe('Expose', () => { let mockSDK: any; let expose: Expose; beforeEach(() => { mockSDK = { existingMethod() { return 'existing'; }, }; expose = new Expose(mockSDK); }); describe('expose()', () => { it('should add single method to SDK', () => { expose.expose({ track(event: string) { return `tracked: ${event}`; }, }); expect(mockSDK.track).toBeDefined(); expect(typeof mockSDK.track).toBe('function'); expect(mockSDK.track('test')).toBe('tracked: test'); }); it('should add multiple methods to SDK', () => { expose.expose({ track(event: string) { return `tracked: ${event}`; }, identify(userId: string) { return `identified: ${userId}`; }, page(name: string) { return `page: ${name}`; }, }); expect(mockSDK.track).toBeDefined(); expect(mockSDK.identify).toBeDefined(); expect(mockSDK.page).toBeDefined(); expect(mockSDK.track('event')).toBe('tracked: event'); expect(mockSDK.identify('123')).toBe('identified: 123'); expect(mockSDK.page('home')).toBe('page: home'); }); it('should preserve existing SDK methods', () => { expose.expose({ newMethod() { return 'new'; }, }); expect(mockSDK.existingMethod).toBeDefined(); expect(mockSDK.existingMethod()).toBe('existing'); expect(mockSDK.newMethod()).toBe('new'); }); it('should allow overwriting existing methods', () => { expose.expose({ existingMethod() { return 'overwritten'; }, }); expect(mockSDK.existingMethod()).toBe('overwritten'); }); it('should handle methods with multiple parameters', () => { expose.expose({ track(event: string, properties: any, options: any) { return { event, properties, options }; }, }); const result = mockSDK.track('click', { button: 'submit' }, { immediate: true }); expect(result).toEqual({ event: 'click', properties: { button: 'submit' }, options: { immediate: true }, }); }); it('should handle methods with no parameters', () => { expose.expose({ ping() { return 'pong'; }, }); expect(mockSDK.ping()).toBe('pong'); }); it('should handle methods with return values', () => { expose.expose({ getValue() { return 42; }, getObject() { return { key: 'value' }; }, getArray() { return [1, 2, 3]; }, }); expect(mockSDK.getValue()).toBe(42); expect(mockSDK.getObject()).toEqual({ key: 'value' }); expect(mockSDK.getArray()).toEqual([1, 2, 3]); }); it('should handle async methods', async () => { expose.expose({ async fetchData() { return await Promise.resolve('data'); }, }); const result = await mockSDK.fetchData(); expect(result).toBe('data'); }); it('should handle methods that access SDK context', () => { mockSDK.config = { timeout: 5000 }; expose.expose({ getTimeout() { return this.config.timeout; }, }); expect(mockSDK.getTimeout()).toBe(5000); }); it('should be callable multiple times', () => { expose.expose({ method1() { return '1'; }, }); expose.expose({ method2() { return '2'; }, }); expose.expose({ method3() { return '3'; }, }); expect(mockSDK.method1()).toBe('1'); expect(mockSDK.method2()).toBe('2'); expect(mockSDK.method3()).toBe('3'); }); it('should handle empty object', () => { const originalKeys = Object.keys(mockSDK); expose.expose({}); expect(Object.keys(mockSDK)).toEqual(originalKeys); }); it('should handle methods with same name from different expose calls', () => { expose.expose({ sharedMethod() { return 'first'; }, }); expose.expose({ sharedMethod() { return 'second'; }, }); // Last one wins expect(mockSDK.sharedMethod()).toBe('second'); }); }); describe('integration with real SDK-like object', () => { it('should work with class instance', () => { class MockSDK { name = 'test-sdk'; get(key: string) { return `getting ${key}`; } } const sdk = new MockSDK(); const expose = new Expose(sdk); expose.expose({ track(event: string) { return `${this.name}: ${event}`; }, }); expect(sdk.track('page_view')).toBe('test-sdk: page_view'); expect(sdk.get('config')).toBe('getting config'); }); it('should preserve method binding', () => { const sdk = { value: 42, getValue() { return this.value; }, }; const expose = new Expose(sdk); expose.expose({ getDouble() { return this.value * 2; }, }); expect(sdk.getValue()).toBe(42); expect(sdk.getDouble()).toBe(84); }); }); describe('edge cases', () => { it('should handle properties that are not functions', () => { expose.expose({ notAFunction: 'just a string', alsoNotAFunction: 42, stillNotAFunction: { nested: 'object' }, }); expect(mockSDK.notAFunction).toBe('just a string'); expect(mockSDK.alsoNotAFunction).toBe(42); expect(mockSDK.stillNotAFunction).toEqual({ nested: 'object' }); }); it('should handle null and undefined values', () => { expose.expose({ nullValue: null, undefinedValue: undefined, }); expect(mockSDK.nullValue).toBeNull(); expect(mockSDK.undefinedValue).toBeUndefined(); }); it('should handle methods with special names', () => { expose.expose({ constructor() { return 'not the real constructor'; }, toString() { return 'custom toString'; }, valueOf() { return 123; }, }); expect(mockSDK.constructor()).toBe('not the real constructor'); expect(mockSDK.toString()).toBe('custom toString'); expect(mockSDK.valueOf()).toBe(123); }); it('should handle methods that throw errors', () => { expose.expose({ throwError() { throw new Error('Intentional error'); }, }); expect(() => { mockSDK.throwError(); }).toThrow('Intentional error'); }); it('should handle getters and setters', () => { let value = 0; expose.expose({ get count() { return value; }, set count(val: number) { value = val; }, }); expect(mockSDK.count).toBe(0); mockSDK.count = 42; expect(mockSDK.count).toBe(42); }); }); });