feat(webapp): group invite after login (#8518)

This commit is contained in:
Moriz Wahl 2025-05-09 21:19:07 +02:00 committed by GitHub
parent 9300fbd5fc
commit da8af6ff42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 169 additions and 12 deletions

View File

@ -30,7 +30,7 @@
<template v-if="registrationType !== 'no-public-registration'" #footer> <template v-if="registrationType !== 'no-public-registration'" #footer>
<ds-space margin-bottom="xxx-small" margin-top="small" centered> <ds-space margin-bottom="xxx-small" margin-top="small" centered>
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link> <nuxt-link :to="loginLink">{{ $t('site.back-to-login') }}</nuxt-link>
</ds-space> </ds-space>
</template> </template>
</component-slider> </component-slider>
@ -163,6 +163,10 @@ export default {
} }
return { return {
loginLink: {
name: 'login',
query: this.$route.query,
},
links, links,
metadata, metadata,
sliderData: { sliderData: {

View File

@ -1,14 +1,20 @@
import Vuex from 'vuex' import Vuex from 'vuex'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import login from './login.vue' import login from './login.vue'
import LoginForm from '~/components/LoginForm/LoginForm.vue'
const localVue = global.localVue const localVue = global.localVue
const stubs = { const stubs = {
'client-only': true, 'client-only': true,
'nuxt-link': true, 'nuxt-link': true,
'router-link': true,
} }
const routerPushMock = jest.fn()
const routerReplaceMock = jest.fn()
const i18nSetMock = jest.fn()
describe('Login.vue', () => { describe('Login.vue', () => {
let store let store
let mocks let mocks
@ -22,6 +28,14 @@ describe('Login.vue', () => {
$t: jest.fn(), $t: jest.fn(),
$i18n: { $i18n: {
locale: () => 'en', locale: () => 'en',
set: i18nSetMock,
},
$route: {
query: {},
},
$router: {
replace: routerReplaceMock,
push: routerPushMock,
}, },
} }
asyncData = false asyncData = false
@ -73,5 +87,51 @@ describe('Login.vue', () => {
wrapper = await Wrapper() wrapper = await Wrapper()
expect(redirect).toHaveBeenCalledWith('/') expect(redirect).toHaveBeenCalledWith('/')
}) })
describe('handle succcess', () => {
beforeEach(async () => {
asyncData = true
tosVersion = '0.0.4'
})
describe('with route query to invite code', () => {
beforeEach(async () => {
mocks.$route.query = {
inviteCode: 'ABCDEF',
}
wrapper = await Wrapper()
wrapper.findComponent(LoginForm).vm.$emit('success')
})
it('calls i18n.set', () => {
expect(i18nSetMock).toBeCalledWith('en')
})
it('call router push to registration page', () => {
expect(routerPushMock).toBeCalledWith({
name: 'registration',
query: {
inviteCode: 'ABCDEF',
},
})
})
})
describe('without route query to invite code', () => {
beforeEach(async () => {
mocks.$route.query = {}
wrapper = await Wrapper()
wrapper.findComponent(LoginForm).vm.$emit('success')
})
it('calls i18n.set', () => {
expect(i18nSetMock).toBeCalledWith('en')
})
it('call router push to registration page', () => {
expect(routerReplaceMock).toBeCalledWith('/')
})
})
})
}) })
}) })

View File

