@@ -7,7 +7,7 @@ const mocks = vi.hoisted(() => {
77 contextStore : { team : null } ,
88 settingsStore : { featuresCheck : { } } ,
99 accountStore : { setTeam : vi . fn ( ) . mockResolvedValue ( ) } ,
10- expertStore : { messages : [ ] , hydrateMessages : vi . fn ( ) } ,
10+ expertStore : { messages : [ ] , openConversation : vi . fn ( ) } ,
1111 supportAgentStore : { reset : vi . fn ( ) } ,
1212 uxStore : { isOnboardingIntake : true , endOnboarding : vi . fn ( ) }
1313 }
@@ -147,40 +147,51 @@ describe('Onboarding page', () => {
147147 expect ( wrapper . vm . $options . provide . call ( wrapper . vm ) [ 'expert-surface' ] ) . toBe ( 'onboarding' )
148148 } )
149149
150- describe ( 'fixture transcript ' , ( ) => {
150+ describe ( 'opening the conversation ' , ( ) => {
151151 beforeEach ( ( ) => {
152- mocks . expertStore . hydrateMessages . mockClear ( )
152+ mocks . expertStore . openConversation . mockClear ( )
153153 mocks . supportAgentStore . reset . mockClear ( )
154154 mocks . expertStore . messages = [ ]
155155 } )
156156
157- test ( 'seeds the placeholder conversation when the transcript is empty' , async ( ) => {
157+ test ( 'asks the Expert to open the conversation when the transcript is empty' , async ( ) => {
158158 await mountPage ( )
159- expect ( mocks . expertStore . hydrateMessages ) . toHaveBeenCalledTimes ( 1 )
160- const seeded = mocks . expertStore . hydrateMessages . mock . calls [ 0 ] [ 0 ]
161- expect ( Array . isArray ( seeded ) ) . toBe ( true )
162- expect ( seeded . length ) . toBeGreaterThan ( 0 )
159+ expect ( mocks . expertStore . openConversation ) . toHaveBeenCalledTimes ( 1 )
163160 expect ( mocks . supportAgentStore . reset ) . not . toHaveBeenCalled ( )
164161 } )
165162
166- test ( 'replaces a transcript that only holds canned messages' , async ( ) => {
163+ // Arriving from the drawer leaves its canned welcome behind; it is not a
164+ // conversation, so it gets cleared rather than opened on top of
165+ test ( 'clears a transcript that only holds canned messages first' , async ( ) => {
167166 mocks . expertStore . messages = [ { _type : 'ai' , generated : true } ]
168167 await mountPage ( )
169168 expect ( mocks . supportAgentStore . reset ) . toHaveBeenCalledTimes ( 1 )
170- expect ( mocks . expertStore . hydrateMessages ) . toHaveBeenCalledTimes ( 1 )
169+ expect ( mocks . expertStore . openConversation ) . toHaveBeenCalledTimes ( 1 )
171170 } )
172171
173- test ( 'does not reseed a real conversation' , async ( ) => {
172+ // This is what makes the page resumable: a conversation already in
173+ // progress is picked up rather than restarted
174+ test ( 'leaves a real conversation alone' , async ( ) => {
174175 mocks . expertStore . messages = [ { _type : 'human' , content : 'hello' } ]
175176 await mountPage ( )
176- expect ( mocks . expertStore . hydrateMessages ) . not . toHaveBeenCalled ( )
177+ expect ( mocks . expertStore . openConversation ) . not . toHaveBeenCalled ( )
177178 expect ( mocks . supportAgentStore . reset ) . not . toHaveBeenCalled ( )
178179 } )
179180
180- test ( 'does not seed when the page is redirecting away' , async ( ) => {
181+ test ( 'does not open when the page is redirecting away' , async ( ) => {
181182 mocks . settingsStore . featuresCheck = { isAiOnboardingFeatureEnabled : false }
182183 await mountPage ( )
183- expect ( mocks . expertStore . hydrateMessages ) . not . toHaveBeenCalled ( )
184+ expect ( mocks . expertStore . openConversation ) . not . toHaveBeenCalled ( )
185+ } )
186+
187+ // The team watcher can fire more than once before the opening turn comes
188+ // back, and an empty transcript would let it through every time
189+ test ( 'only opens once even if the team resolves again' , async ( ) => {
190+ const wrapper = await mountPage ( )
191+ mocks . contextStore . team = { id : 't1' , slug : 'ateam' , instanceCount : 0 }
192+ await wrapper . vm . $nextTick ( )
193+ wrapper . vm . openConversation ( )
194+ expect ( mocks . expertStore . openConversation ) . toHaveBeenCalledTimes ( 1 )
184195 } )
185196 } )
186197
@@ -222,19 +233,14 @@ describe('Onboarding page', () => {
222233 } )
223234
224235 // A direct load or refresh resolves the team after the page mounts, so
225- // the seed lands after the turn baseline would have been captured. The
226- // seeded turns must not read as engagement
227- test ( 'does not count turns seeded after a late team resolve as engagement' , async ( ) => {
236+ // a resumed transcript can land after the turn baseline would have
237+ // been captured. Turns already there must not read as engagement
238+ test ( 'does not count turns present before the team resolves as engagement' , async ( ) => {
228239 mocks . contextStore . team = null
229240 mocks . expertStore . messages = [ ]
230- mocks . expertStore . hydrateMessages . mockImplementationOnce ( ( ) => {
231- mocks . expertStore . messages = [
232- { _type : 'ai' , generated : true } ,
233- { _type : 'human' , content : 'seeded' }
234- ]
235- } )
236241 const wrapper = await mountPage ( )
237242
243+ mocks . expertStore . messages = [ { _type : 'human' , content : 'hello again' } ]
238244 mocks . contextStore . team = { id : 't1' , slug : 'ateam' , instanceCount : 0 }
239245 await flushPromises ( )
240246 expect ( wrapper . find ( '[data-action="skip-onboarding"]' ) . classes ( ) ) . not . toContain ( 'has-engaged' )
0 commit comments