Vue component unit test isolation (#12154)

* Issue 10786 - Add unit test for Home component

* Issue 10786 - Improve test setup and test invite parameter variations

* Issue 10786 - Improve Vue.js test isolation by adding async keyword to dispatch function

* Issue 10786 - Missing action does not need to be awaited

* Use localVue for groupsUtilities test and revert partial zone fix
This commit is contained in:
Bart Enkelaar
2020-05-05 16:20:08 +02:00
committed by GitHub
parent 378325a8a2
commit 6e24cf0fe1
10 changed files with 135 additions and 31 deletions

View File

@@ -28,8 +28,8 @@
<div class="inner-content">
<questDialogContent :item="item" />
<div
class="purchase-amount"
v-if="!item.locked"
class="purchase-amount"
>
<div class="how-many-to-buy">
<strong>{{ $t('howManyToBuy') }}</strong>

View File

@@ -913,6 +913,7 @@ export default {
if (username.length < 1) {
return;
}
this.$store.dispatch('auth:verifyUsername', {
username: this.username,
}).then(res => {
@@ -942,15 +943,7 @@ export default {
groupInvite,
});
let redirectTo;
if (this.$route.query.redirectTo) {
redirectTo = this.$route.query.redirectTo;
} else {
redirectTo = '/';
}
window.location.href = redirectTo;
window.location.href = this.$route.query.redirectTo || '/';
},
playButtonClick () {
Analytics.track({
@@ -968,7 +961,7 @@ export default {
} else {
try {
await hello(network).logout();
} catch (e) {} // eslint-disable-line
} catch (e) {} // eslint-disable-line
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
const auth = await hello(network).login({

View File

@@ -5,7 +5,8 @@
>
<div
class="svg-icon color"
v-html="icons.pin">
v-html="icons.pin"
>
</div>
</div>
</template>

View File

@@ -60,7 +60,7 @@ export function mapActions (actions) {
const res = {};
normalizeMap(actions).forEach(({ key, val }) => {
res[key] = function mappedAction (...args) {
res[key] = async function mappedAction (...args) {
return this.$store.dispatch.apply(this.$store, [val].concat(args)); // eslint-disable-line prefer-spread, max-len
};
});

View File

@@ -41,7 +41,7 @@ export default class Store {
// Actions should be called using store.dispatch(ACTION_NAME, ...ARGS)
// They get passed the store instance and any additional argument passed to dispatch()
dispatch (type, ...args) {
async dispatch (type, ...args) {
const action = this._actions[type];
if (!action) throw new Error(`Action "${type}" not found.`);

View File

@@ -0,0 +1,108 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Home from '@/components/static/home.vue';
import Store from '@/libs/store';
import * as Analytics from '@/libs/analytics';
const localVue = createLocalVue();
localVue.use(Store);
describe('Home', () => {
let registerStub;
let socialAuthStub;
let store;
let wrapper;
function mountWrapper (query) {
return shallowMount(Home, {
store,
localVue,
mocks: {
$t: string => string,
$route: { query: query || {} },
},
});
}
async function fillOutUserForm (username, email, password) {
await wrapper.find('#usernameInput').setValue(username);
await wrapper.find('input[type=email]').setValue(email);
await wrapper.findAll('input[type=password]').setValue(password);
}
beforeEach(() => {
registerStub = sinon.stub();
socialAuthStub = sinon.stub();
store = new Store({
state: {},
getters: {},
actions: {
'auth:register': registerStub,
'auth:socialAuth': socialAuthStub,
},
});
sinon.stub(Analytics, 'track');
wrapper = mountWrapper();
});
afterEach(sinon.restore);
it('has a visible title', () => {
expect(wrapper.find('h1').text()).to.equal('motivateYourself');
});
describe('signup form', () => {
it('registers a user from the form', async () => {
const username = 'newUser';
const email = 'rookie@habitica.com';
const password = 'ImmaG3tProductive!';
await fillOutUserForm(username, email, password);
await wrapper.find('form').trigger('submit');
expect(registerStub.calledOnce).to.be.true;
expect(registerStub.getCall(0).args[1]).to.deep.equal({
username,
email,
password,
passwordConfirm: password,
groupInvite: '',
});
});
it('registers a user with group invite if groupInvite in the query', async () => {
const groupInvite = 'TheBestGroup';
wrapper = mountWrapper({ groupInvite });
await fillOutUserForm('invitedUser', 'invited@habitica.com', '1veGotFri3ndsHooray!');
await wrapper.find('form').trigger('submit');
expect(registerStub.calledOnce).to.be.true;
expect(registerStub.getCall(0).args[1].groupInvite).to.equal(groupInvite);
});
it('registers a user with group invite if p in the query', async () => {
const p = 'ThePiGroup';
wrapper = mountWrapper({ p });
await fillOutUserForm('alsoInvitedUser', 'invited2@habitica.com', '1veGotFri3nds2!');
await wrapper.find('form').trigger('submit');
expect(registerStub.calledOnce).to.be.true;
expect(registerStub.getCall(0).args[1].groupInvite).to.equal(p);
});
it('registers a user with group invite invite if both p and groupInvite are in the query', async () => {
const groupInvite = 'StillTheBestGroup';
wrapper = mountWrapper({ p: 'LesserGroup', groupInvite });
await fillOutUserForm('doublyInvitedUser', 'invited3@habitica.com', '1veGotSm4rtFri3nds!');
await wrapper.find('form').trigger('submit');
expect(registerStub.calledOnce).to.be.true;
expect(registerStub.getCall(0).args[1].groupInvite).to.equal(groupInvite);
});
});
});

View File

@@ -76,15 +76,15 @@ describe('Store', () => {
});
describe('actions', () => {
it('can dispatch an action', () => {
expect(store.dispatch('getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
it('can dispatch an action', async () => {
expect(await store.dispatch('getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
});
it('can dispatch a nested action', () => {
expect(store.dispatch('nested:getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
it('can dispatch a nested action', async () => {
expect(await store.dispatch('nested:getName', 1, 2, 3)).to.deep.equal(['test', 1, 2, 3]);
});
it('throws an error is the action doesn\'t exists', () => {
it('throws an error if the action doesn\'t exists', () => {
expect(() => store.dispatched('wrong')).to.throw;
});
});
@@ -140,9 +140,9 @@ describe('Store', () => {
data: {
title: 'internal',
},
created () {
expect(this.getName('123')).to.deep.equal(['test', '123']);
expect(this.getNameRenamed('123')).to.deep.equal(['test', '123']);
async created () {
expect(await this.getName('123')).to.deep.equal(['test', '123']);
expect(await this.getNameRenamed('123')).to.deep.equal(['test', '123']);
done();
},
methods: {

View File

@@ -1,14 +1,18 @@
import Vue from 'vue';
import { createLocalVue } from '@vue/test-utils';
import groupsUtilities from '@/mixins/groupsUtilities';
import { TAVERN_ID } from '@/../../common/script/constants';
import generateStore from '@/store';
import Store from '@/libs/store';
const LocalVue = createLocalVue();
LocalVue.use(Store);
describe('Groups Utilities Mixin', () => {
let instance; let
user;
let instance;
let user;
before(() => {
instance = new Vue({
instance = new LocalVue({
store: generateStore(),
mixins: [groupsUtilities],
});

View File

@@ -69,13 +69,13 @@ export function getGroupUrl (group) {
export async function sendTxn (mailingInfoArray, emailType, variables, personalVariables) {
mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray]; // eslint-disable-line no-param-reassign, max-len
variables = [ // eslint-disable-line no-param-reassign, max-len
variables = [ // eslint-disable-line no-param-reassign
{ name: 'BASE_URL', content: BASE_URL },
].concat(variables || []);
// It's important to pass at least a user with its `preferences`
// as we need to check if he unsubscribed
mailingInfoArray = mailingInfoArray // eslint-disable-line no-param-reassign, max-len
mailingInfoArray = mailingInfoArray // eslint-disable-line no-param-reassign
.map(mailingInfo => (mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo))
// Always send reset-password emails
// Don't check canSend for non registered users as already checked before

View File

@@ -166,9 +166,7 @@ async function inviteByEmail (invite, group, inviter, req, res) {
} else {
userReturnInfo = invite.email;
let cancelledPlan = false;
if (group.hasActiveGroupPlan() && !group.hasNotCancelled()) cancelledPlan = true;
const cancelledPlan = group.hasActiveGroupPlan() && !group.hasNotCancelled();
const groupQueryString = JSON.stringify({
id: group._id,
inviter: inviter._id,