Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 72254db

Browse files
committed
Implement UserWrapper in ReactSDK
1 parent 3277434 commit 72254db

File tree

6 files changed

+155
-40
lines changed

6 files changed

+155
-40
lines changed

packages/js-web-sdk/packages/react-sdk/src/Experiment.spec.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/// <reference types="jest" />
12
import * as React from 'react'
23
import * as Enzyme from 'enzyme'
34
import * as Adapter from 'enzyme-adapter-react-16'
@@ -8,7 +9,7 @@ import { Experiment, OptimizelyExperiment } from './Experiment'
89
import { mount } from 'enzyme'
910
import { OptimizelyProvider } from './Provider'
1011
import { OptimizelySDKWrapper } from '@optimizely/js-web-sdk'
11-
import { OptimizelyVariation } from './Variation';
12+
import { OptimizelyVariation } from './Variation'
1213

1314
async function sleep(timeout = 0): Promise<{}> {
1415
return new Promise(resolve => {
@@ -46,7 +47,12 @@ describe('<OptimizelyExperiment>', () => {
4647
} as unknown) as OptimizelySDKWrapper
4748

4849
const component = mount(
49-
<OptimizelyProvider optimizely={optimizelyMock} timeout={100}>
50+
<OptimizelyProvider
51+
optimizely={optimizelyMock}
52+
timeout={100}
53+
userId="jordan"
54+
userAttributes={{ plan_type: 'bronze' }}
55+
>
5056
<OptimizelyExperiment experiment="experiment1">
5157
{variation => variation}
5258
</OptimizelyExperiment>
@@ -60,7 +66,9 @@ describe('<OptimizelyExperiment>', () => {
6066

6167
await sleep()
6268

63-
expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1')
69+
expect(optimizelyMock.activate).toHaveBeenCalledWith('experiment1', 'jordan', {
70+
plan_type: 'bronze',
71+
})
6472
expect(component.text()).toBe(variationKey)
6573
})
6674

@@ -80,17 +88,15 @@ describe('<OptimizelyExperiment>', () => {
8088
} as unknown) as OptimizelySDKWrapper
8189

8290
const component = mount(
83-
<OptimizelyProvider optimizely={optimizelyMock}>
91+
<OptimizelyProvider optimizely={optimizelyMock} userId="jordan">
8492
<OptimizelyExperiment experiment="experiment1">
8593
<OptimizelyVariation variation="otherVariation">
8694
other variation
8795
</OptimizelyVariation>
8896
<OptimizelyVariation variation="variationResult">
8997
correct variation
9098
</OptimizelyVariation>
91-
<OptimizelyVariation default>
92-
default variation
93-
</OptimizelyVariation>
99+
<OptimizelyVariation default>default variation</OptimizelyVariation>
94100
</OptimizelyExperiment>
95101
</OptimizelyProvider>,
96102
)
@@ -120,14 +126,12 @@ describe('<OptimizelyExperiment>', () => {
120126
} as unknown) as OptimizelySDKWrapper
121127

122128
const component = mount(
123-
<OptimizelyProvider optimizely={optimizelyMock}>
129+
<OptimizelyProvider optimizely={optimizelyMock} userId="jordan">
124130
<OptimizelyExperiment experiment="experiment1">
125131
<OptimizelyVariation variation="otherVariation">
126132
other variation
127133
</OptimizelyVariation>
128-
<OptimizelyVariation default>
129-
default variation
130-
</OptimizelyVariation>
134+
<OptimizelyVariation default>default variation</OptimizelyVariation>
131135
</OptimizelyExperiment>
132136
</OptimizelyProvider>,
133137
)
@@ -157,7 +161,7 @@ describe('<OptimizelyExperiment>', () => {
157161
} as unknown) as OptimizelySDKWrapper
158162

159163
const component = mount(
160-
<OptimizelyProvider optimizely={optimizelyMock}>
164+
<OptimizelyProvider optimizely={optimizelyMock} userId="jordan">
161165
<OptimizelyExperiment experiment="experiment1">
162166
<OptimizelyVariation variation="otherVariation">
163167
other variation

packages/js-web-sdk/packages/react-sdk/src/Feature.spec.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,31 @@ describe('<OptimizelyFeature>', () => {
4848
} as unknown) as OptimizelySDKWrapper
4949

5050
const component = mount(
51-
<OptimizelyProvider optimizely={optimizelyMock}>
51+
<OptimizelyProvider optimizely={optimizelyMock} userId="jordan">
5252
<OptimizelyFeature feature="feature1">
5353
{(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`}
5454
</OptimizelyFeature>
5555
</OptimizelyProvider>,
5656
)
5757

58-
expect(optimizelyMock.onReady).toHaveBeenCalledWith({timeout: undefined})
58+
expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: undefined })
5959

6060
// while it's waiting for onReady()
6161
expect(component.text()).toBe(null)
6262
resolver.resolve()
6363

6464
await sleep()
6565

66-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1')
67-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1')
66+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith(
67+
'feature1',
68+
'jordan',
69+
{},
70+
)
71+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith(
72+
'feature1',
73+
'jordan',
74+
{},
75+
)
6876
expect(component.text()).toBe('true|bar')
6977
})
7078

@@ -88,23 +96,34 @@ describe('<OptimizelyFeature>', () => {
8896
} as unknown) as OptimizelySDKWrapper
8997

9098
const component = mount(
91-
<OptimizelyProvider optimizely={optimizelyMock} timeout={200}>
99+
<OptimizelyProvider
100+
optimizely={optimizelyMock}
101+
timeout={200}
102+
userId="jordan"
103+
userAttributes={{ plan_type: 'bronze' }}
104+
>
92105
<OptimizelyFeature feature="feature1">
93106
{(isEnabled, variables) => `${isEnabled ? 'true' : 'false'}|${variables.foo}`}
94107
</OptimizelyFeature>
95108
</OptimizelyProvider>,
96109
)
97110

98-
expect(optimizelyMock.onReady).toHaveBeenCalledWith({timeout: 200})
111+
expect(optimizelyMock.onReady).toHaveBeenCalledWith({ timeout: 200 })
99112

100113
// while it's waiting for onReady()
101114
expect(component.text()).toBe(null)
102115
resolver.resolve()
103116

104117
await sleep()
105118

106-
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1')
107-
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith('feature1')
119+
expect(optimizelyMock.isFeatureEnabled).toHaveBeenCalledWith('feature1', 'jordan', {
120+
plan_type: 'bronze',
121+
})
122+
expect(optimizelyMock.getFeatureVariables).toHaveBeenCalledWith(
123+
'feature1',
124+
'jordan',
125+
{ plan_type: 'bronze' },
126+
)
108127
expect(component.text()).toBe('true|bar')
109128
})
110129
})

packages/js-web-sdk/packages/react-sdk/src/Provider.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import * as React from 'react'
22

33
import { OptimizelyContextProvider } from './Context'
44
import { OptimizelySDKWrapper } from '@optimizely/js-web-sdk'
5+
import { UserAttributes, createUserWrapper, UserWrappedOptimizelySDK } from './createUserWrapper';
56

67
interface OptimizelyProviderProps {
78
optimizely: OptimizelySDKWrapper
9+
userId: string
810
timeout?: number
11+
userAttributes?: UserAttributes
912
}
1013

1114
interface OptimizelyProviderState {
@@ -17,23 +20,27 @@ export class OptimizelyProvider extends React.Component<
1720
OptimizelyProviderProps,
1821
OptimizelyProviderState
1922
> {
20-
sdkWrapper: OptimizelySDKWrapper
23+
sdkWrapper: UserWrappedOptimizelySDK
2124

2225
constructor(props: OptimizelyProviderProps) {
2326
super(props)
2427

25-
const { timeout, optimizely } = props
26-
this.sdkWrapper = optimizely
28+
const { optimizely, userId, userAttributes } = props
29+
console.log('creating wrapper', userId, userAttributes)
30+
this.sdkWrapper = createUserWrapper({
31+
instance: optimizely,
32+
userId,
33+
userAttributes,
34+
})
2735
}
2836

2937
render() {
3038
const { children, timeout } = this.props
3139
const value = {
3240
optimizely: this.sdkWrapper,
41+
timeout,
3342
}
34-
if (timeout !== undefined) {
35-
value['timeout'] = timeout
36-
}
43+
3744
return (
3845
<OptimizelyContextProvider value={value}>
3946
{children}

packages/js-web-sdk/packages/react-sdk/src/createUserWrapper.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,71 @@
1-
import { OptimizelySDKWrapper } from '@optimizely/js-web-sdk'
1+
import { OptimizelySDKWrapper, VariableValuesObject } from '@optimizely/js-web-sdk'
22

3-
type UserAttributes = { [attribute: string]: any }
3+
export type UserAttributes = { [attribute: string]: any }
44

55
type EventTags = { [tagKey: string]: boolean | number | string }
66

7+
export interface UserWrappedOptimizelySDK extends OptimizelySDKWrapper {
8+
activate(
9+
experimentKey: string,
10+
overrideUserId?: string,
11+
overrideAttributes?: UserAttributes,
12+
): string | null
13+
14+
getVariation(
15+
experimentKey: string,
16+
overrideUserId?: string,
17+
overrideAttributes?: UserAttributes,
18+
): string | null
19+
20+
getFeatureVariables(
21+
featureKey: string,
22+
overrideUserId?: string,
23+
overrideAttributes?: UserAttributes,
24+
): VariableValuesObject
25+
26+
getFeatureVariableString(
27+
featureKey: string,
28+
variableKey: string,
29+
overrideUserId?: string,
30+
overrideAttributes?: UserAttributes,
31+
): string | null
32+
33+
getFeatureVariableInteger(
34+
featureKey: string,
35+
variableKey: string,
36+
overrideUserId?: string,
37+
overrideAttributes?: UserAttributes,
38+
): number | null
39+
40+
getFeatureVariableBoolean(
41+
featureKey: string,
42+
variableKey: string,
43+
overrideUserId?: string,
44+
overrideAttributes?: UserAttributes,
45+
): boolean | null
46+
47+
getFeatureVariableDouble(
48+
featureKey: string,
49+
variableKey: string,
50+
overrideUserId?: string,
51+
overrideAttributes?: UserAttributes,
52+
): number | null
53+
54+
isFeatureEnabled(
55+
featureKey: string,
56+
overrideUserId?: string,
57+
overrideAttributes?: UserAttributes,
58+
): boolean
59+
60+
track(
61+
eventKey: string,
62+
overrideUserId?: string | EventTags,
63+
overrideAttributes?: UserAttributes,
64+
eventTags?: EventTags,
65+
): void
66+
}
67+
68+
769
/**
870
* Wrapper to memoize the userId / userAttributes around an OptimizelySDKWrapper instance
971
*
@@ -26,7 +88,7 @@ export function createUserWrapper({
2688
instance: OptimizelySDKWrapper
2789
userId: string
2890
userAttributes?: UserAttributes
29-
}) {
91+
}): UserWrappedOptimizelySDK {
3092
function getUserIdAndAttributes(
3193
overrideUserId?: string,
3294
overrideAttributes?: UserAttributes,
@@ -155,5 +217,5 @@ export function createUserWrapper({
155217

156218
return instance.track(eventKey, userId, attributes, eventTags)
157219
},
158-
}
220+
} as UserWrappedOptimizelySDK
159221
}

packages/js-web-sdk/packages/react-sdk/src/withOptimizely.spec.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
jest.mock('./createUserWrapper', () => ({
2+
__esModule: true,
3+
createUserWrapper: jest.fn(),
4+
}))
5+
16
import * as React from 'react'
27
import * as Enzyme from 'enzyme'
38
import * as Adapter from 'enzyme-adapter-react-16'
@@ -6,7 +11,8 @@ Enzyme.configure({ adapter: new Adapter() })
611
import { mount } from 'enzyme'
712
import { OptimizelyProvider } from './Provider'
813
import { OptimizelySDKWrapper } from '@optimizely/js-web-sdk'
9-
import { withOptimizely } from './withOptimizely';
14+
import { withOptimizely } from './withOptimizely'
15+
import { UserWrappedOptimizelySDK, createUserWrapper } from './createUserWrapper'
1016

1117
async function sleep(timeout = 0): Promise<{}> {
1218
return new Promise(resolve => {
@@ -17,11 +23,11 @@ async function sleep(timeout = 0): Promise<{}> {
1723
}
1824

1925
type TestProps = {
20-
optimizely: OptimizelySDKWrapper,
21-
optimizelyReadyTimeout: number | undefined,
26+
optimizely: UserWrappedOptimizelySDK
27+
optimizelyReadyTimeout: number | undefined
2228
}
2329

24-
class InnerComponent extends React.Component<TestProps,any> {
30+
class InnerComponent extends React.Component<TestProps, any> {
2531
constructor(props: TestProps) {
2632
super(props)
2733
}
@@ -34,18 +40,35 @@ class InnerComponent extends React.Component<TestProps,any> {
3440
const WrapperComponent = withOptimizely(InnerComponent)
3541

3642
describe('withOptimizely', () => {
43+
let wrappedOptimizely: UserWrappedOptimizelySDK
44+
beforeEach(() => {
45+
wrappedOptimizely = {} as UserWrappedOptimizelySDK
46+
;(createUserWrapper as jest.Mock).mockReturnValue(wrappedOptimizely)
47+
})
48+
3749
it('should inject optimizely and optimizelyReadyTiemout from <OptimizelyProvider>', async () => {
38-
const optimizelyMock = {} as unknown as OptimizelySDKWrapper
50+
const optimizelyMock = ({} as unknown) as OptimizelySDKWrapper
3951

4052
const component = mount(
41-
<OptimizelyProvider optimizely={optimizelyMock} timeout={200}>
53+
<OptimizelyProvider
54+
optimizely={optimizelyMock}
55+
timeout={200}
56+
userId="jordan"
57+
userAttributes={{ plan_type: 'bronze' }}
58+
>
4259
<WrapperComponent />
4360
</OptimizelyProvider>,
4461
)
4562

63+
expect(createUserWrapper).toHaveBeenCalledWith({
64+
instance: optimizelyMock,
65+
userId: 'jordan',
66+
userAttributes: { plan_type: 'bronze' },
67+
})
68+
4669
const innerComponent = component.find(InnerComponent)
4770
expect(innerComponent.props()).toEqual({
48-
optimizely: optimizelyMock,
71+
optimizely: wrappedOptimizely,
4972
optimizelyReadyTimeout: 200,
5073
})
5174
})

packages/js-web-sdk/packages/react-sdk/src/withOptimizely.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as React from 'react'
22
import { Subtract } from 'utility-types'
3-
import { OptimizelySDKWrapper } from '@optimizely/js-web-sdk'
43

54
import { OptimizelyContextConsumer } from './Context'
5+
import { UserWrappedOptimizelySDK } from './createUserWrapper';
66

77
export interface WithOptimizelyProps {
8-
optimizely: OptimizelySDKWrapper | null
8+
optimizely: UserWrappedOptimizelySDK | null
99
optimizelyReadyTimeout: number | undefined
1010
}
1111

@@ -17,7 +17,7 @@ export function withOptimizely<P extends WithOptimizelyProps>(
1717
return (
1818
<OptimizelyContextConsumer>
1919
{(value: {
20-
optimizely: OptimizelySDKWrapper
20+
optimizely: UserWrappedOptimizelySDK
2121
timeout: number | undefined
2222
}) => (
2323
<Component

0 commit comments

Comments
 (0)