@@ -137,7 +137,10 @@ it('useBoolVariation evaluates once when initialization completes', async () =>
137137 expect ( wrapper . text ( ) ) . toBe ( 'false' ) ;
138138 expect ( client . boolVariation ) . not . toHaveBeenCalled ( ) ;
139139
140+ // start() resolving to complete flips the client to ready and notifies context
141+ // subscribers; the composable re-evaluates via its onContextChange subscription.
140142 controls . emitInitStatus ( { status : 'complete' } ) ;
143+ controls . emitContextChange ( { kind : 'user' , key : 'context-key' } ) ;
141144 await nextTick ( ) ;
142145
143146 expect ( client . boolVariation ) . toHaveBeenCalledTimes ( 1 ) ;
@@ -158,7 +161,12 @@ it('useBoolVariation evaluates when initialization fails (client returns default
158161 mountUnderProvider ( client , Child ) ;
159162 expect ( client . boolVariation ) . not . toHaveBeenCalled ( ) ;
160163
164+ // A failed start() still resolves (failure is a resolved result, not a rejection), so the
165+ // base client notifies context subscribers exactly once. The composable must re-evaluate
166+ // exactly once via onContextChange, not a second time via any init-status subscription
167+ // (SDK-2640 double-eval-on-failure guard).
161168 controls . emitInitStatus ( { status : 'failed' , error : new Error ( 'network error' ) } ) ;
169+ controls . emitContextChange ( { kind : 'user' , key : 'context-key' } ) ;
162170 await nextTick ( ) ;
163171
164172 expect ( client . boolVariation ) . toHaveBeenCalledTimes ( 1 ) ;
@@ -184,7 +192,7 @@ it('useBoolVariation re-evaluates when context changes after identify', async ()
184192 expect ( ( client . boolVariation as jest . Mock ) . mock . calls . length ) . toBe ( callsBefore + 1 ) ;
185193} ) ;
186194
187- it ( 'useBoolVariation evaluates only once per identify (no duplicate analytics impression) ' , async ( ) => {
195+ it ( 'useBoolVariation evaluates twice when a single identify changes BOTH context and the flag value ' , async ( ) => {
188196 const { client, controls } = makeMockClient ( ) ;
189197 ( client . boolVariation as jest . Mock ) . mockReturnValue ( true ) ;
190198
@@ -198,13 +206,54 @@ it('useBoolVariation evaluates only once per identify (no duplicate analytics im
198206 mountUnderProvider ( client , Child ) ;
199207 ( client . boolVariation as jest . Mock ) . mockClear ( ) ;
200208
201- // A single identify() in the base SDK emits change:<key> synchronously AND notifies
202- // context subscribers, so model both for one identify.
209+ // A single identify() that changes both the context and the watched flag's value fires
210+ // change:<key> (flag-value trigger) and notifies context subscribers (context trigger).
211+ // Each fires update() once -> two evaluations. Accepted react-parity cost (SDK-2194): the
212+ // flagChanged counter that used to batch these into one flush is gone.
203213 controls . emitChange ( 'my-flag' ) ;
204214 controls . emitContextChange ( { kind : 'user' , key : 'new-user' } ) ;
205215 await nextTick ( ) ;
206216
207- // One identify must produce exactly one evaluation -> one analytics impression.
217+ expect ( client . boolVariation as jest . Mock ) . toHaveBeenCalledTimes ( 2 ) ;
218+ } ) ;
219+
220+ it ( 'useBoolVariation evaluates exactly once when ONLY the flag value changes' , async ( ) => {
221+ const { client, controls } = makeMockClient ( ) ;
222+ ( client . boolVariation as jest . Mock ) . mockReturnValue ( true ) ;
223+
224+ const Child = defineComponent ( {
225+ setup ( ) {
226+ const flag = useBoolVariation ( 'my-flag' , false ) ;
227+ return ( ) => h ( 'div' , String ( flag . value ) ) ;
228+ } ,
229+ } ) ;
230+
231+ mountUnderProvider ( client , Child ) ;
232+ ( client . boolVariation as jest . Mock ) . mockClear ( ) ;
233+
234+ controls . emitChange ( 'my-flag' ) ;
235+ await nextTick ( ) ;
236+
237+ expect ( client . boolVariation as jest . Mock ) . toHaveBeenCalledTimes ( 1 ) ;
238+ } ) ;
239+
240+ it ( 'useBoolVariation evaluates exactly once when ONLY the context changes' , async ( ) => {
241+ const { client, controls } = makeMockClient ( ) ;
242+ ( client . boolVariation as jest . Mock ) . mockReturnValue ( true ) ;
243+
244+ const Child = defineComponent ( {
245+ setup ( ) {
246+ const flag = useBoolVariation ( 'my-flag' , false ) ;
247+ return ( ) => h ( 'div' , String ( flag . value ) ) ;
248+ } ,
249+ } ) ;
250+
251+ mountUnderProvider ( client , Child ) ;
252+ ( client . boolVariation as jest . Mock ) . mockClear ( ) ;
253+
254+ controls . emitContextChange ( { kind : 'user' , key : 'new-user' } ) ;
255+ await nextTick ( ) ;
256+
208257 expect ( client . boolVariation as jest . Mock ) . toHaveBeenCalledTimes ( 1 ) ;
209258} ) ;
210259
0 commit comments