Skip to content

Commit 441bf83

Browse files
[FSSDK-10777] segments support addition
1 parent c28e1c8 commit 441bf83

6 files changed

Lines changed: 155 additions & 43 deletions

File tree

src/Provider.spec.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ describe('OptimizelyProvider', () => {
5858

5959
it('should resolve user promise and set user in optimizely', async () => {
6060
render(<OptimizelyProvider optimizely={mockReactClient} user={Promise.resolve(user1)} />);
61-
await waitFor(() => expect(mockReactClient.setUser).toHaveBeenCalledWith(user1));
61+
await waitFor(() => expect(mockReactClient.setUser).toHaveBeenCalledWith(user1, undefined));
6262
});
6363

6464
it('should render successfully with user provided', () => {
6565
render(<OptimizelyProvider optimizely={mockReactClient} user={user1} />);
6666

67-
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1);
67+
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1, undefined);
6868
});
6969

7070
it('should throw error, if setUser throws error', () => {
@@ -76,50 +76,59 @@ describe('OptimizelyProvider', () => {
7676
it('should render successfully with userId provided', () => {
7777
render(<OptimizelyProvider optimizely={mockReactClient} userId={user1.id} />);
7878

79-
expect(mockReactClient.setUser).toHaveBeenCalledWith({
80-
id: user1.id,
81-
attributes: {},
82-
});
79+
expect(mockReactClient.setUser).toHaveBeenCalledWith(
80+
{
81+
id: user1.id,
82+
attributes: {},
83+
},
84+
undefined
85+
);
8386
});
8487

8588
it('should render successfully without user or userId provided', () => {
8689
// @ts-ignore
8790
mockReactClient.user = undefined;
8891
render(<OptimizelyProvider optimizely={mockReactClient} />);
8992

90-
expect(mockReactClient.setUser).toHaveBeenCalledWith(DefaultUser);
93+
expect(mockReactClient.setUser).toHaveBeenCalledWith(DefaultUser, undefined);
9194
});
9295

9396
it('should render successfully with user id & attributes provided', () => {
9497
render(<OptimizelyProvider optimizely={mockReactClient} user={user1} />);
9598

96-
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1);
99+
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1, undefined);
97100
});
98101

99102
it('should succeed just userAttributes provided', () => {
100103
// @ts-ignore
101104
mockReactClient.user = undefined;
102105
render(<OptimizelyProvider optimizely={mockReactClient} userAttributes={{ attr1: 'value1' }} />);
103106

104-
expect(mockReactClient.setUser).toHaveBeenCalledWith({
105-
id: DefaultUser.id,
106-
attributes: { attr1: 'value1' },
107-
});
107+
expect(mockReactClient.setUser).toHaveBeenCalledWith(
108+
{
109+
id: DefaultUser.id,
110+
attributes: { attr1: 'value1' },
111+
},
112+
undefined
113+
);
108114
});
109115

110116
it('should succeed with the initial user available in client', () => {
111117
render(<OptimizelyProvider optimizely={mockReactClient} />);
112118

113-
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1);
119+
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1, undefined);
114120
});
115121

116122
it('should succeed with the initial user id and newly passed attributes', () => {
117123
render(<OptimizelyProvider optimizely={mockReactClient} userAttributes={{ attr1: 'value2' }} />);
118124

119-
expect(mockReactClient.setUser).toHaveBeenCalledWith({
120-
id: user1.id,
121-
attributes: { attr1: 'value2' },
122-
});
125+
expect(mockReactClient.setUser).toHaveBeenCalledWith(
126+
{
127+
id: user1.id,
128+
attributes: { attr1: 'value2' },
129+
},
130+
undefined
131+
);
123132
});
124133

