Skip to content
64 changes: 58 additions & 6 deletions src/client/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
import { vi, describe, it, expect, beforeEach, type MockedFunction } from 'vitest';
import { createInstance as jsCreateInstance } from '@optimizely/optimizely-sdk';
import type { Config } from '@optimizely/optimizely-sdk';
import { createInstance, CLIENT_ENGINE, CLIENT_VERSION } from './createInstance';
import { createInstance, CLIENT_ENGINE, CLIENT_VERSION, REACT_CLIENT_META } from './createInstance';
import type { ReactClientMeta } from './createInstance';

type ClientWithMeta = Record<symbol, ReactClientMeta>;

const mockJsClient = vi.hoisted(() => ({
onReady: vi.fn().mockResolvedValue(undefined),
createUserContext: vi.fn(),
close: vi.fn(),
}));

vi.mock('@optimizely/optimizely-sdk', () => ({
createInstance: vi.fn().mockReturnValue({
onReady: vi.fn().mockResolvedValue(undefined),
createUserContext: vi.fn(),
close: vi.fn(),
}),
createInstance: vi.fn().mockReturnValue(mockJsClient),
}));

const mockedJsCreateInstance = jsCreateInstance as MockedFunction<typeof jsCreateInstance>;
Expand All @@ -51,4 +56,51 @@ describe('createInstance', () => {
);
});
});

describe('prototype delegation', () => {
it('should return an object that delegates to the JS SDK client', () => {
const client = createInstance(createTestConfig());
// Methods from the JS client should be accessible via prototype chain
expect(client.onReady).toBe(mockJsClient.onReady);
expect(client.createUserContext).toBe(mockJsClient.createUserContext);
expect(client.close).toBe(mockJsClient.close);
});

it('should have the JS SDK client as its prototype', () => {
const client = createInstance(createTestConfig());
expect(Object.getPrototypeOf(client)).toBe(mockJsClient);
});
});

describe('REACT_CLIENT_META', () => {
it('should set hasOdpManager to false when odpManager is not provided', () => {
const client = createInstance(createTestConfig());
const meta = (client as unknown as ClientWithMeta)[REACT_CLIENT_META];
expect(meta.hasOdpManager).toBe(false);
});

it('should set hasOdpManager to true when odpManager is provided', () => {
const client = createInstance(createTestConfig({ odpManager: {} as Config['odpManager'] }));
const meta = (client as unknown as ClientWithMeta)[REACT_CLIENT_META];
expect(meta.hasOdpManager).toBe(true);
});

it('should set hasVuidManager to false when vuidManager is not provided', () => {
const client = createInstance(createTestConfig());
const meta = (client as unknown as ClientWithMeta)[REACT_CLIENT_META];
expect(meta.hasVuidManager).toBe(false);
});

it('should set hasVuidManager to true when vuidManager is provided', () => {
const client = createInstance(createTestConfig({ vuidManager: {} as Config['vuidManager'] }));
const meta = (client as unknown as ClientWithMeta)[REACT_CLIENT_META];
expect(meta.hasVuidManager).toBe(true);
});

it('should store meta on the react client, not on the prototype', () => {
const client = createInstance(createTestConfig());
expect(Object.prototype.hasOwnProperty.call(client, REACT_CLIENT_META)).toBe(true);
expect(Object.prototype.hasOwnProperty.call(mockJsClient, REACT_CLIENT_META)).toBe(false);
});
});
});
23 changes: 21 additions & 2 deletions src/client/createInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,35 @@ import type { Config, Client } from '@optimizely/optimizely-sdk';
export const CLIENT_ENGINE = 'react-sdk';
export const CLIENT_VERSION = '4.0.0';

export const REACT_CLIENT_META = Symbol('react-client-meta');

export interface ReactClientMeta {
hasOdpManager: boolean;
hasVuidManager: boolean;
}

/**
* Creates an Optimizely client instance for use with React SDK.
*
* Uses prototype delegation so the returned object inherits all methods
* from the JS SDK client while carrying React-specific metadata.
*
* @param config - Configuration object for the Optimizely client
* @returns An OptimizelyClient instance
* @returns An OptimizelyClient instance with React SDK metadata
*/
export function createInstance(config: Config): Client {
return jsCreateInstance({
const jsClient = jsCreateInstance({
...config,
clientEngine: CLIENT_ENGINE,
clientVersion: CLIENT_VERSION,
});

const reactClient = Object.create(jsClient);

reactClient[REACT_CLIENT_META] = {
hasOdpManager: !!config.odpManager,
hasVuidManager: !!config.vuidManager,
} satisfies ReactClientMeta;

return reactClient;
}
3 changes: 2 additions & 1 deletion src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* limitations under the License.
*/

export { createInstance } from './createInstance';
export { createInstance, REACT_CLIENT_META } from './createInstance';
export type { ReactClientMeta } from './createInstance';

export type * from '@optimizely/optimizely-sdk';

Expand Down
16 changes: 14 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,21 @@
* limitations under the License.
*/

// Client - re-export everything
export * from './client';
export {
createInstance,
createPollingProjectConfigManager,
createStaticProjectConfigManager,
createBatchEventProcessor,
createForwardingEventProcessor,
createOdpManager,
createVuidManager,
createErrorNotifier,
createLogger
} from './client/index';

export type * from "@optimizely/optimizely-sdk";

// Provider
// Todo: Remove OptimizelyContext export in future
export { OptimizelyProvider } from './provider/index';
export type { UserInfo, OptimizelyProviderProps } from './provider/index';
Loading