11import { describe , it , expect , vi , beforeEach } from 'vitest' ;
2- import { render , screen , waitFor , fireEvent } from '@testing-library/react' ;
2+ import { render , screen , waitFor } from '@testing-library/react' ;
33import Collection from './Collection' ;
44import { pokemonAPI } from '@/api/client' ;
55
@@ -10,6 +10,7 @@ vi.mock('@/api/client', () => ({
1010 getPokemon : vi . fn ( ) ,
1111 updateCaughtPokemon : vi . fn ( ) ,
1212 releasePokemon : vi . fn ( ) ,
13+ claimStarters : vi . fn ( ) ,
1314 } ,
1415} ) ) ;
1516
@@ -31,21 +32,16 @@ describe('Collection Page', () => {
3132
3233 it ( 'should show loading state initially' , ( ) => {
3334 pokemonAPI . getCaughtPokemon . mockResolvedValue ( [ ] ) ;
34-
3535 render ( < Collection onNavigate = { vi . fn ( ) } /> ) ;
36-
37- expect ( screen . getByText ( / L o a d i n g P o k é m o n / i) ) . toBeInTheDocument ( ) ;
36+ expect ( screen . getByText ( '⭐' ) ) . toBeInTheDocument ( ) ;
3837 } ) ;
3938
4039 it ( 'should show empty state when no pokemon caught' , async ( ) => {
4140 pokemonAPI . getCaughtPokemon . mockResolvedValue ( [ ] ) ;
42-
4341 render ( < Collection onNavigate = { vi . fn ( ) } /> ) ;
44-
4542 await waitFor ( ( ) => {
46- expect ( screen . queryByText ( / L o a d i n g P o k é m o n / i ) ) . not . toBeInTheDocument ( ) ;
43+ expect ( screen . queryByText ( '⭐' ) ) . not . toBeInTheDocument ( ) ;
4744 } ) ;
48-
4945 expect ( screen . getByText ( / Y o u r c o l l e c t i o n i s e m p t y / i) ) . toBeInTheDocument ( ) ;
5046 expect ( screen . getByText ( / E x p l o r e W i l d P o k é m o n / i) ) . toBeInTheDocument ( ) ;
5147 } ) ;
@@ -55,9 +51,9 @@ describe('Collection Page', () => {
5551 { id : 1 , pokemon_id : 25 , nickname : null , caught_date : '2024-01-01' } ,
5652 { id : 2 , pokemon_id : 1 , nickname : 'Bulby' , caught_date : '2024-01-02' } ,
5753 ] ;
58-
54+
5955 pokemonAPI . getCaughtPokemon . mockResolvedValue ( caughtPokemon ) ;
60- pokemonAPI . getPokemon . mockImplementation ( ( id ) =>
56+ pokemonAPI . getPokemon . mockImplementation ( ( id ) =>
6157 Promise . resolve ( {
6258 id,
6359 name : id === 25 ? 'Pikachu' : 'Bulbasaur' ,
@@ -67,42 +63,84 @@ describe('Collection Page', () => {
6763 image_url : 'https://example.com/image.png' ,
6864 } )
6965 ) ;
70-
66+
7167 render ( < Collection onNavigate = { vi . fn ( ) } /> ) ;
72-
68+
7369 await waitFor ( ( ) => {
74- expect ( screen . queryByText ( / L o a d i n g P o k é m o n / i ) ) . not . toBeInTheDocument ( ) ;
70+ expect ( screen . queryByText ( '⭐' ) ) . not . toBeInTheDocument ( ) ;
7571 } ) ;
76-
72+
7773 await waitFor ( ( ) => {
7874 expect ( screen . getByText ( 'Pikachu' ) ) . toBeInTheDocument ( ) ;
7975 } ) ;
80-
76+
8177 expect ( screen . getByText ( 'Bulby' ) ) . toBeInTheDocument ( ) ;
8278 } ) ;
8379
8480 it ( 'should handle API errors gracefully' , async ( ) => {
8581 pokemonAPI . getCaughtPokemon . mockRejectedValue ( new Error ( 'Network error' ) ) ;
86-
8782 render ( < Collection onNavigate = { vi . fn ( ) } /> ) ;
88-
8983 await waitFor ( ( ) => {
90- expect ( screen . queryByText ( / L o a d i n g P o k é m o n / i ) ) . not . toBeInTheDocument ( ) ;
84+ expect ( screen . queryByText ( '⭐' ) ) . not . toBeInTheDocument ( ) ;
9185 } ) ;
92-
9386 // Should show empty state or error message
9487 expect ( screen . getByText ( / Y o u r c o l l e c t i o n i s e m p t y / i) ) . toBeInTheDocument ( ) ;
9588 } ) ;
9689
9790 it ( 'should have navigation buttons' , async ( ) => {
9891 pokemonAPI . getCaughtPokemon . mockResolvedValue ( [ ] ) ;
92+ render ( < Collection onNavigate = { vi . fn ( ) } /> ) ;
93+ await waitFor ( ( ) => {
94+ expect ( screen . queryByText ( '⭐' ) ) . not . toBeInTheDocument ( ) ;
95+ } ) ;
96+ expect ( screen . getByText ( / E x p l o r e W i l d P o k é m o n / i) ) . toBeInTheDocument ( ) ;
97+ } ) ;
98+
99+ it ( 'should auto-claim starter pokemon for new users' , async ( ) => {
100+ const starterPokemon = [
101+ { id : 1 , pokemon_id : 'starter-1' , nickname : null , caught_date : '2024-01-01' } ,
102+ { id : 2 , pokemon_id : 'starter-2' , nickname : null , caught_date : '2024-01-01' } ,
103+ { id : 3 , pokemon_id : 'starter-3' , nickname : null , caught_date : '2024-01-01' } ,
104+ ] ;
99105
106+ // Empty at first, then has starters after claiming
107+ pokemonAPI . getCaughtPokemon
108+ . mockResolvedValueOnce ( [ ] )
109+ . mockResolvedValueOnce ( starterPokemon ) ;
110+
111+ pokemonAPI . claimStarters . mockResolvedValue ( {
112+ success : true ,
113+ message : 'Welcome to the world of Pokemon! You received 3 starter Pokemon!' ,
114+ starters : [
115+ { caught_id : 1 , name : 'Flametail Jr' } ,
116+ { caught_id : 2 , name : 'Ripplefin' } ,
117+ { caught_id : 3 , name : 'Leaflet' } ,
118+ ] ,
119+ } ) ;
120+
100121 render ( < Collection onNavigate = { vi . fn ( ) } /> ) ;
101122
102123 await waitFor ( ( ) => {
103- expect ( screen . queryByText ( / L o a d i n g P o k é m o n / i ) ) . not . toBeInTheDocument ( ) ;
124+ expect ( pokemonAPI . claimStarters ) . toHaveBeenCalledTimes ( 1 ) ;
104125 } ) ;
105126
106- expect ( screen . getByText ( / E x p l o r e W i l d P o k é m o n / i) ) . toBeInTheDocument ( ) ;
127+ // Should fetch the collection twice (empty, then with starters)
128+ expect ( pokemonAPI . getCaughtPokemon ) . toHaveBeenCalledTimes ( 2 ) ;
129+ } ) ;
130+
131+ it ( 'should handle auto-claim failure gracefully' , async ( ) => {
132+ // Empty collection
133+ pokemonAPI . getCaughtPokemon . mockResolvedValue ( [ ] ) ;
134+ pokemonAPI . claimStarters . mockRejectedValue ( new Error ( 'API Error' ) ) ;
135+
136+ render ( < Collection onNavigate = { vi . fn ( ) } /> ) ;
137+
138+ await waitFor ( ( ) => {
139+ expect ( screen . queryByText ( '⭐' ) ) . not . toBeInTheDocument ( ) ;
140+ } ) ;
141+
142+ // Should show empty state even if auto-claim failed
143+ expect ( screen . getByText ( / Y o u r c o l l e c t i o n i s e m p t y / i) ) . toBeInTheDocument ( ) ;
144+ expect ( screen . getByText ( / C l a i m Y o u r S t a r t e r P o k é m o n / i) ) . toBeInTheDocument ( ) ;
107145 } ) ;
108146} ) ;
0 commit comments