Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@ import Vuex from 'vuex';
import Vue from 'vue';
import VueRouter from 'vue-router';
import RequestNewActivationLink from '../activateAccount/RequestNewActivationLink';
import commonStrings from 'shared/translator';

Vue.use(Vuex);
Vue.use(VueRouter);
let testStore;

function createTestStore() {
function createTestStore({ sendActivationLink = jest.fn(() => Promise.resolve()) } = {}) {
testStore = new Vuex.Store({
modules: {
account: {
namespaced: true,
actions: {
sendActivationLink: jest.fn(() => Promise.resolve()),
sendActivationLink,
},
},
},
});
return testStore;
}

function renderComponent() {
function renderComponent(storeOptions) {
const router = new VueRouter({
routes: [
{
Expand All @@ -38,21 +39,42 @@ function renderComponent() {
});

return render(RequestNewActivationLink, {
store: createTestStore(),
store: createTestStore(storeOptions),
router,
});
}

describe('requestNewActivationLink', () => {
it('should show validation error when submitting with invalid email', async () => {
it('should show a required-field error when submitting with an empty email', async () => {
const user = userEvent.setup();
renderComponent();

const submitButton = screen.getByRole('button', { name: /submit/i });
const submitButton = screen.getByRole('button', {
name: RequestNewActivationLink.$trs.submitButton,
});
await user.click(submitButton);

await waitFor(() => {
expect(screen.getByText(commonStrings.$tr('fieldRequired'))).toBeInTheDocument();
});
});

it('should show a validation error when submitting an invalid email', async () => {
const user = userEvent.setup();
renderComponent();

const emailInput = screen.getByLabelText(RequestNewActivationLink.$trs.emailLabel);
const submitButton = screen.getByRole('button', {
name: RequestNewActivationLink.$trs.submitButton,
});

await user.type(emailInput, 'not-an-email');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The validation tests don't assert the submit was blocked. generateFormMixin's field setter validates on every v-model update (shared/mixins.js:406-413), so the message is already rendered by the end of user.type — the click is incidental and the assertion passes regardless of what requestActivationLink does. Both this test and the empty-email one would stay green if submit dispatched with invalid data.

Adding expect(sendActivationLink).not.toHaveBeenCalled() closes the gap; renderComponent already accepts an injectable mock, so it just needs hoisting into the test. forgotPassword.spec.js:45-56 is the existing pattern.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LightCreator1007, this seems like a valid add that would make the test more robust. Thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably worth adding the expect(sendActivationLink).not.toHaveBeenCalled() as suggested to close the gap.

await user.click(submitButton);

await waitFor(() => {
expect(screen.getByText(/activation failed/i)).toBeInTheDocument();
expect(
screen.getByText(RequestNewActivationLink.$trs.emailValidationMessage),
).toBeInTheDocument();
});
});

Expand All @@ -61,8 +83,10 @@ describe('requestNewActivationLink', () => {
renderComponent();
const sendActivationLink = jest.spyOn(testStore, 'dispatch');

const emailInput = screen.getByLabelText(/email/i);
const submitButton = screen.getByRole('button', { name: /submit/i });
const emailInput = screen.getByLabelText(RequestNewActivationLink.$trs.emailLabel);
const submitButton = screen.getByRole('button', {
name: RequestNewActivationLink.$trs.submitButton,
});

await user.type(emailInput, 'test@test.com');
await user.click(submitButton);
Expand All @@ -74,4 +98,23 @@ describe('requestNewActivationLink', () => {
);
});
});

it('should show a banner when the request fails', async () => {
const user = userEvent.setup();
renderComponent({ sendActivationLink: jest.fn(() => Promise.reject(new Error('failed'))) });

const emailInput = screen.getByLabelText(RequestNewActivationLink.$trs.emailLabel);
const submitButton = screen.getByRole('button', {
name: RequestNewActivationLink.$trs.submitButton,
});

await user.type(emailInput, 'test@test.com');
await user.click(submitButton);

await waitFor(() => {
expect(
screen.getByText(RequestNewActivationLink.$trs.activationRequestFailed),
).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,62 +1,84 @@
<template>

<MessageLayout
<StudioMessageLayout
:header="$tr('activationExpiredTitle')"
:text="$tr('activationExpiredText')"
>
<VForm
ref="form"
lazy-validation
<form
class="request-activation-form"
novalidate
@submit.prevent="requestActivationLink"
>
<Banner
:text="$tr('activationRequestFailed')"
:value="error"
<StudioBanner
v-if="error"
error
class="mb-4"
/>
<EmailField
class="banner"
>
{{ $tr('activationRequestFailed') }}
</StudioBanner>
<StudioEmailField
v-model="email"
autofocus
:label="$tr('emailLabel')"
:errorMessages="errors.email ? [emailErrorText] : []"

@rtibblesbot rtibblesbot Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

suggestion: With generateFormMixin, the email setter runs the validator on every v-model update, so errorMessages can now appear after the very first keystroke rather than on blur/submit as before. Matches Create.vue's existing behavior, but flagging since it's a UX change not called out in the issue.

@rtibblesbot rtibblesbot Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

suggestion: With generateFormMixin, the email setter runs the validator on every v-model update, so error text can now appear after the first keystroke rather than on blur/submit as before. Matches Create.vue's existing behavior, but it's a UX shift not called out in the issue.

/>
<KButton
primary
class="w-100"
:text="$tr('submitButton')"
type="submit"
/>
</VForm>
</MessageLayout>
</form>
</StudioMessageLayout>

</template>


<script>

import { mapActions } from 'vuex';
import MessageLayout from '../../components/MessageLayout';
import EmailField from 'shared/views/form/EmailField';
import Banner from 'shared/views/Banner';
import StudioMessageLayout from '../../components/StudioMessageLayout';
import StudioEmailField from '../../components/form/StudioEmailField';
import StudioBanner from 'shared/views/StudioBanner';
import commonStrings from 'shared/translator';
import { generateFormMixin } from 'shared/mixins';

const formMixin = generateFormMixin({
email: {
required: true,
validator: v => Boolean(v && v.trim()) && /.+@.+\..+/.test(v),
},
});

export default {
name: 'RequestNewActivationLink',
components: {
MessageLayout,
EmailField,
Banner,
StudioMessageLayout,
StudioEmailField,
StudioBanner,
},
mixins: [formMixin],
data() {
return {
email: '',
error: false,
};
},
computed: {
emailErrorText() {
if (!this.email || !this.email.trim()) {
/* eslint-disable-next-line kolibri/vue-no-undefined-string-uses */
return commonStrings.$tr('fieldRequired');
}
return this.$tr('emailValidationMessage');
},
},
methods: {
...mapActions('account', ['sendActivationLink']),
requestActivationLink() {
this.error = false;
if (this.$refs.form.validate()) {
this.sendActivationLink(this.email)
const formData = this.clean();
if (this.validate(formData)) {
this.sendActivationLink(formData.email)
.then(() => {
this.$router.replace({ name: 'ActivationLinkReSent' }).catch(() => {});
})
Expand All @@ -71,6 +93,8 @@
activationExpiredText: 'This activation link has been used already or has expired.',
submitButton: 'Submit',
activationRequestFailed: 'Failed to send a new activation link. Please try again.',
emailLabel: 'Email',
emailValidationMessage: 'Please enter a valid email address',
},
};

Expand All @@ -79,6 +103,17 @@

<style lang="scss" scoped>

.request-activation-form {
width: 400px;
max-width: 100%;
text-align: left;
}

.banner {
width: 100%;
margin-bottom: 16px;
}

.w-100 {
width: 100%;
}
Expand Down
Loading