-
Details coming soon.
-
Tour of a Component
===
Include discussion of why build simple custom components..
- reusability (e.g. fetching locations)
- testability
Custom FarmData2 Vue Components.
- Create a directory for each component
.vue
file for the component.comp.cy.js
files for the component tests
-
use
addComponent.bash
- describe its use...
- creates its own branch
- To add component in a branch...
- run it
- switch to branch
- then commit
- describe its use...
-
modify
.vue
file as appropriate. -
modify the
.comp.cy.js
file as appropriate.- See component testing below...
-
Use other components as examples!!
- copy / paste and modify liberally.
-
Include a
<div>
or a<span>
inside<template>
to ensure thatcy.get
andcy.find
will work consistently across components. Without this extra level of nesting it seems that the Vue component element containing the component can sometimes be optimized away. Thus, trying tocy.get
that element will fail in tests. -
Many components will be one or more Form Group elements.
- the label for a required input element is followed by
<sup v-if="required" class="text-danger">*</sup>
.
- the label for a required input element is followed by
-
Each input element must have:
<BFormInvalidFeedback>
- with its
state
propv-bound
to theinvalidStyling
prop
- with its
-
Elements must also have:
-
id
must be set for each input element. -
All testable elements in the component must have a
data-cy
attribute.- e.g. every input element must have a
data-cy
attribute. - all
data-cy
in a component should be prefixed by the component name or abbreviation.- E.g.
data-cy="crop-group
orlocation-0
.
- E.g.
- e.g. every input element must have a
-
their
state
propv-bound
to theinvalidStyling
computed property -
BRow
andBCol
can be used to create more complex layouts. -
BFormSelect
elements should begin with{ value: null, text: '' }
.- to allow them to be blank when form is reset.
-
attributes on component/element tags should appear in the order:
- id
- data-cy
- classes, properties
- v-if / v-show
- v-model
- v-bind
- v-on
-
imports appear after
<script>
and before the component comment.- This is where custom components are imported.
-
elements of the script should be in the order:
- name
- components - may be empty as Bootstrap-Vue-Next components are automatically imported.
- When using custom components they should be imported and then listed here.
- emits
- props
- data
- computed
- methods
- created
-
list props in alphabetical order.
-
All components must have a
required
prop.- Indicates that inputs are required field if present.
- required fields are indicated by a red asterisk
-
All components must have a
showValidityStyling
prop-
This prop is set by the entry point to indicates that bootstrap styling should be shown for inputs.
-
The component indicates the validity of inputs using its
isValid
computed property.- If
isValid
returns false if the component should block submission of the form that contains it.- If the component is not required then blank/empty/no value/etc is considered a valid value.
- If the component is required then blank/empty/no value/etc is not considered a valid value.
- The submit function in the page will use this value to determine if the submit button should be enabled or disabled and whether or not to add non-required fields to the submission.
- If
-
The component indicates the type of styling to be used using its
validityStyling
computed property.- This function returns:
true
to apply valid styling (green check)false
to apply invalid styling (red x and invalidFeedbackText)null
to not apply either styling. This should be applied when showValidityStyling is set false, and also to non-required inputs that are blank/empty.
- This function returns:
-
The
showValidityStyling
prop should be is set by the entry point totrue
when "Submit" is clickedfalse
when "Reset" is clickedfalse
when a submission is successful.
-
-
Components manage props, state and events to allow page to change state via the prop.
- The component provides a
prop
for every value that is collected by the component- An entry point can
v-model
the associatedprop
to an element in itsdata.form
- An entry point can
- The
prop
is assigned to some internal state (indata
) - The internal state is
v-model
ed to the input element or sub-component. - The component watches the
props
- when a watched
prop
changes the component updates the internal state.
- when a watched
- The component listens (
v-on
) forupdate
events from the input element or sub-component.- When an
update
event occurs the component emits anupdated:prop_name
event with a payload communicating the new value of theprop
to the entry point or parent component.
- When an
- The component provides a
-
NOTE: Keeping internal state allows for more thorough testing of the component apart from a page that changes props in response to events (i.e. closes the loop). It make this loop more explicit and makes the code more idiomatic across components.
-
All events emitted must be kabob-case.
-
All components must emit a
ready
event when they are ready to be used in tests.- e.g. any API calls that were made in
created
have completed.
- e.g. any API calls that were made in
-
If an error occurs, the component must emit an
error
event with aString
message as the payload. The component should also print more detailed information to the console for debugging. SeeCropSelector
for an example.- The entrypoint will handle the error.
-
All components must emit a
valid
event any time theirisValid
computed property changes.- This event will have a
boolean
payload indicating if the component's value is valid or not. - The component
watch
es theisValid
computed property for changes and emits this event. - This event should be emitted when:
- any time the component's
isValid
computed property changes. This should be done with awatch
for theisValid
computed property.- If
isValid
isnull
then this watcher should not emit the event.
- If
- any time the component's
- This event will have a
-
If a component only contains one element, then it should be wrapped in a
<div>
element. See theCommentComponent
component for an example and explanation.
A component can check the permissions of the logged in farmOS user using appropriate function in farmosUtil.js
.
If a permission needs to be checked that is not yet supported it can be added to the $perms
array in the permissions
function in modules/farm_fd2/src/module/Controller/FD2_Controller.php
file.
-
use
bin/test.bash
test.bash --comp --gui
- initially to run individual tests.test.bash --comp --glob="**/CompName/*.comp.cy.js"
- to run all test on the component headless.
Each test file is structured as follows:
import LocationSelector from '@comps/LocationSelector/LocationSelector.vue';
describe('Test the default LocationSelector content', () => {
beforeEach(() => {
cy.restoreLocalStorage();
cy.restoreSessionStorage();
});
afterEach(() => {
cy.saveLocalStorage();
cy.saveSessionStorage();
});
it('Check for the SelectorBase element', () => {
const readySpy = cy.spy().as('readySpy');
cy.mount(LocationSelector, {
props: {
onReady: readySpy,
},
});
cy.get('@readySpy')
.should('have.been.calledOnce')
.then(() => {
cy.get('[data-cy="location-selector"]').should('exist');
});
});
});
Best practice is to reset the database state before tests are run. Doing this before every test or even at the start of every file adds significantly to the runtime of the test suite. FarmData2 compromises by resetting the database to the DB that was most recently installed (i.e. using installDB.bash
) before each test run. A test run is one cypress command (e.g. as is done by test.bash --comp
). Any test that absolutely requires a clean database (i.e. cannot tolerate changes made by prior tests) can reset it in its before
hook using the following code:
before(() => {
cy.task('initDB');
});
You can change the database that will be used for testing by using the installDB.bash
script manually prior to running the tests. This is useful when you want to run tests against a pre-release of the sample database. For example:
installDB.bash --release=v3.1.0-development.3 --asset=db.sample.tar.gz
Note: Every test it
should wait for the ready
event to be emitted before performing any tests. In some components this will be immediately, in others it will wait for an API call to complete. This is included in all tests for consistency and to reduce test flake.
Use: cy.task('log', 'message')
to log messages to the console.
Use: cy.task('logObject', obj)
to log an object to the console.
-
Visible in the console when running headless.
-
Click on the task in the test events output to print to console in Cypress gui.
-
Add pointers to canonical examples of tests:
- basic existence
- checking styles
- events
- interacting with elements
- generating network errors
- changing props
- Others???
- basic existence
-
Every component should have tests that:
-
These should be in approximately the order they can be written as a new component is created.
-
check initial content (
*.content.comp.cy.js
)- Check required props and default prop values
- Set only required props in the test.
- Check static content not controlled by props.
- check effect of all required props
- e.g.
label
,invalidFeedback
, etc.
- e.g.
- Check default values of all optional props.
- e.g.
required
,showValidityStyling
, etc.
- e.g.
- look at
<template>
to see what elements need to be tested.- check existence of
data-cy
elements usingexist
. - use
have.class
/have.text
/have.value
on elements - If component uses other components, check sub-component elements as necessary.
- check existence of
- can usually be done in one test.
- check that non-default optional
prop
values are handled correctly- Set each optional prop to a non-default value and check for its effect.
- use
have.class
/have.text
/have.value
on elements- If component uses other components, check sub-component elements as necessary.
- ideally one test per optional prop.
- check that all content loaded via API on creation is loaded correctly.
- e.g. crops or fields vs greenhouses in LocationSelector.
- Check required props and default prop values
-
check styling (
*.styling.comp.cy.js
)- check that the type of valid/invalid styling to be shows as expected based on
isValid
required
,showValidityStyling
and any other criteria that is necessary.- This is often an enumeration test that checks all combinations of these values.
- If the computations for displaying the valid/invalid styling are done by a sub-component (e.g.
SelectorBase
) then this test is not required because the prop will have been tested by thecontent
test and the styling will have been tested by the sub-component.- The
content
test will have checked thatshowValidityStyling
is passed and the sub-component's tests will have checked that the type of styling to show is correct.
- The
- If a component is never styled (e.g.
CommentBox
orTextDisplay
) then this test is not required.
- check that the type of valid/invalid styling to be shows as expected based on
-
check events (
*.events.comp.cy.js
)- check that all events listed in the component's
emits
property are emitted properly- If the component computes
isValid
for itself then thevalid
event should be tested exhaustively. - At least one test per event for all other events:
- Do something that should cause the event to be emitted.
- e.g. change the selection, type some text, etc...
- e.g. Use
cy.intercept
to generate network errors on the appropriate route.
- Check that it is emitted and has the correct payload.
- Do something that should cause the event to be emitted.
- If using a sub-component check propagated events.
- It is not necessary to check the payloads for these events or all circumstances for their emission - the tests of the sub-component will have done that.
- The
ready
event is used in all tests so does not need to be tested separately.
- If the component computes
- check that all events listed in the component's
-
check other behaviors (
*.behavior.comp.cy.js
)- check that changing each reactive or watched prop has the proper effect.
- need to give example or point to one where props are changed.
- e.g. changes to prop changes the component as desired.
- e.g. watches and deep watches work.
- Note: if using a sub-component that reacts to a prop, the
content
tests should be sufficient. They show the prop is "wired" correctly, the sub-component tests show that it is reactive to the prop.
- check that actions in the component have the proper effect.
- e.g. clicking buttons, validating value (required, length, values, format, etc.)
- etc.
- check that changing each reactive or watched prop has the proper effect.
-
check permission based content (
*.permission.comp.cy.js
)- Check that correct content is displayed based on the user's permissions.
- Do not save and restore
localStorage
orsessionStorage
between tests as changing users requires a new farmOS instance be created for each test. - create
farmOS
instance asadmin
(which should have all permissions).- The component code will use the same
farmOS
instance because it requests it with no parameters. - Check for the UI elements / behaviors that should be present.
- The component code will use the same
- Create a
farmOS
instance asguest
(which should not have many permissions.)- Check that the UI elements / behaviors that should not be present are not present.
- Note:
worker#
andmanager#
have appropriate permissions when logging into farmOS, but when running via API they need to request a scope, which is not currently implemented infarmosUtil.js
.
- Do not save and restore
- If there is no user permission based content then this test is not required.
- Check that correct content is displayed based on the user's permissions.
-
-
NOT ALL OF THESE ARE FOLLOWED UNIVERSALLY AT THIS POINT. SHOULD HAVE ISSUES CREATED FOR THEM.
-
use
cy.get(@spy).its('callCount').should('equal', 1);
instead ofshould('be.called.once')
etc. because'called'
passes if the spy was called at least the specified number of times (once
,twice
, etc). [ This may not be true since calls may not have registered before the check unless it is in a then. ] -
wrapper.setProps
- should always be first thing in athen
because it does not wait forcy
calls before it and can mess things up.
-
docs are in
docs/components
-
to generate docs
npm run doc:gen
-
to view docs
npm run doc:view
-
Docs are generated by vue-docgen-cli
- List all minimum expectations here...
Inside the script, after the imports and just before the export default {
line:
- First line of this comment must be a one sentence description that will appear in the index doc.
- This block should give an example of how to place the component in a page.
- This block must document the data-cy attributes of the component.
/**
* This component does blah blah blah...
*
* More description of the component can go here...
*
* ## Example
*
* ```html
* Give example of tag for using component in a page.
* ```
*
* ## `data-cy` Attributes
*
* Attribute Name | Description
* ----------------------| -----------
* `data-cy-att-name` | Identify the element tagged with the attribute.
* `another-one` | Blah blah.
*/
- every prop gets a comment
- if the prop is watched the comment should indicate this.
- if the prop is deep watched the comment should indicate this.
- every event gets a comment. If the same event is emitted multiple times, just the first one gets a comment.
- If the event hasa payload it must include an
@property
tag describing the payload.
- these are not user facing and are not included in the docs.
- comments in the code can still be helpful to anyone modifying the component.
A component can check the permissions of the logged in farmOS user using appropriate function in farmosUtil.js
.
If a permission needs to be checked that is not yet supported it can be added to the $perms
array in the permissions
function in modules/farm_fd2/src/module/Controller/FD2_Controller.php
file.
The log categories and units used by FarmData2 are installed by the farm_fd2.install
file in modules/farm_fd2/src/module
.
To add new log categories or units:
- Edit the
farm_fd2.install
file. - Rebuild the module.
- Uninstall the FarmData2 module (machine name:
farm_fd2
) - Re-enable the Farmdata2 module.
Note: the installDB.bash
script also uninstalls and re-enables the FarmData2 module. Thus, changes to the log categories and units will be reflected when tests are run headless.