Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract NewPasswordPage from SignInPage, and make new route #7826

Merged
Merged
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 @@ -72,8 +72,7 @@ function makeWrapper(options) {
describe('ActivityList component', () => {
it('on first render, calls the notification resource', async () => {
const { fetchMock, wrapper } = makeWrapper({});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await global.flushPromises();
Copy link
Member

Choose a reason for hiding this comment

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

Swanky

expect(fetchMock).toHaveBeenCalled();
expect(wrapper.vm.moreResults).toBe(false);
});
Expand All @@ -84,33 +83,29 @@ describe('ActivityList component', () => {
noActivityString: 'No activity in this classroom',
},
});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await global.flushPromises();
const noActivity = wrapper.find('.notifications p');
expect(noActivity.text()).toEqual('No activity in this classroom');
});

it('has a "show more" button if there are more pages of notifications', async () => {
const { wrapper } = makeWrapper({ moreResults: true });
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await global.flushPromises();
const showMoreButton = wrapper.findComponent({ name: 'KButton' });
expect(showMoreButton.exists()).toEqual(true);
});

it('does not have a "show more" button if there are no more pages of notifications', async () => {
const { wrapper } = makeWrapper({});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await global.flushPromises();
const showMoreButton = wrapper.findComponent({ name: 'KButton' });
expect(showMoreButton.exists()).toEqual(false);
});

it('disables the "show more" button if any filters are activated', async () => {
const { wrapper } = makeWrapper({});
const showMoreButton = () => wrapper.findComponent({ name: 'KButton' });
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await global.flushPromises();
wrapper.setData({
loading: false,
moreResults: true,
Expand Down Expand Up @@ -157,8 +152,7 @@ describe('ActivityList component', () => {
],
});

await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await global.flushPromises();

const filters = wrapper.findComponent({ name: 'NotificationsFilter' });
// Logic is very simple: just find the unique values in the notifications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function makeWrapper() {
store,
});
// Have to wait to let the channels data load
await wrapper.vm.$nextTick();
await global.flushPromises();
return { wrapper };
}

Expand All @@ -27,10 +27,7 @@ describe('RearrangeChannelsPage', () => {
newArray: [wrapper.vm.channels[1], wrapper.vm.channels[0]],
});
expect(wrapper.vm.postNewOrder).toHaveBeenCalledWith(['2', '1']);
// Have to wait a bunch of ticks for some reason
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await global.flushPromises();
}

