diff --git a/src/__tests__/testUtils/mockSplitSdk.ts b/src/__tests__/testUtils/mockSplitSdk.ts
index 161630f..4038f30 100644
--- a/src/__tests__/testUtils/mockSplitSdk.ts
+++ b/src/__tests__/testUtils/mockSplitSdk.ts
@@ -54,6 +54,9 @@ function mockClient(key: SplitIO.SplitKey, trafficType?: string) {
const getTreatmentsWithConfig: jest.Mock = jest.fn(() => {
return 'getTreatmentsWithConfig';
});
+ const getTreatmentWithConfig: jest.Mock = jest.fn(() => {
+ return 'getTreatmentWithConfig';
+ });
const ready: jest.Mock = jest.fn(() => {
return new Promise((res, rej) => {
if (__isReady__) res();
@@ -90,6 +93,7 @@ function mockClient(key: SplitIO.SplitKey, trafficType?: string) {
return Object.assign(Object.create(__emitter__), {
getTreatmentsWithConfig,
+ getTreatmentWithConfig,
track,
ready,
destroy,
diff --git a/src/__tests__/useTreatment.test.tsx b/src/__tests__/useTreatment.test.tsx
new file mode 100644
index 0000000..4b4890f
--- /dev/null
+++ b/src/__tests__/useTreatment.test.tsx
@@ -0,0 +1,125 @@
+import React from 'react';
+import { mount } from 'enzyme';
+
+/** Mocks */
+import { mockSdk } from './testUtils/mockSplitSdk';
+jest.mock('@splitsoftware/splitio', () => {
+ return { SplitFactory: mockSdk() };
+});
+import { SplitFactory as SplitSdk } from '@splitsoftware/splitio';
+import { sdkBrowser } from './testUtils/sdkConfigs';
+jest.mock('../constants', () => {
+ const actual = jest.requireActual('../constants');
+ return {
+ ...actual,
+ getControlTreatmentsWithConfig: jest.fn(actual.getControlTreatmentsWithConfig),
+ };
+});
+import { getControlTreatmentsWithConfig } from '../constants';
+const logSpy = jest.spyOn(console, 'log');
+
+/** Test target */
+import SplitFactory from '../SplitFactory';
+import SplitClient from '../SplitClient';
+import useTreatment from '../useTreatment';
+
+describe('useTreatment', () => {
+
+ const splitName = 'split1';
+ const attributes = { att1: 'att1' };
+
+ test('returns the Treatment from the client at Split context updated by SplitFactory.', () => {
+ const outerFactory = SplitSdk(sdkBrowser);
+ let treatment;
+
+ mount(
+ {
+ React.createElement(() => {
+ treatment = useTreatment(splitName, attributes);
+ return null;
+ })},
+ );
+ const getTreatmentWithConfig: jest.Mock = (outerFactory.client() as any).getTreatmentWithConfig;
+ expect(getTreatmentWithConfig).toBeCalledWith(splitName, attributes);
+ expect(getTreatmentWithConfig).toHaveReturnedWith(treatment);
+ });
+
+ test('returns the Treatment from the client at Split context updated by SplitClient.', () => {
+ const outerFactory = SplitSdk(sdkBrowser);
+ let treatment;
+
+ mount(
+
+ {
+ React.createElement(() => {
+ treatment = useTreatment(splitName, attributes);
+ return null;
+ })}
+
+ ,
+ );
+ const getTreatmentWithConfig: jest.Mock = (outerFactory.client('user2') as any).getTreatmentWithConfig;
+ expect(getTreatmentWithConfig).toBeCalledWith(splitName, attributes);
+ expect(getTreatmentWithConfig).toHaveReturnedWith(treatment);
+ });
+
+ test('returns the Treatment from a new client given a splitKey.', () => {
+ const outerFactory = SplitSdk(sdkBrowser);
+ let treatment;
+
+ mount(
+ {
+ React.createElement(() => {
+ treatment = useTreatment(splitName, attributes, 'user2');
+ return null;
+ })}
+ ,
+ );
+ const getTreatmentWithConfig: jest.Mock = (outerFactory.client('user2') as any).getTreatmentWithConfig;
+ expect(getTreatmentWithConfig).toBeCalledWith(splitName, attributes);
+ expect(getTreatmentWithConfig).toHaveReturnedWith(treatment);
+ });
+
+ test('returns Control Treatment if invoked outside Split context.', () => {
+ let treatment;
+
+ mount(
+ React.createElement(
+ () => {
+ treatment = useTreatment(splitName, attributes);
+ return null;
+ }),
+ );
+ expect(getControlTreatmentsWithConfig).toBeCalledWith([splitName]);
+ expect(getControlTreatmentsWithConfig).toHaveReturnedWith({
+ split1: {
+ config: null,
+ treatment: 'control'
+ }
+ });
+ });
+
+ /**
+ * Input validation. Passing invalid split names or attributes while the Sdk
+ * is not ready doesn't emit errors, and logs meaningful messages instead.
+ */
+ test('Input validation: invalid "name" and "attributes" params in useTreatment.', (done) => {
+ mount(
+ React.createElement(
+ () => {
+ // @ts-ignore
+ let treatment = useTreatment('split1');
+ expect(treatment).toEqual({});
+ // @ts-ignore
+ treatment = useTreatment([true]);
+ expect(treatment).toEqual({});
+ return null;
+ }),
+ );
+ expect(logSpy).toBeCalledWith('[ERROR] split names must be a non-empty array.');
+ expect(logSpy).toBeCalledWith('[ERROR] you passed an invalid split name, split name must be a non-empty string.');
+
+ done();
+ });
+
+});
diff --git a/src/useTreatment.ts b/src/useTreatment.ts
new file mode 100644
index 0000000..08d06f7
--- /dev/null
+++ b/src/useTreatment.ts
@@ -0,0 +1,20 @@
+import useClient from './useClient';
+import { getControlTreatmentsWithConfig, ERROR_UT_NO_USECONTEXT } from './constants';
+import { checkHooks } from './utils';
+
+/**
+ * 'useTreatment' is a custom hook that returns a treatment for a given split.
+ * It uses the 'useContext' hook to access the client from the Split context,
+ * and invokes the 'getTreatmentWithConfig' method.
+ *
+ * @return A TreatmentWithConfig instance, that might contain the control treatment if the client is not available or ready, or if split name does not exist.
+ * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#get-treatments-with-configurations}
+ */
+const useTreatment = (splitName: string, attributes?: SplitIO.Attributes, key?: SplitIO.SplitKey): SplitIO.TreatmentWithConfig => {
+ const client = checkHooks(ERROR_UT_NO_USECONTEXT) ? useClient(key) : null;
+ return client ?
+ client.getTreatmentWithConfig(splitName, attributes) :
+ getControlTreatmentsWithConfig([splitName])[0];
+};
+
+export default useTreatment;