You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Closes#173. Notes:
- avoriaz has some information on migrating to Vue Test Utils (VTU):
https://github.com/eddyerburgh/avoriaz/blob/master/docs/guides/migrating-to-vue-test-utils.md
- VTU wrappers no longer have a data() method. Using the `vm` property
to access data properties instead.
- Replace wrapper methods:
- avoriaz() getProp() becomes VTU props()
- hasClass() becomes classes()
- getAttribute() and hasAttribute() become attributes()
- first() becomes get() or getComponent()
- find() becomes findAll() or findAllComponents()
- Unless doing an existence check, in which case we call either
find() or findComponent(), then call exists().
- For a wrapper of a Vue component, access the element using the
`element` property rather than using vm.$el.
- The VTU wrapper method text() trims the text. The avoriaz wrapper
method did not, so we often called trim() after text(). We now access
element.textContent directly in the rare case that we do not want the
text to be trimmed.
- The VTU wrapper method html() prettifies the HTML, which the avoriaz
wrapper method did not. That may be useful for us in the future, but
for our few existing tests that used the method (in
SubmissionFeedEntry), we now use element.outerHTML instead.
- Replace our `trigger` object with the VTU wrapper methods trigger(),
setValue(), and setChecked().
- But keeping a version of trigger.dragAndDrop(), in the new
test/util/file.js.
- The methods of the `trigger` object returned a promise that resolved
to the avoriaz wrapper. The VTU wrapper methods return a promise
that doesn't resolve to a value, so I had to restructure some
promise chains. In many cases, I rewrote the promise chain using
async/await.
- It is now possible to specify options when using VTU to trigger an
event, so we don't need to use jQuery to mock events. I removed
jQuery from FormNew and FormAttachmentUploadFiles, which only used
jQuery so that we could mock events in testing.
- The tests of FormNew have good examples of some of these changes.
- Use the VTU wrapper method emitted() instead of using Sinon to fake
$emit(). (The tests of DateRangePicker have good examples of this
change.)
- Replace attachToDocument with attachTo.
- avoriaz allowed a wrapper to be passed as a slot, but VTU does not.
However, VTU allows a template string to be passed, which avoriaz did
not. The only place this comes up is a test of LinkIfCan, where the
wrapper could be replaced with a template string.
- Use the parentComponent option of mount() to account for i18n custom
blocks. Previously, we overwrote the setProps() wrapper method.
- We inject the router in fewer tests. In some cases, we are able to
switch from load() to mount(), making the test more isolated and often
making it synchronous. (The tests of Download and FormDraftChecklist
have good examples of that.)
- If a component uses <router-link>, VTU can stub that.
- If a component uses $route, VTU can mock that.
- In order for VTU to mock $route, we use createLocalVue() to create a
copy of Vue with the router and a copy without, which is what VTU
recommends.
- That means that src/setup.js can no longer import src/plugins.js.
Instead, src/plugins.js is imported in src/main.js.
- For some components, these changes result in a natural sequence of
tests: tests first use mount() to test the behavior of the component
before a request, then use mockHttp() to test behavior before a
successful response, then use load() to test a route change and
other behavior after a successful response. For some components (for
example, AccountLogin), I reordered tests to match this sequence.
- load() will now inject the router only if it is mounting the root
component (App). In other cases, it stubs <router-link> and mocks
$route.
- The documentation for mockHttp() and load() describes some of this
in more detail, including guidelines around which utility function
to use in different cases.
- One case where mocking $route doesn't work is if an async component
uses $route: see
vuejs/vue-test-utils#1486. This was an
issue for ProjectSubmissionOptions, so it is no longer loaded async.
I had actually already wanted not to load it async: it's not a large
component, and it may be needed soon after the page renders. I think
I set it up to load async because multiple components import it, but
I don't think I realized at the time that webpack will automatically
split out a large shared component into its own .js file.
- test/index.js used to contain some complexity related to the router,
but with these changes in place, it felt like the right time to
remove the previous workaround. I was able to simplify things by
having the router use abstract mode in testing. This change also
sped up tests significantly.
- Previously, one of the tests of AccountClaim would fail
intermittently depending on how quickly the Password async component
loaded. After these changes, the test failed more often (maybe
because some of the tests before it are now faster?), so I updated
the test to wait for the component to load. This involved a small
change to FormGroup.
- There are benefits to mocking/stubbing for Vue Router, but there seem
to be fewer benefits to doing so for our other plugins, Vuex and Vue
I18n. The router contains a fair amount of logic and implements some
of that behavior (some of which can be async) as soon as it is
injected into a component. However, the same is not true of Vuex or
Vue I18n.
- I'm not sure whether avoriaz created a parent component when mounting,
but Vue Test Utils seems to. We destroy the parent component after
each test. We used to destroy the component before removing it from
the DOM, but we now do those in the reverse order. That matches what
avoriaz and VTU do, and I don't see any obvious issues with that
approach. (We introduced the previous logic in
304e5db, maybe because Modal at the
time used a ref in its beforeDestroy hook. However, that is no longer
the case.)
- VTU automatically sets Vue.config.productionTip and
Vue.config.devtools to `false`. This removes a message about Vue
devtools that was previously shown in testing. Since VTU now sets
productionTip, I have moved that configuration from src/setup.js to
src/main.js.
- VTU wrappers have an isVisible() method, but I think we should
continue using our visible() and hidden() assertions. One reason for
that is that both assertions can be used to test style-based
visibility. Also, the hidden() assertion is more specific than
not.visible().
- Make other improvements to testing, including:
- Remove deprecated functions and properties:
- Replace mockRoute() with load() or mockHttp().
- Replace mountAndMark() with mount().
- Replace mockHttp().standardButton() with
mockHttp().testStandardButton().
- Replace testData.administrators with testData.standardUsers.
- Remove the disabled() assertion.
- Remove String.prototype.iTrim().
- Use mockHttp().testModalToggles() in more tests, and call it with a
single argument consistently.
- Mount the root component (App) in fewer tests.
- I initially made this change in too many cases. I've added a check
to load() to help prevent that going forward.
- Add an id or class attribute to certain elements to make it easier
to select elements in testing. In other cases, shorten an existing
class name.
- Move tests:
- From UserList to router.spec.js
- From ProjectRow to ProjectIntroduction
- Remove unneeded tests.
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+11-24Lines changed: 11 additions & 24 deletions
Original file line number
Diff line number
Diff line change
@@ -56,24 +56,17 @@ We use Vue.js along with Vue Router, Vuex, and Vue CLI.
56
56
57
57
ODK Central Frontend uses jQuery in limited ways.
58
58
59
-
Wherever possible, we try to use Vue instead of jQuery. Vue will not always know about or respect the changes that jQuery makes, and using jQuery can add complexity to a component. It can also add complexity to testing: for example, we generally use avoriaz for testing, but if you use jQuery in a component, then in testing, you may need to use jQuery's `trigger()` method rather than avoriaz's `trigger()` method.
59
+
Wherever possible, we try to use Vue instead of jQuery. Vue will not always know about or respect changes that jQuery makes to the DOM, and using jQuery can add complexity to a component.
60
60
61
-
That said, there are a couple of occasions in which we reach for jQuery:
62
-
63
-
- We use some of Bootstrap's jQuery plugins.
64
-
- avoriaz does not allow you to mock events. However, jQuery does, so we sometimes use jQuery in order to facilitate testing.
65
-
66
-
One thing to keep in mind when using jQuery is that you will have to manually remove any jQuery listeners and data, perhaps when the component is destroyed.
67
-
68
-
If possible despite our use of Bootstrap, we may wish to remove jQuery from Frontend in the future. For that reason, if you have a choice between using jQuery and vanilla JavaScript, you should consider using the latter. Remember though that as with jQuery, Vue will not always know about or respect the changes that you make using vanilla JavaScript.
61
+
That said, we make use of some of Bootstrap's jQuery plugins. We may replace those in the future, after which we may be able to remove jQuery. With that in mind, if you have a choice between using jQuery and vanilla JavaScript, consider using the latter. Remember though that as with jQuery, Vue will not always know about or respect changes to the DOM that you make using vanilla JavaScript.
69
62
70
63
### Bootstrap
71
64
72
-
ODK Central Frontend uses Bootstrap 3. (However, we are considering [moving to Bootstrap 4](https://github.com/getodk/central-frontend/issues/142). Let us know if that is something you can help with!)
65
+
ODK Central Frontend uses Bootstrap 3.
73
66
74
-
Frontend's [global styles](/src/assets/scss/app.scss) override some of Bootstrap's, as do the styles of Frontend components that correspond to a Bootstrap component (for example, `Modal`). However, we tend to stick pretty closely to Bootstrap, and you should be able to use most of Bootstrap's examples with only small changes. If you are creating a new component that is similar to an existing one, you may find it useful to base the new component off the existing one.
67
+
Frontend's [global styles](/src/assets/scss/app.scss) override some of Bootstrap's, as do the styles of Frontend components that correspond to a Bootstrap component (for example, `Modal`). However, we tend to stick pretty closely to Bootstrap, and you should be able to use many of Bootstrap's examples with only small changes. If you are creating a new component that is similar to an existing Frontend component, you may find it useful to base the new component off the existing one.
75
68
76
-
We use some, but not all, of Bootstrap's jQuery plugins ([`/src/bootstrap.js`](/src/bootstrap.js)). We try to limit our use of Bootstrap's plugins, because they use jQuery, and jQuery tends to add complexity to components and testing in the ways described above. For example, if you use a Bootstrap plugin, then in testing, you may need to use jQuery's `trigger()` method rather than avoriaz's.
69
+
We use a limited number of Bootstrap's jQuery plugins: see the [section above](https://github.com/getodk/central-frontend/blob/master/CONTRIBUTING.md#jquery) on jQuery.
77
70
78
71
### Global Utilities
79
72
@@ -105,6 +98,8 @@ To learn how a given component works, one of the best places to start is how the
105
98
- Does it have slots?
106
99
- Does it emit events?
107
100
101
+
A component can also communicate with other components using the Vuex store. For example, a component may use [response data](https://github.com/getodk/central-frontend/blob/master/CONTRIBUTING.md#response-data) that another component requested.
102
+
108
103
### Component Names
109
104
110
105
We specify a name for every component, which facilitates the use of the Vue devtools. In general, we try not to use component names to drive behavior: in most ways, renaming a component should have no effect.
// This comment will be added for each of the messages within "fruit".
192
-
fruit: {
187
+
"fruit": {
193
188
"apple": "Apple",
194
189
"banana": "Banana"
195
190
}
@@ -322,15 +317,15 @@ Our tests use a number of external packages:
322
317
- Should.js, for assertions
323
318
- Sinon.JS, for spies and stubs
324
319
- faker.js, to generate test data
325
-
-avoriaz, to test Vue components
320
+
-Vue Test Utils, to test Vue components
326
321
327
322
`npm run test` runs [`/test/index.js`](/test/index.js), which mocks global utilities and sets up Mocha hooks.
328
323
329
324
We extend Should.js assertions in [`/test/assertions.js`](/test/assertions.js).
330
325
331
-
[avoriaz](https://eddyerburgh.gitbooks.io/avoriaz/content/) renders Vue components for testing, allowing you to test that a component renders and behaves as expected. We have built some functionality on top of avoriaz, in particular [`mount()`](/test/util/lifecycle.js) and [`trigger`](/test/util/event.js). We define components used only for testing in [`/test/util/components/`](/test/util/components/).
326
+
[Vue Test Utils](https://vue-test-utils.vuejs.org/) renders Vue components for testing, allowing you to test that a component renders and behaves as expected. We have built some functionality on top of Vue Test Utils, in particular [`mount()`](/test/util/lifecycle.js). We define components used only for testing in [`/test/util/components/`](/test/util/components/).
332
327
333
-
Many tests involve sending a request. You can mock a series of request-response cycles by using `load()` or `mockHttp()`, defined in [`test/util/http.js`](/test/util/http.js). You can use these to implement common tests, for example, testing some standard button things: see [`/test/util/http/common.js`](/test/util/http/common.js).
328
+
Many tests involve sending a request. You can mock a series of request-response cycles by using `load()` or `mockHttp()`, defined in [`/test/util/http.js`](/test/util/http.js). You can use these to implement common tests, for example, testing some standard button things: see [`/test/util/http/common.js`](/test/util/http/common.js).
334
329
335
330
As provided by default by Mocha, add `.only` after any `describe()` or `it()` call in the tests to run only the marked tests. For example:
336
331
@@ -349,11 +344,3 @@ We generate and store test data specific to ODK Central using the [`testData`](/
349
344
Most Backend resources have a `createdAt` property. To generate an object whose `createdAt` property is in the past, use the `createPast()` method of the store or view. To generate an object whose `createdAt` property is set to the current time, use `createNew()`. Most of the time, you will use `createPast()`. For a test that mounts a component, use `createPast()` to set up data that exists before the component is mounted. Use `createNew()` for data created after the component is mounted, for example, after the component sends a POST request. You can pass options to `createPast()` and `createNew()`; each store accepts a different set of options.
350
345
351
346
To learn more about stores and views, see [`/test/data/data-store.js`](/test/data/data-store.js).
352
-
353
-
#### Improvements to Testing
354
-
355
-
We want to improve our testing in two major ways. (Let us know if this is something you can help with!)
356
-
357
-
- Moving away from Karma
358
-
- Vue CLI does not offer core support for Karma. Perhaps the easiest move would be to the unit-mocha plugin for Vue CLI, which uses JSDOM.
0 commit comments