125134
it('should not update when isServerSide is true', () => {
@@ -142,7 +151,7 @@ describe('OptimizelyProvider', () => {
142151
// Change props to trigger componentDidUpdate
143152
rerender(<OptimizelyProvider optimizely={mockReactClient} user={user1} />);
144153

145-
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1);
154+
expect(mockReactClient.setUser).toHaveBeenCalledWith(user1, undefined);
146155
});
147156

148157
it('should update user if users are not equal', () => {
@@ -153,7 +162,7 @@ describe('OptimizelyProvider', () => {
153162
// Change props to a different user to trigger componentDidUpdate
154163
rerender(<OptimizelyProvider optimizely={mockReactClient} user={user2} />);
155164

156-
expect(mockReactClient.setUser).toHaveBeenCalledWith(user2);
165+
expect(mockReactClient.setUser).toHaveBeenCalledWith(user2, undefined);
157166
});
158167

159168
it('should not update user if users are equal', () => {

src/Provider.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ interface OptimizelyProviderProps {
3030
user?: Promise<UserInfo> | UserInfo;
3131
userId?: string;
3232
userAttributes?: UserAttributes;
33+
qualifiedSegments?: string[];
3334
children?: React.ReactNode;
3435
}
3536

@@ -46,7 +47,7 @@ export class OptimizelyProvider extends React.Component<OptimizelyProviderProps,
4647
}
4748

4849
async setUserInOptimizely(): Promise<void> {
49-
const { optimizely, userId, userAttributes, user } = this.props;
50+
const { optimizely, userId, userAttributes, user, qualifiedSegments } = this.props;
5051

5152
if (!optimizely) {
5253
logger.error('OptimizelyProvider must be passed an instance of the Optimizely SDK client');
@@ -58,7 +59,7 @@ export class OptimizelyProvider extends React.Component<OptimizelyProviderProps,
5859
if (user) {
5960
if ('then' in user) {
6061
user.then((res: UserInfo) => {
61-
optimizely.setUser(res);
62+
optimizely.setUser(res, qualifiedSegments);
6263
});
6364
} else {
6465
finalUser = {
@@ -89,7 +90,7 @@ export class OptimizelyProvider extends React.Component<OptimizelyProviderProps,
8990
// if user is a promise, setUser occurs in the then block above
9091
if (finalUser) {
9192
try {
92-
await optimizely.setUser(finalUser);
93+
await optimizely.setUser(finalUser, qualifiedSegments);
9394
} catch {
9495
logger.error('Error while trying to set user.');
9596
}
@@ -105,7 +106,7 @@ export class OptimizelyProvider extends React.Component<OptimizelyProviderProps,
105106
if (this.props.user && 'id' in this.props.user) {
106107
if (!optimizely.user.id) {
107108
// no user is set in optimizely, update
108-
optimizely.setUser(this.props.user);
109+
optimizely.setUser(this.props.user, this.props.qualifiedSegments);
109110
} else if (
110111
// if the users aren't equal update
111112
!areUsersEqual(
@@ -120,7 +121,7 @@ export class OptimizelyProvider extends React.Component<OptimizelyProviderProps,
120121
}
121122
)
122123
) {
123-
optimizely.setUser(this.props.user);
124+
optimizely.setUser(this.props.user, this.props.qualifiedSegments);
124125
}
125126
}
126127
}

src/client.spec.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,74 @@ describe('ReactSDKClient', () => {
386386

387387
expect(instance.fetchQualifiedSegments).toHaveBeenCalledTimes(3);
388388
});
389+
390+
it('should set qualifiedSegments synchronously on userContext before fetchQualifiedSegments is called', async () => {
391+
jest.spyOn(mockInnerClient, 'isOdpIntegrated').mockReturnValue(true);
392+
jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId);
393+
jest.spyOn(mockOptimizelyUserContext, 'getAttributes').mockReturnValue(userAttributes);
394+
395+
const segments = ['segment1', 'segment2'];
396+
let segmentsAtFetchTime: string[] | null | undefined;
397+
398+
instance = createInstance(config);
399+
jest.spyOn(instance, 'fetchQualifiedSegments').mockImplementation(async () => {
400+
// Capture qualifiedSegments at the time fetchQualifiedSegments is called
401+
segmentsAtFetchTime = mockOptimizelyUserContext.qualifiedSegments;
402+
return true;
403+
});
404+
405+
await instance.setUser({ id: userId, attributes: userAttributes }, segments);
406+
407+
// Verify segments were already set on the userContext before fetchQualifiedSegments ran
408+
expect(segmentsAtFetchTime).toEqual(segments);
409+
});
410+
411+
it('should not set qualifiedSegments when ODP is explicitly disabled', async () => {
412+
jest.spyOn(mockInnerClient, 'isOdpIntegrated').mockReturnValue(true);
413+
jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId);
414+
jest.spyOn(mockOptimizelyUserContext, 'getAttributes').mockReturnValue(userAttributes);
415+
416+
const segments = ['segment1', 'segment2'];
417+
418+
instance = createInstance({
419+
...config,
420+
odpOptions: { disabled: true },
421+
});
422+
jest.spyOn(instance, 'fetchQualifiedSegments').mockResolvedValue(true);
423+
424+
await instance.setUser({ id: userId, attributes: userAttributes }, segments);
425+
426+
expect(mockOptimizelyUserContext.qualifiedSegments).toBeUndefined();
427+
});
428+
429+
it('should not set qualifiedSegments when ODP is not integrated', async () => {
430+
jest.spyOn(mockInnerClient, 'isOdpIntegrated').mockReturnValue(false);
431+
jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(userId);
432+
jest.spyOn(mockOptimizelyUserContext, 'getAttributes').mockReturnValue(userAttributes);
433+
434+
const segments = ['segment1', 'segment2'];
435+
436+
instance = createInstance(config);
437+
jest.spyOn(instance, 'fetchQualifiedSegments').mockResolvedValue(true);
438+
439+
await instance.setUser({ id: userId, attributes: userAttributes }, segments);
440+
441+
expect(mockOptimizelyUserContext.qualifiedSegments).toBeUndefined();
442+
});
443+
444+
it('should not set qualifiedSegments for anonymous/default users', async () => {
445+
jest.spyOn(mockInnerClient, 'isOdpIntegrated').mockReturnValue(true);
446+
jest.spyOn(mockOptimizelyUserContext, 'getUserId').mockReturnValue(validVuid);
447+
448+
const segments = ['segment1', 'segment2'];
449+
450+
instance = createInstance(config);
451+
jest.spyOn(instance, 'fetchQualifiedSegments').mockResolvedValue(true);
452+
453+
await instance.setUser(DefaultUser, segments);
454+
455+
expect(mockOptimizelyUserContext.qualifiedSegments).toBeUndefined();
456+
});
389457
});
390458

391459
describe('onUserUpdate', () => {

src/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export interface ReactSDKClient
5959
user: UserInfo;
6060
client: optimizely.Client | null;
6161
onReady(opts?: { timeout?: number }): Promise<any>;
62-
setUser(userInfo: UserInfo): Promise<void>;
62+
setUser(userInfo: UserInfo, qualifiedSegments?: string[]): Promise<void>;
6363
onUserUpdate(handler: OnUserUpdateHandler): DisposeFn;
6464
isReady(): boolean;
6565
getIsReadyPromiseFulfilled(): boolean;
@@ -381,7 +381,7 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
381381
return await this.userContext.fetchQualifiedSegments(options);
382382
}
383383

384-
public async setUser(userInfo: UserInfo): Promise<void> {
384+
public async setUser(userInfo: UserInfo, qualifiedSegments?: string[]): Promise<void> {
385385
// If user id is not present and ODP is explicitly off, user promise will be pending until setUser is called again with proper user id
386386
if (userInfo.id === null && this.odpExplicitlyOff) {
387387
return;
@@ -404,6 +404,9 @@ class OptimizelyReactSDKClient implements ReactSDKClient {
404404
// synchronous user context setting is required including for server side rendering (SSR)
405405
this.setCurrentUserContext(userInfo);
406406

407+
if (this.userContext && !this.odpExplicitlyOff && this._client?.isOdpIntegrated() && qualifiedSegments) {
408+
this.userContext.qualifiedSegments = qualifiedSegments;
409+
}
407410
// we need to wait for fetch qualified segments success for failure
408411
await this._client?.onReady();
409412
}

src/hooks.spec.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,25 @@ describe('hooks', () => {
10611061
await waitFor(() => expect(mockLog).toHaveBeenCalledWith(true));
10621062
});
10631063

1064+
it('should pass qualifiedSegments to setUser when provided via OptimizelyProvider', async () => {
1065+
const segments = ['segment1', 'segment2'];
1066+
decideMock.mockReturnValue({ ...defaultDecision, enabled: true });
1067+
1068+
render(
1069+
<OptimizelyProvider
1070+
optimizely={optimizelyMock}
1071+
user={{ id: 'testuser', attributes: {} }}
1072+
qualifiedSegments={segments}
1073+
>
1074+
<MyDecideComponent />
1075+
</OptimizelyProvider>
1076+
);
1077+
1078+
await waitFor(() => {
1079+
expect(optimizelyMock.setUser).toHaveBeenCalledWith({ id: 'testuser', attributes: {} }, segments);
1080+
});
1081+
});
1082+
10641083
it('should re-render after updating the override user ID argument', async () => {
10651084
decideMock.mockReturnValue({ ...defaultDecision });
10661085
const { rerender } = render(

src/withOptimizely.spec.tsx

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe('withOptimizely', () => {
7070
);
7171

7272
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
73-
expect(optimizelyClient.setUser).toHaveBeenCalledWith({ id: userId, attributes });
73+
expect(optimizelyClient.setUser).toHaveBeenCalledWith({ id: userId, attributes }, undefined);
7474
});
7575
});
7676

@@ -84,10 +84,13 @@ describe('withOptimizely', () => {
8484
);
8585

8686
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
87-
expect(optimizelyClient.setUser).toHaveBeenCalledWith({
88-
id: userId,
89-
attributes: {},
90-
});
87+
expect(optimizelyClient.setUser).toHaveBeenCalledWith(
88+
{
89+
id: userId,
90+
attributes: {},
91+
},
92+
undefined
93+
);
9194
});
9295
});
9396

@@ -101,10 +104,13 @@ describe('withOptimizely', () => {
101104
);
102105

103106
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
104-
expect(optimizelyClient.setUser).toHaveBeenCalledWith({
105-
id: userId,
106-
attributes: {},
107-
});
107+
expect(optimizelyClient.setUser).toHaveBeenCalledWith(
108+
{
109+
id: userId,
110+
attributes: {},
111+
},
112+
undefined
113+
);
108114
});
109115
});
110116

@@ -119,10 +125,13 @@ describe('withOptimizely', () => {
119125
);
120126

121127
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
122-
expect(optimizelyClient.setUser).toHaveBeenCalledWith({
123-
id: userId,
124-
attributes,
125-
});
128+
expect(optimizelyClient.setUser).toHaveBeenCalledWith(
129+
{
130+
id: userId,
131+
attributes,
132+
},
133+
undefined
134+
);
126135
});
127136
});
128137

@@ -143,10 +152,13 @@ describe('withOptimizely', () => {
143152
);
144153

145154
await waitFor(() => expect(optimizelyClient.setUser).toHaveBeenCalledTimes(1));
146-
expect(optimizelyClient.setUser).toHaveBeenCalledWith({
147-
id: userId,
148-
attributes,
149-
});
155+
expect(optimizelyClient.setUser).toHaveBeenCalledWith(
156+
{
157+
id: userId,
158+
attributes,
159+
},
160+
undefined
161+
);
150162
});
151163
});
152164

0 commit comments

Comments
 (0)