@@ -15,6 +15,14 @@ import optimisticBetsReducer, {
1515 clearBet ,
1616} from "../../store/optimisticBetsSlice" ;
1717import { useOptimisticBet } from "../../hooks/useOptimisticBet" ;
18+ import {
19+ REFERRAL_STORAGE_KEY ,
20+ generateReferralCode ,
21+ } from "../../lib/referral" ;
22+
23+ jest . mock ( "../../lib/stellar" , ( ) => ( {
24+ validateStellarAddress : jest . fn ( ( ) => true ) ,
25+ } ) ) ;
1826
1927// ── Helpers ──────────────────────────────────────────────────────────────────
2028
@@ -99,6 +107,7 @@ describe("optimisticBetsSlice", () => {
99107
100108function HookHarness ( { onResult } : { onResult : ( r : boolean ) => void } ) {
101109 const { submitBet, optimisticBets } = useOptimisticBet ( ) ;
110+ const walletAddress = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" ;
102111
103112 return (
104113 < div >
@@ -117,7 +126,7 @@ function HookHarness({ onResult }: { onResult: (r: boolean) => void }) {
117126 outcomeIndex : 0 ,
118127 outcomeName : "Yes" ,
119128 amount : 100 ,
120- walletAddress : "GTEST" ,
129+ walletAddress,
121130 } ,
122131 ( reason ) => onResult ( false )
123132 ) . then ( onResult )
@@ -142,7 +151,10 @@ function renderHook(onResult = jest.fn()) {
142151}
143152
144153describe ( "useOptimisticBet hook" , ( ) => {
145- beforeEach ( ( ) => jest . resetAllMocks ( ) ) ;
154+ beforeEach ( ( ) => {
155+ jest . resetAllMocks ( ) ;
156+ localStorage . clear ( ) ;
157+ } ) ;
146158
147159 it ( "optimistic add: bet appears immediately as pending" , async ( ) => {
148160 global . fetch = jest . fn ( ( ) => new Promise ( ( ) => { } ) ) as any ; // hang
@@ -216,7 +228,14 @@ describe("useOptimisticBet hook", () => {
216228 < button
217229 onClick = { ( ) =>
218230 submitBet (
219- { marketId : 2 , marketTitle : "Q" , outcomeIndex : 0 , outcomeName : "Yes" , amount : 10 , walletAddress : "G" } ,
231+ {
232+ marketId : 2 ,
233+ marketTitle : "Q" ,
234+ outcomeIndex : 0 ,
235+ outcomeName : "Yes" ,
236+ amount : 10 ,
237+ walletAddress : "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" ,
238+ } ,
220239 onError
221240 )
222241 }
@@ -245,8 +264,22 @@ describe("useOptimisticBet hook", () => {
245264 < div data-testid = "m1" > { market1Bets . length } </ div >
246265 < div data-testid = "m2" > { market2Bets . length } </ div >
247266 < button onClick = { ( ) => {
248- submitBet ( { marketId : 1 , marketTitle : "M1" , outcomeIndex : 0 , outcomeName : "Yes" , amount : 10 , walletAddress : "G" } ) ;
249- submitBet ( { marketId : 2 , marketTitle : "M2" , outcomeIndex : 0 , outcomeName : "No" , amount : 20 , walletAddress : "G" } ) ;
267+ submitBet ( {
268+ marketId : 1 ,
269+ marketTitle : "M1" ,
270+ outcomeIndex : 0 ,
271+ outcomeName : "Yes" ,
272+ amount : 10 ,
273+ walletAddress : "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" ,
274+ } ) ;
275+ submitBet ( {
276+ marketId : 2 ,
277+ marketTitle : "M2" ,
278+ outcomeIndex : 0 ,
279+ outcomeName : "No" ,
280+ amount : 20 ,
281+ walletAddress : "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" ,
282+ } ) ;
250283 } } > go</ button >
251284 </ div >
252285 ) ;
@@ -261,4 +294,74 @@ describe("useOptimisticBet hook", () => {
261294 expect ( screen . getByTestId ( "m2" ) . textContent ) . toBe ( "1" ) ;
262295 } ) ;
263296 } ) ;
297+
298+ it ( "includes the stored referral code only on the first successful bet for a wallet" , async ( ) => {
299+ const walletAddress = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" ;
300+ const ownReferralCode = generateReferralCode ( walletAddress ) ;
301+ expect ( ownReferralCode ) . toHaveLength ( 8 ) ;
302+
303+ localStorage . setItem ( REFERRAL_STORAGE_KEY , "REF12345" ) ;
304+
305+ global . fetch = jest
306+ . fn ( )
307+ . mockResolvedValueOnce ( {
308+ ok : true ,
309+ json : async ( ) => ( { bet : { id : 1 } } ) ,
310+ } )
311+ . mockResolvedValueOnce ( {
312+ ok : true ,
313+ json : async ( ) => ( { bet : { id : 2 } } ) ,
314+ } ) as any ;
315+
316+ function AttributionHarness ( ) {
317+ const { submitBet } = useOptimisticBet ( ) ;
318+
319+ return (
320+ < button
321+ onClick = { async ( ) => {
322+ await submitBet ( {
323+ marketId : 1 ,
324+ marketTitle : "Market A" ,
325+ outcomeIndex : 0 ,
326+ outcomeName : "Yes" ,
327+ amount : 10 ,
328+ walletAddress,
329+ } ) ;
330+ await submitBet ( {
331+ marketId : 2 ,
332+ marketTitle : "Market B" ,
333+ outcomeIndex : 1 ,
334+ outcomeName : "No" ,
335+ amount : 12 ,
336+ walletAddress,
337+ } ) ;
338+ } }
339+ >
340+ attribute
341+ </ button >
342+ ) ;
343+ }
344+
345+ const store = configureStore ( { reducer : { optimisticBets : optimisticBetsReducer } } ) ;
346+ render (
347+ < Provider store = { store } >
348+ < AttributionHarness />
349+ </ Provider >
350+ ) ;
351+
352+ act ( ( ) => {
353+ screen . getByRole ( "button" , { name : "attribute" } ) . click ( ) ;
354+ } ) ;
355+
356+ await waitFor ( ( ) => expect ( global . fetch ) . toHaveBeenCalledTimes ( 2 ) ) ;
357+
358+ const firstRequest = JSON . parse ( ( global . fetch as jest . Mock ) . mock . calls [ 0 ] [ 1 ] . body ) ;
359+ const secondRequest = JSON . parse ( ( global . fetch as jest . Mock ) . mock . calls [ 1 ] [ 1 ] . body ) ;
360+
361+ expect ( firstRequest . referralCode ) . toBe ( "REF12345" ) ;
362+ expect ( secondRequest . referralCode ) . toBeUndefined ( ) ;
363+ expect (
364+ localStorage . getItem ( `stella.referral.attributed:${ walletAddress . toUpperCase ( ) } ` )
365+ ) . toBe ( "true" ) ;
366+ } ) ;
264367} ) ;
0 commit comments