FarmData2 is built using Drupal modules that run within FarmOS. The FieldKit and BarnKit tabs in the FarmData2 interface are created by modules. The FD2 Example tab is created by a sample module and provides canonical examples of many of the operations and UI elements used in the FieldKit and BarnKit tabs. The FD2 School tab is created by a module used just for the FarmData2 School onboarding materials.
As mentioned, each of the tabs in the FarmData2 interface are created by Drupal modules. Each FarmData2 module is defined in its own directory within the farmdata2_modules/fd2_tabs
directory (e.g. fd2_barn_kit
, fd2_example
, fd2_field_kit
). All modules should be named with the prefix fd2_
. This keeps them together and makes them easier to find within the farmOS admin interface.
A FarmData2 module named fd2_xyz
would include a folder within farmdata2_modules/fd2_tabs
named fd2_xyz
. That folder will contain at least the following files and directories, where fd2_xyz
, abc
and pqr
are simply place holders and should be replaced with module-specific names.
fd2_xyz.info
: Defines the modulefd2_xyz
so that it is recognized by Drupal.fd2_xyz.module
: Contains the PHP implementation of thefd2_xyz
module. This code determines where and when the tab is visible based on the user's role (e.g.admin
,manager
,worker
orguest
). It also defines the sub-tabs and their content.- A folder for each sub-tab in the module. For example for a sub-tab
abc
there will be a folder namedabc
with the following contents:abc.html
: The content for a sub-tababc
within thefd2_xyz
module (e.g.info.html
,api.html
).abc.spec.js
: Test file containing end-to-end tests for theabc.html
page. The end-to-end tests are written using the Cypress testing tools (more info below). If multiple test files are used for theabc.html
file, they should be namedabc.pqr.spec.js
wherepqr
clarifies the testing performed by the file. Multiple additional periods and names can be used as necessary, but the filename of the test must end with.spec.js
in order to be recognized by the FarmData2 Cypress configuration.
cypress
: A directory containing additional module-level end-to-end tests for thefd2_xyz
module. These are tests that are important for the module but that are not associated with a specific sub-tab. These test files must also be named*.spec.js
to be recognized by the FarmData2 Cypress configuration.
The FD2 Example tab is created by the fd2_example
module and is enabled by default by the developer install. This module and tab provide a sandbox for learning and experimentation with FarmData2 modules. The associated FD2 Example tab will not appear in production installs. Developers wanting to mimic a production environment for demonstrations can hide the FD2 Example tab by disabling the fd2_example
module as follows:
- Log into FarmData2 as
admin
- Click
Manage
- Click
Modules
- Click
FarmData2
in the left column. - Click to turn the
FarmData2 Example
module off. - Click
Save configuration
When returning to the home screen the FD2 Example tab should no longer be visible. The FD2 Example tab can be reenabled by a similar process.
Most FarmData2 front-end development will consist of adding a new sub-tab to one of the FarmData2 tabs (i.e. the Field Kit or the Barn Kit). This section discusses how to add such a sub-tab.
To add a new sub-tab to the fd2_xyz
module:
- Ensure that a development instance of FarmData2 is up and running. See INSTALL.md for full instructions.
- Edit the
fd2_xyz.module
file to add a newarray
to the$items
array for the new sub-tab. Thearray
for the UI tab (shown below) is a good example to work from:<?php // ... omitted code ... function fd2_example_menu() { // ... omitted code ... $items['farm/fd2-example/ui'] = array( 'title' => 'UI', 'type' => MENU_LOCAL_TASK, 'page callback' => 'fd2_example_view', 'page arguments' => array('ui'), 'access arguments' => array('View FD2 Example'), 'weight' => 120, ); // ... omitted code ... }; // ... omitted code ...
- Four elements of the
array
must be customized for your new sub-tab:farm/fd2-example/ui
: This is the URL to directly access the UI sub-tab. Replaceui
with the URL you want for your sub-tab.- Note: This string also controls the placement of the FD2 Example and UI tabs. The FD2 Example tab will appear on the Farm menu, which is where the Dashboard tab also appears. The UI tab will appear as a sub-tab on the FD2 Example tab. The value
farm
cannot be changed. But,fd2-example
andui
can. In particular, they are not required to match the filenames. For example,fd2-example
does not have to match the filenamefd2_example.module
andui
does not have to correspond toui.html
. However, it is a good convention to follow.
- Note: This string also controls the placement of the FD2 Example and UI tabs. The FD2 Example tab will appear on the Farm menu, which is where the Dashboard tab also appears. The UI tab will appear as a sub-tab on the FD2 Example tab. The value
title
: This defines the name of the sub-tab that appears in the Example module's tab. ReplaceUI
with the text you want to appear as the sub-tab title.page arguments
: This is the name of the directory that contains the content for the sub-tab. Replaceui
with the name of the folder file that contains the code for your new sub-tab.weight
: The weight controls the placement of the sub-tabs with respect to the others that appear. Sub-tabs with lower weights appear further left and those with higher weights appear further right.
- Four elements of the
- Create a folder for the sub-tab that you named in the
page arguments
above. - Create a
.html
file using the same base name as the folder (e.g.abc.html
). - Insert some dummy code into the
.html
file for now just to get the sub-tab up and visible. For example:<p>This is my sub-tab</p>
- Note that it is not necessary to include all of the html elements (e.g.
<html>
,<head>
,<body>
etc). The code that you provide in this file is inserted into the body of the page when it is generated by Drupal.
- Note that it is not necessary to include all of the html elements (e.g.
- Clear the Drupal cache:
docker exec -it fd2_farmdata2 drush cc all
- Visit the FarmData2 home page when logged in as a Farm Worker, a Farm Manager or as
admin
. Your new sub-tab should now be visible under the FD2 Example tab. You can find the login credentials that are available in the developement environment on the INSTALL.md page.- If your tab does not appear, there are likely syntax errors in the
fd2_example.module
file or in your.html
file. You can log into FarmData2 asadmin
and check the Drupal error logs on theadmin/reports
page.
- If your tab does not appear, there are likely syntax errors in the
- Replace the dummy code in your
.html
file with the code for your new sub-tab. This file can contain any valid html code including CSS, JavaScript, Vue.js, etc...- Sub-tabs typically (e.g.
ui.html
) use Vue.js, Axios and the FarmOS API to interact with the FarmData2 database. More information on these tools and resources for getting started with them are available in the ONBOARDING.md file.
- Sub-tabs typically (e.g.
Local libraries, such as those in the fd2_tabs/resources
directory, are included by adding them to the .info
configuration file of the fd2_config
module (i.e. fd2_config/fd2_config.info
). A file added to the fd2_config
module will be availble to all other modules.
Libraries such as those served from Content Delivery Networks (CDN) are included by adding them to the <module>_preprocess_page()
function in the .module
file. For example, in the fd2_example.module
file the following lines add the Vue.js and Axios libraries:
drupal_add_js('https://unpkg.com/vue/dist/vue.min.js','external');
drupal_add_js('https://unpkg.com/axios/dist/axios.min.js','external');
The lines for external libraries must be included in each module's .module
file.
Drupal Modules can pass variables from the farmOS/Drupal system through to the sub-tab. All of the current modules define two such variables:
fd2UserID
: The numeric id of the user that is currently logged in to FarmData2.fd2UserName
: The text user name of the user that is currently logged in to FarmData2.
These variables are global and can be used in scripts and in the Vue instance within the page. Additional variables can be added to a module by adding their definitions to the <module>_preprocess_page()
method in the appropriate .module
file. See the fd2_example_preprocess_page()
function in the fd2_example.module
file for an example.
Vue Components help to reduce code duplication, speed up development, and create a consistent look and feel across all of the sub-tabs in FarmData2. Information about the creation of Vue Components is available in the ONBOARDING.md file.
Custom Vue Components used in FarmData2 are contained in the fd2_tabs/resources
folder. Each component is defined in a .js
file. The filename for components should match *Component.js
(e.g. dropdownWithAllComponent.js
) Each component is also accompanied by a file with a name of the same prefx but with the suffix .spec.comp.js
that contains Cypress component tests for the component (e.g. dropdownWithAllComponent.spec.comp.js
). See below for more information on the component tests.
In addition to the standard implementation of a Vue Component as a JavaScript object (see DropdownWithAllComponent.js
) each Vue Component for FarmData2 exports the object as a CommonJS Module so that it can be imported into the Cypress Component test framework. For example, the snippet of code below appears at the bottom of the DropdownWithAllComponent.js
file to export the DropdownWithAllComponent
.
try {
module.exports = {
DropdownWithAllComponent
}
}
catch {}
- More Details: Drupal 7 adds JavaScript files to a page using a
<script>
tag. Thus, there is no need for this statement to use the component in a FarmData2 page. However, the Cypress Component testing tools require that a component under test be imported from a module. Thus themodulde.export
statement is necessary to make it possible to test the component. So, this little hack basically just makes it possible for us to use the same.js
file both in FarmData2 through Drupal and in the Cypress Component testing tools.
When a parent Vue component contains a nested child Vue component, the parent component needs to have access to the child component. Within Drupal this happens as explained below. To support testing of nested components it is necessary that the parent component import
the child component when it is running with the Cypress copmponet test runner but not when being used in a FarmData2 page served by Drupal. To accomplish this you will add a try/catch
to the top of any Vue component file that uses a nested component. For example, the code below appears at the top of the DateRandeSelectionComponent.js
file because that component uses the DateSelectionComponent
:
try {
FarmData2
}
catch(err) {
var DateComps = require("./DateSelectionComponent.js")
DateSelectionComponent = DateComps.DateSelectionComponent
}
- More Details: The variable
FarmData2
is defined on pages that are running within the FarmData2 system (i.e. those served by Drupal). However, it is not defined when the Cypress component test runner is running. Thus, therequire
will only execute when the component is being tested in the Cypress component test runner.
To use a Vue component within a sub-tab, the .js
file for the component must be added to the scripts
array in the module's .info
file. For example, the following line in the fd2_example.info
file makes the DropdownWithAllComponent
available in the .html
pages that define the sub-tabs on the FD2 Example
tab:
scripts[] = '../resources/dropdownWithAllComponent.js'
If a Vue component contains a nested Vue compnent then both the nested component and the parent component must be added to the scripts[]
. The nested component must be listed before the parent component. For example, the following lines make the DateSelectionComponent
and the DateRandeSelectionComponent
available on the FD2 Example
tab:
scripts[] = '../resources/DateSelectionComponent.js'
scripts[] = '../resources/DateRangeSelectionComponent.js'
FarmData2 also uses a number of custom JavaScript libraries that are contained in the farmdata2_modules/fd2_tabs/resources
folder. Similar to what was done with Vue comonents and nested Vue components there are blocks of code that will appear at the top and bottom of each JavaScript file so that they can be served by Drupal and be tested within the Cypress End-to-End test runner.
Any modules that are needed by the library will be required
by a block of code at the top of the file. For example, the code in the FarmOSAPI.js
file uses the axios
module. These lines appear at the top of that file:
try {
FarmData2
}
catch {
axios = require('axios')
}
The JavaScript file will also export any variables, objects or functions that it contains so that they can be exercised by Cypress test. For example, the FarmOSAPI.js
file exports the getAllPages
function using the following code that appears at the end of the file:
try {
module.exports = {
getAllPages
}
}
catch {}
FarmData2 modules, sub-tabs and components are all tested using the Cypress testing framework. Having a complete set of tests facilitates future changes and the evolution of FarmData2 by making it easy to identify any functionality that has been broken by changes. More information about Cypress and resources for getting started with it are available in the ONBOARDING.md file.
Cypress end-to-end tests are used to confirm that modules and sub-tabs operate correctly. They do this by interacting with the live development instance of FarmData2. Cypress end-to-end tests will typically log into FarmData2 as a specific user, visit a specific page, interact with the elements of that page (e.g. click buttons, enter text, etc) and then make assertions about the resulting changes to the page.
Each sub-tab will have have an associated set of end-to-end tests. For example, fd2vars.spec.js
file contains the end-to-end tests for the sub-tab defined by the fd2vars.html
file (i.e. the Vars sub-tab on the FD2 Example tab). Note that, by convention, the name of the spec
file has the same prefix as the sub-tab's .html
file. If there are multiple test files for the same html
file use additional prefixes to identify their purpose (i.e. fd2vars.pqr.spec.js
, where pqr
would provide additional clarifying information about the tests in that file.
The cypress
directory within a module directory (e.g.fd2_example/cypress
) will contain module-level tests. These are tests that apply to the main tab rather than to individual sub-tabs. For example, in the fd2_example
module:
visibility.spec.js
tests that the FD2 Example tab is visible for the admin, manager and worker users but not for a guest.
The FarmData2 development environment provides support for running the end-to-end tests using the Cypress test runner. The Cypress end-to-end test runner can be launched from the farmdata2
directory in an interactive GUI or a non-interactive terminal-based mode:
- GUI:
./test_runner.bash
- Headless all tests:
./test_runner.bash e2e
- Headless with specific browser:
./test_runner.bash e2e --browser firefox
- Headless select tests:
./test_runner.bash e2e --spec "**/fd2_example/ui/*.spec.js"
Cypress component tests are used to check the behavior of custom Vue Components in isolation. This is done by mounting the Vue Component into the test runner, interacting with it (similar to the end-to-end tests) and making assertions about changes in the component and emitted events.
Each component in the fd2_tabs/resources
folder (e.g. dropdownWithAllComponent.js
) will be accompanied by Cypress component tests (e.g. dropdownWithAllComponent.spec.comp.js
). All of the tests for the component are contained in the .spec.comp.js
file.
Resources and references for writing Cypress tests for Vue Components can be found in the ONBOARDING.md file.
The Cypress test runner (discussed above) also provides support for running Vue Component tests in the same Docker container. The Vue Component tests are run from the farmdata2
directory in an interactive GUI or a non-interactive terminal-based mode:
- GUI:
./test_runner.bash
- Headless all tests:
./test_runner.bash ct
- Headless with specific browser:
./test_runner.bash ct --browser firefox
- Headless select tests:
./test_runner.bash ct --spec "**/resources/**/*.spec.comp.js"