This project was bootstrapped with Create React App, TypeScript version.
The backend is developed separately and their communication is enabled via CORS support on the backend.
Upon Running the code, you will need to edit config/server.json and supply:
- server URL - URL to the TermIt server (see https://github.com/kbss-cvut/termit for details)
Jest is used for testing the TermIt UI code.
The proposed test structure consists of:
- Unit tests - should test singular classes/components. The tests should be put in a
__tests__
directory next to the file they test (seesrc/reducer
andsrc/reducer/__tests__
). - Integration tests - tests using multiple components and classes. These include sanity tests,
regression tests and general integration tests. These should be put in the
src/__tests__
directory. Further structuring is recommended (e.g., the sanity tests are currently insrc/__tests__/sanity
).
To enable easy integration with Jest's file name matching, all test files should have the pattern *.test.ts(x)
. The actual
name of the file (before the first dot) should reflect the purpose of the test file, e.g., for unit tests this is the name of the
tested file (see for example TermItReducers.ts
and the corresponding TermItReducers.test.ts
).
General testing utilities should be put in src/__tests__/environment
.
Since most of the components are localized, they expect intl-related functions like i18n
, formatMessage
(specified in HasI18n
). To be able to render such components
in tests, there are two things that need to be done:
- Render the component using
mountWithIntl
instead of Enzyme's defaultmount
. This wraps the component in anIntlProvider
, which sets up the intl context. - Pass the intl-related functions to the component. This can be done by invoking the
intlFunctions
function, which returns an object with all the necessary functions/objects.
So mounting the component in tests can look for example as follows:
const wrapper = mountWithIntl(<CreateVocabulary onCreate={onCreate} {...intlFunctions()}/>);
Note that this means that wrapper
is not the actual tested component but an instance of IntlProvider
wrapping the component. mountWithIntl
also provides a default Redux store
mock which is required by some components.
If shallow rendering is used, use the regular Enzyme shallow
method to mount the component and set up the intl context using the intlFunctions
function.
For example:
const wrapper = shallow<CreateVocabulary>(<CreateVocabulary onCreate={onCreate} {...intlFunctions()}/>);
Do not forget to import the core component into tests, not the wrapped component!
- Action are currently split into
SyncAction
,AsyncActions
andComplexActions
, whereSyncActions
are simple synchronous actions represented by objects, whereasAsyncActions
andComplexActions
exploitredux-thunk
and return functions.ComplexActions
represent actions which involve both synchronous and asynchronous actions or other additional logic. AsyncActions
API guidelines:- Load - use IRI identifiers as parameters (+ normalized name as string if necessary, e.g. when fetching a term).
- Create - use the instance to be created as parameter + IRI identifier if additional context is necessary (e.g. when creating a term).
- Update - use the instance to be updated as parameter. It should contain all the necessary data.
- Remove - use the instance to be removed as parameter.
- Action naming conventions for CRUD operations:
- load${ASSET(S)} for loading assets from the server, e.g.
loadVocabulary
- create${ASSET} for creating an asset, e.g.
createVocabulary
- update${ASSET} for updating an asset, e.g.
updateVocabulary
- remove${ASSET} for removing an asset, e.g.
removeVocabulary
- load${ASSET(S)} for loading assets from the server, e.g.
- Navigation is handled separately from Redux, although the Redux documentation contains a section on setting up routing with react-router and redux. Currently, I believe it is not necessary to interconnect the two.
- Localization is now handled by Redux state, so that page refreshes are not necessary when switching language.
- Logout involves no server request, only removal of user token from local storage. This is because JWT is stateless and all user info is stored in the token, so server keeps no sessions.
- In case a component needs props specified by parent + store-based props (actions, Redux state), interfaces have to defined
separately and then specified in generic arguments to
connect
. An example of this technique can be found inTermAssignments
. Also, this means that intl props need to be explicitly passed to the component inconnect
. Otherwise, internationalization would not work properly (language switching would have no effect). SeeTermAssignments
again for a showcase how to do this. - Marker CSS classes should be used to denote important elements. These classes help in testing. Marker classes should be prefixed with
m-
and no styling should be applied based on them. - Although there are some problems with testing, it is possible to use React Hooks in the code. See
PasswordReset
. The workaround for testing components with hooks can bee seen by inspecting the Git history ofUsers.tests
.
- Tests can be debugged directly in IDEA just like JUnit tests - IDEA is able to run singular tests.
- The application can be debugged in IDEA as well, see the JetBrains blog.
It is possible to mock server REST API, so that the application can be developed and run without having to start the backend application.
To do so, use npm run start-mock-rest
, which sets environment variables telling the app to mock the REST API. Now, the mock API is set up
in src/util/Ajax
, function mockRestApi
, we are using Axios Mock Adapter. The usage should be
fairly intuitive. Data should be kept in JSON files in src/rest-mock
(has to be in src
, otherwise webpack refuses to import the data).
It is possible to adjust the styling of the application. We are using Bootstrap which provides SCSS definitions which can be overridden. Based on the
documentation here and here,
there is the _custom.scss
file in src
. This file allows to override default Bootstrap styles and its content is automatically imported in the application.
Further documentation is in the doc folder.
The docker image of TermIt UI can be built by
docker build -t kbss-cvut:termit-ui .
An optional argument is SERVER_URL
pointing to the TermIt backend.
Then, TermIt UI can be run and exposed at the port 3000 as
sudo docker run -p 3000:80 kbss-cvut:termit-ui
Licensed under GPL v3.0.