Skip to content

Commit 304e5db

Browse files
committed
Destroy component after each test, hiding any modal
1 parent 2ec8788 commit 304e5db

File tree

10 files changed

+101
-21
lines changed

10 files changed

+101
-21
lines changed

lib/components/field-key/list.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { deflate } from 'pako/lib/deflate';
5858
import FieldKeyNew from './new.vue';
5959
import alert from '../../mixins/alert';
6060
import highlight from '../../mixins/highlight';
61+
import modal from '../../mixins/modal';
6162
import request from '../../mixins/request';
6263

6364
const QR_CODE_TYPE_NUMBER = 0;
@@ -123,7 +124,7 @@ const POPOVER_CONTENT_TEMPLATE = `
123124
export default {
124125
name: 'FieldKeyList',
125126
components: { FieldKeyNew },
126-
mixins: [alert(), request(), highlight()],
127+
mixins: [alert(), request(), modal('newFieldKey'), highlight()],
127128
data() {
128129
return {
129130
alert: alert.blank(),

lib/components/form/list.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,13 @@ import moment from 'moment';
7777

7878
import FormNew from './new.vue';
7979
import alert from '../../mixins/alert';
80+
import modal from '../../mixins/modal';
8081
import request from '../../mixins/request';
8182

8283
export default {
8384
name: 'FormList',
8485
components: { FormNew },
85-
mixins: [alert(), request()],
86+
mixins: [alert(), request(), modal('newForm')],
8687
data() {
8788
return {
8889
alert: alert.blank(),

lib/components/modal.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ export default {
6868
$(this.$refs.modal)
6969
.on('shown.bs.modal', () => this.$emit('shown'))
7070
.on('hidden.bs.modal', () => this.$emit('hidden'));
71-
this.toggle(this.state);
71+
if (this.state) this.toggle(this.state);
7272
},
7373
beforeDestroy() {
74+
this.toggle(false);
7475
$(this.$refs.modal).off();
7576
},
7677
methods: {
@@ -82,6 +83,10 @@ export default {
8283
modularity, because parent components can use this modal component without
8384
knowing that it uses Bootstrap. */
8485
toggle(state) {
86+
// For tests in which the component is not attached to the document, we
87+
// return immediately rather than calling modal(), because it has side
88+
// effects on the document.
89+
if (!$.contains(document.body, this.$refs.modal)) return;
8590
$(this.$refs.modal).modal(state ? 'show' : 'hide');
8691
},
8792
modalMousedown(e) {

lib/components/user/list.vue

+7-1
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,18 @@ import UserNew from './new.vue';
6868
import UserResetPassword from './reset-password.vue';
6969
import alert from '../../mixins/alert';
7070
import highlight from '../../mixins/highlight';
71+
import modal from '../../mixins/modal';
7172
import request from '../../mixins/request';
7273

7374
export default {
7475
name: 'UserList',
7576
components: { UserNew, UserResetPassword },
76-
mixins: [alert(), request(), highlight()],
77+
mixins: [
78+
alert(),
79+
request(),
80+
modal(['newUser', 'resetPassword']),
81+
highlight()
82+
],
7783
data() {
7884
return {
7985
alert: alert.blank(),

lib/mixins/modal.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright 2017 Super Adventure Developers
3+
See the NOTICE file at the top-level directory of this distribution and at
4+
https://github.com/nafundi/super-adventure/blob/master/NOTICE.
5+
6+
This file is part of Super Adventure. It is subject to the license terms in
7+
the LICENSE file found in the top-level directory of this distribution and at
8+
https://www.apache.org/licenses/LICENSE-2.0. No part of Super Adventure,
9+
including this file, may be copied, modified, propagated, or distributed
10+
except according to the terms contained in the LICENSE file.
11+
*/
12+
export default (modalOrModals) => {
13+
const modalNames = typeof modalOrModals === 'string'
14+
? [modalOrModals]
15+
: modalOrModals;
16+
const hideModals = function hideModals(to, from, next) {
17+
for (const name of modalNames)
18+
this[name].state = false;
19+
this.$nextTick(next);
20+
};
21+
// @vue/component
22+
return {
23+
beforeRouteUpdate: hideModals,
24+
beforeRouteLeave: hideModals
25+
};
26+
};

test/components/user/new.spec.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ https://www.apache.org/licenses/LICENSE-2.0. No part of Super Adventure,
99
including this file, may be copied, modified, propagated, or distributed
1010
except according to the terms contained in the LICENSE file.
1111
*/
12-
import { mount } from 'avoriaz';
13-
1412
import Alert from '../../../lib/components/alert.vue';
1513
import UserList from '../../../lib/components/user/list.vue';
1614
import UserNew from '../../../lib/components/user/new.vue';
@@ -68,11 +66,11 @@ describe('UserNew', () => {
6866
const newUser = { id: 2, email: '[email protected]' };
6967

7068
beforeEach(() => mockHttp()
71-
.request(() => {
72-
page = mount(UserList);
73-
})
69+
.mount(UserList)
7470
.respondWithData([mockUser()])
75-
.complete()
71+
.afterResponse(component => {
72+
page = component;
73+
})
7674
.request(() => clickCreateButton(page).then(submitForm))
7775
.respondWithData(newUser)
7876
.respondWithData([mockUser(), newUser]));

test/components/user/reset-password.spec.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ https://www.apache.org/licenses/LICENSE-2.0. No part of Super Adventure,
99
including this file, may be copied, modified, propagated, or distributed
1010
except according to the terms contained in the LICENSE file.
1111
*/
12-
import { mount } from 'avoriaz';
13-
1412
import Alert from '../../../lib/components/alert.vue';
1513
import UserList from '../../../lib/components/user/list.vue';
1614
import UserResetPassword from '../../../lib/components/user/reset-password.vue';
@@ -60,11 +58,11 @@ describe('UserResetPassword', () => {
6058
describe('after successful response', () => {
6159
let page;
6260
beforeEach(() => mockHttp()
63-
.request(() => {
64-
page = mount(UserList);
65-
})
61+
.mount(UserList)
6662
.respondWithData([mockUser()])
67-
.complete()
63+
.afterResponse(component => {
64+
page = component;
65+
})
6866
.request(() => openModal(page).then(confirmResetPassword))
6967
.respondWithSuccess());
7068

test/destroy.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2017 Super Adventure Developers
3+
See the NOTICE file at the top-level directory of this distribution and at
4+
https://github.com/nafundi/super-adventure/blob/master/NOTICE.
5+
6+
This file is part of Super Adventure. It is subject to the license terms in
7+
the LICENSE file found in the top-level directory of this distribution and at
8+
https://www.apache.org/licenses/LICENSE-2.0. No part of Super Adventure,
9+
including this file, may be copied, modified, propagated, or distributed
10+
except according to the terms contained in the LICENSE file.
11+
*/
12+
import { mount } from 'avoriaz';
13+
14+
let componentToDestroy = null;
15+
16+
export const markComponentForDestruction = (component) => {
17+
if (componentToDestroy != null)
18+
throw new Error('another component has already been marked for destruction and has not been destroyed yet');
19+
componentToDestroy = component;
20+
};
21+
22+
export const destroyMarkedComponent = () => {
23+
if (componentToDestroy == null) return;
24+
componentToDestroy.vm.$destroy();
25+
if (componentToDestroy.vm.$el.parentNode != null)
26+
$(componentToDestroy.vm.$el).remove();
27+
componentToDestroy = null;
28+
};
29+
30+
export const mountAndMark = (component, mountOptions = {}) => {
31+
const wrapper = mount(component, mountOptions);
32+
markComponentForDestruction(wrapper);
33+
return wrapper;
34+
};

test/http.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ including this file, may be copied, modified, propagated, or distributed
1010
except according to the terms contained in the LICENSE file.
1111
*/
1212
import Vue from 'vue';
13-
import { mount as avoriazMount } from 'avoriaz';
1413

1514
import Alert from '../lib/components/alert.vue';
1615
import Spinner from '../lib/components/spinner.vue';
16+
import { mountAndMark } from './destroy';
1717

1818
const REQUEST_METHODS = ['get', 'post', 'delete'];
1919

@@ -84,11 +84,15 @@ If you already have a mounted component, you can skip mockHttp().mount():
8484
...
8585
8686
mockHttp()
87-
.request(component => {
87+
.request(() => {
8888
mockLogin();
8989
component.vm.$router.push('/users');
9090
})
9191
92+
...
93+
94+
// Destroy component.
95+
9296
The important thing is that you call either mount() or request() (or both).
9397
9498
After specifying the request, specify the response:
@@ -190,7 +194,7 @@ class MockHttp {
190194
throw new Error('cannot call mount() more than once in a single chain');
191195
if (this._previousPromise != null)
192196
throw new Error('cannot mount component after first series in chain');
193-
const mount = () => avoriazMount(component, options);
197+
const mount = () => mountAndMark(component, options);
194198
return new MockHttp(
195199
this._previousPromise,
196200
this._component,

test/setup.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Vue from 'vue';
1313
import 'should';
1414

1515
import { MockLogger } from './util';
16+
import { destroyMarkedComponent } from './destroy';
1617
import { setHttp } from './http';
1718
import '../lib/setup';
1819

@@ -24,6 +25,12 @@ setHttp((...args) => {
2425
});
2526

2627
afterEach(() => {
27-
// Remove a component attached to the document if there is one.
28-
$('body > script:last-of-type + *').remove()
28+
destroyMarkedComponent();
29+
30+
const afterScript = $('body > script:last-of-type + *');
31+
if (afterScript.length > 0) {
32+
// eslint-disable-next-line no-console
33+
console.log(`Unexpected element: ${afterScript[0].outerHTML}`);
34+
throw new Error('Unexpected element after last script element. Have all components and Bootstrap elements been destroyed?');
35+
}
2936
});

0 commit comments

Comments
 (0)