it('loads the data on mount', async () => {
Expand Down
1 change: 1 addition & 0 deletions kolibri/plugins/user/assets/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const ComponentMap = {
PROFILE_EDIT: 'ProfileEditPage',
AUTH_SELECT: 'AuthSelect',
FACILITY_SELECT: 'FacilitySelect',
NEW_PASSWORD: 'NewPasswordPage',
};

export const pageNameToModuleMap = {
Expand Down
19 changes: 19 additions & 0 deletions kolibri/plugins/user/assets/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ProfilePage from './views/ProfilePage';
import ProfileEditPage from './views/ProfileEditPage';
import SignInPage from './views/SignInPage';
import SignUpPage from './views/SignUpPage';
import NewPasswordPage from './views/SignInPage/NewPasswordPage';

router.beforeEach((to, from, next) => {
const profileRoutes = [ComponentMap.PROFILE, ComponentMap.PROFILE_EDIT];
Expand Down Expand Up @@ -104,6 +105,24 @@ export default [
}
},
},
{
path: '/set-password',
component: NewPasswordPage,
beforeEnter(to, from, next) {
store.commit('CORE_SET_PAGE_LOADING', false);
if (!to.query.facility || !to.query.username) {
next({ path: '/' });
} else {
next();
}
},
props(route) {
return {
facilityId: route.query.facility,
username: route.query.username,
};
},
},
{
path: '/facilities',
component: FacilitySelect,
Expand Down
132 changes: 132 additions & 0 deletions kolibri/plugins/user/assets/src/views/SignInPage/NewPasswordPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>

<AuthBase>
<div style="text-align: left">
<KButton
appearance="basic-link"
style="margin-bottom: 16px;"
data-test="goback"
@click="goBack"
>
<template #icon>
<KIcon
icon="back"
:style="{
fill: $themeTokens.primary,
height: '1.125em',
width: '1.125em',
position: 'relative',
marginRight: '8px',
top: '2px',
}"
/>{{ coreString('goBackAction') }}
</template>
</KButton>

<p>{{ $tr("needToMakeNewPasswordLabel", { user: username }) }}</p>

<PasswordTextbox
ref="createPassword"
:autofocus="true"
:disabled="busy"
:value.sync="password"
:isValid.sync="passwordIsValid"
:shouldValidate="busy"
@submitNewPassword="updatePassword"
/>
<KButton
appearance="raised-button"
:primary="true"
:text="coreString('continueAction')"
style="width: 100%; margin: 24px auto 0; display:block;"
:disabled="busy"
data-test="submit"
@click="updatePassword"
/>
</div>
</AuthBase>

</template>


<script>

import pickBy from 'lodash/pickBy';
import PasswordTextbox from 'kolibri.coreVue.components.PasswordTextbox';
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import AuthBase from '../AuthBase';
import { ComponentMap } from '../../constants';

export default {
name: 'NewPasswordPage',
components: {
AuthBase,
PasswordTextbox,
},
mixins: [commonCoreStrings],
props: {
username: {
type: String,
required: true,
},
facilityId: {
type: String,
required: true,
},
},
data() {
return {
busy: false,
password: '',
passwordIsValid: false,
};
},
computed: {
credentials() {
return pickBy({
username: this.username,
password: this.password,
facility: this.facilityId,
next: this.$route.query.next,
});
},
},
methods: {
updatePassword() {
if (this.passwordIsValid) {
this.busy = true;
this.$store
.dispatch('kolibriSetUnspecifiedPassword', this.credentials)
.then(() => {
this.signIn();
})
.catch(() => {
// In case user has already set password or user does not exist,
// simply go back to the Sign In page.
this.goBack();
});
} else {
this.$refs.createPassword.focus();
}
},
signIn() {
this.$store.dispatch('kolibriLogin', this.credentials).catch(() => {
// In case of an error, we just go back to the Sign In page
this.goBack();
});
},
goBack() {
this.$router.push({
name: ComponentMap.SIGN_IN,
});
},
},
$trs: {
needToMakeNewPasswordLabel: 'Hi, {user}. You need to set a new password for your account.',
},
};

</script>


<style lang="scss" scoped></style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { mount } from '@vue/test-utils';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
import NewPasswordPage from '../NewPasswordPage';

const loginSpy = jest.fn().mockResolvedValue();
const updatePwSpy = jest.fn().mockResolvedValue();

const store = new Vuex.Store({
actions: {
kolibriSetUnspecifiedPassword: updatePwSpy,
kolibriLogin: loginSpy,
},
});

const router = new VueRouter({
routes: [{ name: 'SignInPage', path: '/sign-in' }],
});

function makeWrapper() {
const signInSpy = jest.spyOn(NewPasswordPage.methods, 'signIn');
const wrapper = mount(NewPasswordPage, {
propsData: {
username: 'a',
facilityId: 'f',
},
store,
router,
stubs: {
AuthBase: {
template: '<div><slot></slot></div>',
},
},
});
return { wrapper, signInSpy };
}

describe('NewPasswordPage', () => {
afterEach(() => {
loginSpy.mockReset();
updatePwSpy.mockReset();
});

it('if password is not valid, clicking "continue" does nothing', async () => {
const { wrapper } = makeWrapper();
const button = wrapper.find('[data-test="submit"]');
button.trigger('click');
await global.flushPromises();
expect(updatePwSpy).not.toHaveBeenCalled();
});

it('if password is valid, clicking "continue" updates pw and signs user in', async () => {
const { wrapper } = makeWrapper();
wrapper.setData({
password: 'pass',
passwordIsValid: true,
});
const button = wrapper.find('[data-test="submit"]');
button.trigger('click');
await global.flushPromises();
expect(updatePwSpy.mock.calls[0][1]).toMatchObject({
username: 'a',
password: 'pass',
facility: 'f',
});
expect(loginSpy.mock.calls[0][1]).toMatchObject({
username: 'a',
password: 'pass',
facility: 'f',
});
});

it('clicking the "go back" navigates user to the Sign-In page', () => {
const { wrapper } = makeWrapper();
const button = wrapper.find('[data-test="goback"]');
button.trigger('click');
expect(wrapper.vm.$route.name).toEqual('SignInPage');
});
});
Loading