Skip to content

Commit b6ef4bf

Browse files
committed
[8383] Stop the welcome tour firing on the AI onboarding path
The tour explains hosted instances, remote instances and the audit log to someone who has just arrived and has none of them. On the onboarding path the Expert covers the same ground by building something, so the tour talks over it. The education modal goes with it: it is not triggered independently, it opens as the tour's completion callback. The flag is never raised rather than raised and ignored. Onboarding sits outside the team route tree, so nothing checks the flag while the user is in the conversation, and leaving it set would fire the tour at them later when they reached the team page. Both remain reachable from the user menu, and both behave exactly as before when AI onboarding is off. Adds the page's first spec covering all of it.
1 parent 4507340 commit b6ef4bf

2 files changed

Lines changed: 103 additions & 2 deletions

File tree

frontend/src/pages/UnverifiedEmail.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import userApi from '../api/user.js'
3434
import FFLayoutBox from '../layouts/Box.vue'
3535
3636
import { useAccountAuthStore } from '@/stores/account-auth.js'
37+
import { useAccountSettingsStore } from '@/stores/account-settings.js'
3738
import { useUxToursStore } from '@/stores/ux-tours.js'
3839
import { useUxStore } from '@/stores/ux.js'
3940
@@ -51,7 +52,8 @@ export default {
5152
}
5253
},
5354
computed: {
54-
...mapState(useAccountAuthStore, ['user'])
55+
...mapState(useAccountAuthStore, ['user']),
56+
...mapState(useAccountSettingsStore, ['featuresCheck'])
5557
},
5658
methods: {
5759
...mapActions(useUxStore, ['setNewlyCreatedUser']),
@@ -60,7 +62,9 @@ export default {
6062
try {
6163
await userApi.verifyEmailToken(this.token)
6264
clearTimeout(this.resendTimeout)
63-
this.presentTour()
65+
if (!this.featuresCheck?.isAiOnboardingFeatureEnabled) {
66+
this.presentTour()
67+
}
6468
this.setNewlyCreatedUser()
6569
this.$router.go()
6670
} catch (err) {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { flushPromises, mount } from '@vue/test-utils'
2+
import { beforeEach, describe, expect, test, vi } from 'vitest'
3+
4+
const mocks = vi.hoisted(() => {
5+
return {
6+
authStore: { user: { username: 'alice' } },
7+
settingsStore: { featuresCheck: {} },
8+
toursStore: { presentTour: vi.fn() },
9+
uxStore: { setNewlyCreatedUser: vi.fn() }
10+
}
11+
})
12+
13+
vi.mock('@/stores/account-auth.js', () => ({
14+
useAccountAuthStore: () => mocks.authStore
15+
}))
16+
vi.mock('@/stores/account-settings.js', () => ({
17+
useAccountSettingsStore: () => mocks.settingsStore
18+
}))
19+
vi.mock('@/stores/ux-tours.js', () => ({
20+
useUxToursStore: () => mocks.toursStore
21+
}))
22+
vi.mock('@/stores/ux.js', () => ({
23+
useUxStore: () => mocks.uxStore
24+
}))
25+
vi.mock('../../../../frontend/src/api/user.js', () => ({
26+
default: {
27+
verifyEmailToken: vi.fn().mockResolvedValue({}),
28+
triggerVerification: vi.fn().mockResolvedValue({})
29+
}
30+
}))
31+
vi.mock('../../../../frontend/src/layouts/Box.vue', () => ({
32+
default: { name: 'FFLayoutBox', template: '<div><slot /><slot name="content" /></div>' }
33+
}))
34+
35+
// imported after mocks so vi.mock hoisting resolves correctly
36+
import userApi from '../../../../frontend/src/api/user.js'
37+
import UnverifiedEmail from '../../../../frontend/src/pages/UnverifiedEmail.vue'
38+
39+
const routerGo = vi.fn()
40+
41+
function mountPage () {
42+
return mount(UnverifiedEmail, {
43+
global: {
44+
stubs: { 'ff-button': true, 'ff-text-input': true },
45+
mocks: { $router: { go: routerGo } }
46+
}
47+
})
48+
}
49+
50+
describe('UnverifiedEmail', () => {
51+
beforeEach(() => {
52+
mocks.settingsStore.featuresCheck = {}
53+
mocks.toursStore.presentTour.mockClear()
54+
mocks.uxStore.setNewlyCreatedUser.mockClear()
55+
userApi.verifyEmailToken.mockClear()
56+
userApi.verifyEmailToken.mockResolvedValue({})
57+
routerGo.mockClear()
58+
})
59+
60+
async function verify (wrapper) {
61+
wrapper.vm.token = 'a-token'
62+
await wrapper.vm.submitVerificationToken()
63+
await flushPromises()
64+
}
65+
66+
test('queues the welcome tour on the classic path', async () => {
67+
const wrapper = mountPage()
68+
await verify(wrapper)
69+
expect(mocks.toursStore.presentTour).toHaveBeenCalledTimes(1)
70+
})
71+
72+
// The tour and the education modal it opens on close both explain concepts
73+
// the onboarding conversation covers by doing
74+
test('does not queue the welcome tour when AI onboarding is enabled', async () => {
75+
mocks.settingsStore.featuresCheck = { isAiOnboardingFeatureEnabled: true }
76+
const wrapper = mountPage()
77+
await verify(wrapper)
78+
expect(mocks.toursStore.presentTour).not.toHaveBeenCalled()
79+
})
80+
81+
test('still marks the user as newly created either way', async () => {
82+
mocks.settingsStore.featuresCheck = { isAiOnboardingFeatureEnabled: true }
83+
const wrapper = mountPage()
84+
await verify(wrapper)
85+
expect(mocks.uxStore.setNewlyCreatedUser).toHaveBeenCalledTimes(1)
86+
expect(routerGo).toHaveBeenCalledTimes(1)
87+
})
88+
89+
test('queues nothing when verification fails', async () => {
90+
userApi.verifyEmailToken.mockRejectedValue(new Error('nope'))
91+
vi.spyOn(console, 'error').mockImplementation(() => {})
92+
const wrapper = mountPage()
93+
await verify(wrapper)
94+
expect(mocks.toursStore.presentTour).not.toHaveBeenCalled()
95+
expect(mocks.uxStore.setNewlyCreatedUser).not.toHaveBeenCalled()
96+
})
97+
})

0 commit comments

Comments
 (0)