@ -32,7 +32,14 @@ export default {
this.$i18n.set(this.user.locale || 'en') this.$i18n.set(this.user.locale || 'en')
try { try {
if (this.$route.query.inviteCode) {
this.$router.push({
name: 'registration',
query: this.$route.query,
})
} else {
await this.$router.replace(this.$route.query.path || '/') await this.$router.replace(this.$route.query.path || '/')
}
} catch (err) { } catch (err) {
// throw new Error(`Problem handling something: ${err}.`); // throw new Error(`Problem handling something: ${err}.`);
// TODO this is causing trouble - most likely due to double redirect on terms&conditions // TODO this is causing trouble - most likely due to double redirect on terms&conditions

View File

@ -20,7 +20,7 @@ const app = {
apolloProvider: { apolloProvider: {
defaultClient: { defaultClient: {
query: queryMock, query: queryMock,
mutation: mutationMock, mutate: mutationMock,
}, },
}, },
} }
@ -387,8 +387,7 @@ describe('Registration', () => {
}) })
}) })
// no idea why this is not working describe('route contains group invite code to public group', () => {
describe.skip('route contains group invite code to public group', () => {
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks() jest.clearAllMocks()
queryMock.mockResolvedValue({ queryMock.mockResolvedValue({
@ -433,6 +432,96 @@ describe('Registration', () => {
}) })
}) })
}) })
describe('route contains group invite code to closed group', () => {
beforeEach(async () => {
jest.clearAllMocks()
queryMock.mockResolvedValue({
data: {
validateInviteCode: {
invitedTo: {
id: 'closed-group',
slug: 'closed-group',
groupType: 'closed',
},
},
},
})
mutationMock.mockResolvedValue({
data: {
redeemInviteCode: true,
},
})
route.query.inviteCode = 'ABCDEF'
wrapper = await Wrapper()
})
it('calls validate invite code', () => {
expect(queryMock).toHaveBeenCalledWith({
query: validateInviteCodeQuery,
variables: {
code: 'ABCDEF',
},
})
})
it('redirects to index', () => {
expect(redirect).toHaveBeenCalledWith('/')
})
it('redeems the code', () => {
expect(mutationMock).toBeCalledWith({
mutation: redeemInviteCodeMutation,
variables: {
code: 'ABCDEF',
},
})
})
})
describe('route contains group invite code to public group, but redeem throws', () => {
beforeEach(async () => {
jest.clearAllMocks()
queryMock.mockResolvedValue({
data: {
validateInviteCode: {
invitedTo: {
id: 'public-group',
slug: 'public-group',
groupType: 'public',
},
},
},
})
mutationMock.mockRejectedValue({
error: 'Aua!',
})
route.query.inviteCode = 'ABCDEF'
wrapper = await Wrapper()
})
it('calls validate invite code', () => {
expect(queryMock).toHaveBeenCalledWith({
query: validateInviteCodeQuery,
variables: {
code: 'ABCDEF',
},
})
})
it('redirects to index', () => {
expect(redirect).toHaveBeenCalledWith('/')
})
it('redeems the code', () => {
expect(mutationMock).toBeCalledWith({
mutation: redeemInviteCodeMutation,
variables: {
code: 'ABCDEF',
},
})
})
})
}) })
// copied from webapp/components/Registration/Signup.spec.js as testing template // copied from webapp/components/Registration/Signup.spec.js as testing template

View File

@ -36,7 +36,7 @@ export default {
} }
}, },
async asyncData({ store, route, app, redirect }) { async asyncData({ store, route, app, redirect }) {
// http://localhost:3000/registration?method=invite-code&inviteCode=T9TWMJ // http://localhost:3000/registration?method=invite-code&inviteCode=PEY8FN
if (store.getters['auth/isLoggedIn']) { if (store.getters['auth/isLoggedIn']) {
const { const {
query: { inviteCode: code }, query: { inviteCode: code },
@ -62,19 +62,16 @@ export default {
}) })
if (mutationResult.data.redeemInviteCode && group.groupType === 'public') { if (mutationResult.data.redeemInviteCode && group.groupType === 'public') {
redirect(`/groups/${group.id}/${group.slug}`) redirect(`/groups/${group.id}/${group.slug}`)
} else { return
redirect('/')
} }
} else {
redirect('/')
} }
} catch (_err) { } catch (_err) {
redirect('/') redirect('/')
return
}
} }
} else {
redirect('/') redirect('/')
} }
}
}, },
computed: { computed: {
registrationType() { registrationType() {