- Documentation
- Environment
- Module aliases
export default
- Logging things
- Including CSS files from external modules
- Colors
- Icons
- Components
- Internationalization
- End-to-end tests
- TODO
The application is an Ionic hybrid mobile application.
It was generated with:
- Ionic 3.9.2
- Cordova 7.0.1
Here are the links to the documentation of the main technologies, framework and libraries used in the project:
- Ionic
- Angular
- ngx-leaflet - Angular component to display leaflet maps
- Leaflet
- Lodash
- Turf - Utilities to manipulate geographical information
And the same for the libraries and frameworks used to test the project:
- Chai - Test assertion library
- Mocha - JavaScript test framework
- Protractor - End-to-end test framework for Angular
- Sinon - Test spies, stubs and mocks
Files can be placed in the src/environments
directory to define
environment-specific configuration:
File | Environment |
---|---|
src/environments/environment.dev.ts |
Development environment (when running ionic serve normally) |
src/environments/environment.ts |
Production environment (when running ionic build --prod ) |
The file for the current environment can be imported like this:
import { ENV } from '@app/env';
The @app/env
module is a module resolution alias defined by
augmenting Ionic's default webpack configuration in the following files:
config/optimization.config.js
config/webpack.config.js
This configuration is applied by the config.ionic_optimization
and
config.ionic_webpack
properties of package.json
.
For TypeScript to correctly identify the @app/env
module, the path to the
environment files has been added to the compilerOptions.paths
property of the
tsconfig.json
file at the root of the project.
This mechanism is based on Easy to use environment variables for Ionic3!.
Following the mechanism as described in the Environment > Implementation section, several custom module aliases have been defined for this app.
Those aliases have been added to the spec/webpack.test.js
file, so that tests that use them can be properly resolved by webpack.
Here is a list of those aliases and the path (relative to the root folder) for the file or folder to which they point to:
Using those aliases improves writing and reading imports, as one does not have to care about the relative paths
Alias | Path | Note |
---|---|---|
@app/env |
src/environments/<environment-file> |
(1) |
@print |
src/utils/print.ts |
(2) |
@utils |
src/utils |
|
@pages |
src/pages |
|
@components |
src/components |
|
@providers |
src/providers |
|
@models |
src/models |
|
@app |
src |
(3) |
(1): Specific environment file. See here.
(2): Direct access to the logging utility. See here.
(3): Access something that is not accessible using the other aliases.
The Ionic script that builds the application for production does not seem to accept classes exported using the default
keyword, like this:
export default class SomeClass {
// ...
}
When such classes are imported and used in a Page constructor, for example, building the app for production fails with an error indicating that the parameter could not be resolved:
import SomeClass from '../path/to/some/class';
@Component({
// ...
})
export class SomePage {
constructor(
private someClass: SomeClass
) { }
}
[XX:XX:XX] typescript error
Can't resolve all parameters for SomePage in
/path/to/some/page/some-page.ts: (?).
As a result, one should not use the default
keyword when exporting a class that will be used in a constructor.
To keep consistency throughout the codebase, the default
keyword should not be used in any export.
The correct way to export
/import
should then be:
export class SomeClass {}
import { SomeClass } from '../path/to/some/class';
When in need of logging things in the application, refrain from using the javascript console
utility.
The project has its own logging utility, called Print
.
It provides the same API as the console
utility but only actually log things if the value of ENV.environment
is in its whitelist.
The whitelist is found in the ./src/utils/print.ts
file, and can be updated there to add more environment :
// ./src/utils/print.ts
const whitelist: string[] = [
'development'
];
To use the Print
utility, import it in your file, and call one of its methods :
import { Print } from '@print';
Print.log('Hello World');
Print.debug('Foo');
Print.info('Bar');
Print.debug(Print);
Print.error('Ooops');
When installing external libraries or modules, it could happen that those lib/modules requires a special stylesheet that it provides.
It is not possible to include such a file in a .scss
file with an @import
statement like this:
// ./src/app/app.scss
@import 'node_modules/leaflet/dist/leaflet.css'
This results in a
404
on the file at runtime
When Ionic builds the application, with ionic cordova build {platform}
or ionis serve
, it executes a copy
script that copies some ionic's core files from the node_module
to the www
or build
directory.
The config/copy.config.js
file can be modified to add new files to copy during this step.
This feature is documented in the Ionic Documentation : https://ionicframework.com/docs/developer-resources/app-scripts/
To do so, add a new property to the exported object, named as you see fit, that has two properties :
src
: an array of paths of directory or files to copydest
: the path of the destination folder in which all files listed insrc
will be copied
Specific wildcards can be used in the above mentionned path :
{{SRC}}
refers to the./src
folder{{ROOT}}
refers to the root folder{{BUILD}}
refers to thewww/build
folder{{WWW}}
refers to thewww
folder
// ./config/copy.config.js file
module.exports = {
// previous properties
copyLeaflet: {
src: ['{{ROOT}}/node_modules/leaflet/dist/leaflet.css'],
dest: '{{BUILD}}/leaflet/'
}
};
For the file to be actually copied, you need to restart the server (or rebuild the app)
When the needed files are copied in the www
directory, they can be referenced in the index.html
or in a component.
Several colors have been defined for the BioPocket project by the design team. They have been declared inside two color maps in the src/theme/variables.scss
file.
One map, $colors
, contains the main color of the BioPocket color theme.
The second map, $secondaryColors
, contains what its name implies: the secondary colors of the color theme.
Each color have been given a specific key in the map.
Following are two tables (one for the main colors, the other for the secondary colors) that matches the color with their key and their HEX and RGB values.
Use the SASS utility function
bipColor($key)
to retrieve the HEX code for the color..example { color: bipColor('primary'); background-color: bipColor('boreal'); border-color: bipColor('lagoon'); }
Icons specific to BioPocket are displayed in the app using inline SVG and an SVG sprite file.
Each icon from the icon set is represented by a .svg
file in the src/assets/icon/svg
folder.
When updating or creating a new icon for the set, simply put the exported .svg
file from Illustrator in this folder.
Several scripts will concatenate all those individual .svg
files into one single SVG sprite file, named bip-icon-set.svg
and located in the src/assets/icon
folder.
This file will then be copied by the bundling process to the correct path so that the Ionic app can display the icon, using a special bip-icon
component (see Components).
One script, svg-sprite
, generate the sprite file, while the other, svg-sprite:watch
, will run the svg-sprite
script whenever it detects changes on the above mentioned svg
folder.
Please note that this process is handled by the
npm start
script, so you shouldn't have to run it manually.
Here's a list of available icons and the name that should be used when referencing them in bip-icon
components (or other components that support them).
Icon | Name |
---|---|
![]() |
'bell' |
![]() |
'calendar-check' |
![]() |
'calendar' |
![]() |
'camera' |
![]() |
'clock' |
![]() |
'comments' |
![]() |
'cross' |
![]() |
'gear' |
![]() |
'heart' |
![]() |
'hourglass' |
![]() |
'info' |
![]() |
'list' |
![]() |
'location' |
![]() |
'map' |
![]() |
'question' |
![]() |
'repeat' |
![]() |
'spiral' |
![]() |
'user' |
Here's a list of the custom Angular components that are available in the BioPocket app.
Selector | Purpose | Documentation |
---|---|---|
bip-icon |
To display BioPocket icons | API |
bip-menu-item-icon |
To display BioPocket icons in menu items | API |
bip-menu-item |
To display menu item in the app's main menu | API |
bip-menu-header |
To display the user profile in the app's main menu | API |
bip-profile-picture |
To display a profile picture (with the double rounded border) | API |
The plugin ngx-markdown allows the rendering of a markdown text as proper HTML in the templates.
The simplest usage is to add a <markdown>
tag in the template, enclosing the actual markdown string:
<markdown>
# Markdown
Add _some_ markdown **here** and see it `rendered` on the page
</markdown>
The plugin provides more option to render Markdown in the app. See its documentation for more details.
The application is internationalized with ngx-translate for text in general, while dates are handled by moment.
Translations are defined in .yml
files in the src/locales
directory:
app: "BioPocket"
pages:
home:
title: "Accueil"
Each translation can be identified by its dot-separated path in the translation
file. For example, pages.home.title
corresponds to Accueil
in the example
above.
Translations can be displayed in a template with the translate
pipe:
<h1>{{ 'app' | translate }}</h1>
Or retrieved in code with TranslateService
:
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/Rx';
class HomePageComponent {
constructor(private translateService: TranslateService) {
}
getTitle(): Observable<string> {
return this.translateService.get('pages.home.title');
}
}
The get
method always returns an observable since it is possible to load
translations asynchronously through HTTP or another mechanism.
If you require a synchronous translation, use instant
. In that case, it's your
responsibility to make sure that the translations are available at the time of
the call:
const translation: string = this.translateService.instant('pages.home.title');
Translations are loaded synchronously in app.component.ts
for now, but that
could change in the future when we add multiple languages, so get
is preferred
to instant
.
Translations follow the ICU message format and support pluralization and gender:
results: >
{GENDER, select, male{He} female{She} other{They} } found
{RES, plural, =0{no results} one{1 result} other{# results} } in the
{CAT, selectordinal, one{#st} two{#nd} few{#rd} other{#th} } category.
The message will adapt to the options:
translateService.get({ GENDER: 'male', RES: 1, CAT: 2 })
// 'He found 1 result in the 2nd category.'
translateService.get({ GENDER: 'female', RES: 1, CAT: 2 })
// 'She found 1 result in the 2nd category.'
translateService.get({ GENDER: 'male', RES: 2, CAT: 1 })
// 'He found 2 results in the 1st category.'
translateService.get({ RES: 2, CAT: 2 })
// 'They found 2 results in the 2nd category.'
See the format guide for more information.
There currently is a YouTube video that can be played from the Home page of the app.
This is possible thanks to the Ionic Native's YouTube Video Player plugin.
As stated in the documentation above, we need an API key for the plugin to work on Android 5.0+. This API key has been added in the config.xml
file.
The value and management of this key are available through the Google Developer Console dashboard, in the Credentials section.
To connect to this page, you'll need the Media Comem gmail account credentials, which can be found in the MEI KeePass.
End-to-end tests are automated tests that run the application in a browser and simulate user interaction by actually driving the browser like a user would. The user interaction functionality is provided with Protractor, while the tests are standard Mocha tests like the other tests in the project.
These tests are the *.spec.ts
files located in the e2e
directory.
It's important to note that unlike unit tests, end-to-end tests execute on the running application. In other words, they require both the backend and mobile application to be running (the latter in the browser) in order to work.
The following instructions describe how to run the end-to-end tests with a backend and mobile application running in test mode.
Note that the end-to-end tests wipe the backend's database clean before each test. To have a proper
test environment with a separate backend running on a separate database than your development
database, you must stop the ionic serve
(or npm start
) command used for development if you
have it running. The ionic serve
command is not designed to be run twice in the same directory,
and the one you run for development is running in the wrong environment and using the wrong
configuration for testing (e.g. the wrong backend URL).
Follow these instructions to set up the project for end-to-end testing:
-
You must have a
src/environments/environment.test.ts
file with itsbackendUrl
property set. This will be the backend that the mobile application will communicate with when running in test mode. (If you had previously created this file with a fake sample URL, replace it with the correct one.) -
You must have a
config/e2e.config.ts
file with itsbackendDatabaseUrl
set. This will be the database into which data will be inserted before each test.Beware not to use the same database as your development database, as it will be wiped clean before each test.
The rest of the setup depends on how you run the backend and mobile application in test mode. Two ways are described below.
TLDR: stop your development ionic serve
command (if running) and run npm run test:e2e
. It
takes a while to start (at least 15 seconds) but is the easiest way to run the end-to-end tests
once.
The project's e2e/utils.ts
file contains utilities to automatically spawn a backend and mobile
application preconfigured for testing. The following additional setup steps are required:
-
You must retrieve the
backend
git submodule by running the following command the first time:git submodule update --init --recursive
Or the following command if someone else has changed the backend version and you need to update it:
git submodule update --recursive
-
You must install the backend's dependencies by running
npm install
in thebackend
directory.
To run the end-to-end tests, stop any running ionic serve
command if you have it running for
development, then run:
$> npm run test:e2e
Note the following caveat: if you have a development backend already running, you may leave it
running, but then the backendUrl
configured in your src/environments/environment.test.ts
file
must use a different port number than the one defined in your src/environments/environment.dev.ts
file. Otherwise, the test utilities will be unable to start a standalone backend process on the
same, already occupied port number.
The backend and mobile application spawned for testing will be stopped automatically once the tests are done running.
If the backend or mobile application do not seem to start successfully, set the $IONIC_E2E_DEBUG
environment to 1
when running the tests to have the standard output and error streams of the
subprocesses printed to the console:
$> IONIC_E2E_DEBUG=1 npm run test:e2e
If the backend or mobile application take more than 30 seconds to start on your machine, increase
the timeout with the $IONIC_E2E_STANDALONE_TIMEOUT
environment variable (set it to a number of
milliseconds) or with the standaloneTimeout
property in the config/e2e.config.ts
file:
$> IONIC_E2E_STANDALONE_TIMEOUT=90000 npm run test:e2e
TLDR: run a backend in test mode, run the mobile application in test mode, and run npm run test:e2e:fast
or npm run test:e2e:fast:watch
to run the tests. This is a bit longer to set up
each time but once it's running, the end-to-end tests will run much faster (as they don't have to
spawn processes each time). It's the way to go if you want to work on the end-to-end tests for a
while.
The tests can be notified that a backend and/or mobile application are already running in test mode, so that they don't attempt to automatically spawn new ones, by setting the following environment variables:
IONIC_E2E_STANDALONE_BACKEND=0
tells the test utilities not to run a backend.IONIC_E2E_STANDALONE_IONIC=0
tells the test utilities not to run a mobile application.IONIC_E2E_STANDALONE=0
is equivalent to setting both the previous variables.
Running the end-to-end tests with the npm run test:e2e:fast
script automatically sets
IONIC_E2E_STANDALONE
to 0
. You may also use npm run test:e2e:fast:watch
to keep running the
tests automatically as you make changes.
When running the tests this way, it is your responsibility to have the backend and mobile application running in test mode before you execute the tests:
-
You may run the backend in test mode by running the
npm run start:e2e
script in the backend's project directory. (You may use thebackend
directory in this project if you follow the setup instructions of the previous method, or you may use the same repository you use for development.)Make sure that your backend's configuration connects it to a different database in the test environment than in the development environment, as the database will be wiped clean before each test.
Also make sure that the
backendUrl
property in yoursrc/environments/environment.test.ts
file corresponds to the URL at which your backend is available. -
You may run the mobile application in test mode by running the
npm run start:e2e
script in this project's directory.It also runs on
http://localhost:8100
, so you must stop theionic serve
command you use for development if you have it running. Do not attempt to run both commands at the same time, as theionic serve
command was not designed for it.
Once you have both the backend and mobile application running, use the following command to run the end-to-end tests:
$> npm run test:e2e:fast
You may also run the following command to automatically re-run the tests every time the code or the tests change:
$> npm run test:e2e:fast:watch
Note the following caveat: when running the end-to-end tests in watch mode, there is no problem
when modifying the tests, but there can be a timing issue when you modify the application: the tests
may start running before ionic serve
has had a chance to re-compile and serve the new version.
A few pointers on how to work with end-to-end tests in this project.
Page object implementations are found in the e2e/po
directory. They represent the various pages of
the application, and provide methods that can retrieve information from the page, or simulate the
interactions that a user can have with it.
Most of the Protractor-specific code referencing elements of the page should be in these page objects. This centralizes references to HTML IDs and classes in the page objects to facilitate refactoring of the tests when the page structure changes.
Note that many Protractor methods are asynchronous and return promises, so make judicious use of
async/await
to simplify the tests:
it('should work', async function() {
const page = new MyPageObject();
await page.navigateTo();
await expect(page.getTitle()).to.eventually.equal('BioPocket');
// Test other stuff...
});
In this example, both page.navigateTo()
and page.getTitle()
returns a promise, so we use the
following patterns:
- Declare the test function as
async
so that we can useawait
to wait for each assertion. - Use chai-as-promised's
.eventually
chain utility to have theequal
assertion wait a while (e.g. it might have to wait for some AJAX requests or Angular processing to complete). - Do not forget to use
await
on the assertion (which itself returns a promise) so that the test function waits for it to be checked before moving on to further assertions.
Be careful when using npm run test:e2e:watch
or npm run test:e2e:fast:watch
and making changes
to the application. When a change occurs, both Ionic and the end-to-end tests will react, the former
to reload the app and the latter to re-run the tests. There is no guarantee that the app will be
reloaded when the tests start executing: they may execute on the previous version, so results may
not be reliable the first time after making a change on the app.
The problem will not occur if you only modify the end-to-end test files, as the application is not reloaded in this case, only the tests.
End-to-end tests are also run on the Travis continuous integration platform.
Note that the servers running the Travis environment are typically not as fast as your local development machine. Badly written asynchronous assertions may pass locally because everything is fast, and fail on Travis because page loads and AJAX calls are slower there.
Do not forget to:
- Use
await
on asynchronous assertions (e.g.await expect(a).to.eventually.equal(b)
). Without theawait
keyword, the test will proceed immediately to the next assertion. Sometimes this works locally because things load faster, but it may fail on Travis. - Wait for asynchronous HTTP calls to complete. You cannot explicitly do this with Protractor, but you can, for example, make an assertion on the presence of an HTML element that will only appear once an HTTP call is done. If you forget to do this, it may work locally because HTTP calls are very fast, but may fail on Travis.
To build the app for release in Android or iOS, you can use respectively the npm run apr
and npm run ipr
scripts.
TODO: document the complete release process both for iOS and Android
In the release process for Android, you'll need to do two specific action that requires executing commands in the CLI:
- Zipaligning the
.apk
- Signing the
.apk
For this step, you'll also need an Android KeyStore. Use the Android Release KeyStore that is available in the MEI KeePassX.
As a reminder of the commands to execute, you'll find two dummy scripts in the package.json
file: zipalign
and apksigner
.
Those two scripts SHOULD NOT be run since they are using absolute path to the required utilities that would most likely be missing in your computer.
**TODO: It would be nice to have some kind of script that automates the process of preparing the
.apk
for and Android release, using some kind of config file for the utilities path.
-
Remove fixed dependency on
postcss
when Ionic issue has been fixed.Updating
@ionic/app-scripts
to 3.1.8 caused the following warning to be displayed when starting the app:[17:10:06] sass started ... Without `from` option PostCSS could generate wrong source map and will not find Browserslist config. Set it to CSS file path or to `undefined` to prevent this warning. [17:10:07] sass finished in 819 ms
This has been temporarily fixed by adding a fixed dependency to
postcss
version 6.0.14 (@ionic/app-scripts
is currently incompatible with the latest version of this dependency).See ionic-team/ionic-framework#13763
The new dependency should be removed with
npm uninstall postcss
once the issue has been fixed by the Ionic team. -
Remove calls to
detectChanges()
when the issue fromngx-leaflet
has been fixed or a workaround has been deployed.Updating
ngx-leaflet
to a version higher than 2.5.x caused an issue when updating markers visible on the map. Previously, the library automatically notified Angular when changes where happening inside a Leaflet event (such asonMapMoved()
). With 2.6.x and onward, the decision has been made (although the author is still not sure about it) to remove this automatic update.Thus, when updating a component's property from inside a leaflet event, Angular does not detect those changes and does not update itself accordingly. To solve this issue, one has to manually tell angular when changes are made.
This can be done by importing the
ChangeDetectorRef
angular component from@angular/core
and using itsdetectChanges()
method the desired changes.For more information, see bluehalo/ngx-leaflet#123 and https://github.com/Asymmetrik/ngx-leaflet#a-note-about-change-detection