first commit

This commit is contained in:
Akhil Gupta
2021-05-29 15:20:50 +05:30
commit d25c30a7b2
194 changed files with 49873 additions and 0 deletions

93
ui/docs/architecture.md Normal file
View File

@@ -0,0 +1,93 @@
# Architecture
- [Architecture](#architecture)
- [`.circleci`](#circleci)
- [`.vscode`](#vscode)
- [`.vuepress`](#vuepress)
- [`docs`](#docs)
- [`generators`](#generators)
- [`public`](#public)
- [`index.html`](#indexhtml)
- [`src`](#src)
- [`assets`](#assets)
- [`components`](#components)
- [`design`](#design)
- [`router`](#router)
- [`state`](#state)
- [`utils`](#utils)
- [`app.config.json`](#appconfigjson)
- [`app.vue`](#appvue)
- [`main.js`](#mainjs)
- [`tests`](#tests)
## `.circleci`
Configuration for continuous integration with [Circle CI](https://circleci.com/). See [the production doc](production.md#from-circle-ci) for more.
## `.vscode`
Settings and extensions specific to this project, for Visual Studio Code. See [the editors doc](editors.md#visual-studio-code) for more.
## `.vuepress`
[VuePress](https://vuepress.vuejs.org/) configuration for docs static site generation.
## `docs`
You found me! :wink:
## `generators`
Generator templates to speed up development. See [the development doc](development.md#generators) for more.
## `public`
Where you'll keep any static assets, to be added to the `dist` directory without processing from our build system.
### `index.html`
This one file actually _does_ get processed by our build system, allowing us to inject some information from Webpack with [EJS](http://ejs.co/), such as the title, then add our JS and CSS.
## `src`
Where we keep all our source files.
### `assets`
This project manages assets via Vue CLI. Learn more about [its asset handling here](https://cli.vuejs.org/guide/html-and-static-assets.html).
### `components`
Where most of the components in our app will live, including our [global base components](development.md#base-components).
### `design`
Where we keep our [design variables and tooling](tech.md#design-variables-and-tooling).
### `router`
Where the router, routes, and any routing-related components live. See [the routing doc](routing.md) for more.
### `state`
Where all our global state management lives. See [the state management doc](state.md) for more.
### `utils`
These are utility functions you may want to share between many files in your application. They will always be pure and never have side effects, meaning if you provide a function the same arguments, it will always return the same result. These should also never directly affect the DOM or interface with our Vuex state.
### `app.config.json`
Contains app-specific metadata.
### `app.vue`
The root Vue component that simply delegates to the router view. This is typically the only component to contain global CSS.
### `main.js`
The entry point to our app, were we create our Vue instance and mount it to the DOM.
## `tests`
Where all our tests go. See [the tests doc](tests.md) for more.

146
ui/docs/development.md Normal file
View File

@@ -0,0 +1,146 @@
# Setup and development
- [Setup and development](#setup-and-development)
- [First-time setup](#first-time-setup)
- [Installation](#installation)
- [Dev server](#dev-server)
- [Developing with the production API](#developing-with-the-production-api)
- [Generators](#generators)
- [Aliases](#aliases)
- [Globals](#globals)
- [Base components](#base-components)
- [Docker (optional)](#docker-optional)
## First-time setup
Make sure you have the following installed:
- [Node](https://nodejs.org/en/) (at least the latest LTS)
- [Yarn](https://yarnpkg.com/lang/en/docs/install/) (at least 1.0)
Then update the following files to suit your application:
- `src/app.config.json` (provides metadata about your app)
- `.circleci/config.yml` (assuming you want to automatically [deploy to production](production.md) with continuous integration)
## Installation
```bash
# Install dependencies from package.json
yarn install
```
## Dev server
> Note: If you're on Linux and see an `ENOSPC` error when running the commands below, you must [increase the number of available file watchers](https://stackoverflow.com/questions/22475849/node-js-error-enospc#answer-32600959).
```bash
# Launch the dev server
yarn dev
# Launch the dev server and automatically open it in
# your default browser when ready
yarn dev --open
# Launch the dev server with the Cypress client for
# test-driven development in a friendly interface
yarn dev:e2e
```
### Developing with the production API
By default, dev and tests filter requests through [the mock API](/docs/tests.md#the-mock-api) in `tests/mock-api`. To test directly against a local/live API instead, run dev and test commands with the `API_BASE_URL` environment variable set. For example:
```bash
# To develop against a local backend server
API_BASE_URL=http://localhost:3000 yarn dev
# To test and develop against a production server
API_BASE_URL=https://example.io yarn dev:e2e
```
## Generators
This project includes generators to speed up common development tasks. Commands include:
```bash
# Generate a new component with adjacent unit test
yarn new component
# Generate a new view component with adjacent unit test
yarn new view
# Generate a new layout component with adjacent unit test
yarn new layout
# Generate a new Vuex module with adjacent unit test
yarn new module
# Generate a new utility function with adjacent unit test
yarn new util
# Generate a new end-to-end test
yarn new e2e
```
Update existing or create new generators in the `generators` folder, with help from the [Hygen docs](http://www.hygen.io/).
## Aliases
To simplify referencing local modules and refactoring, you can set aliases to be shared between dev and unit tests in `aliases.config.js`. As a convention, this project uses an `@` prefix to denote aliases.
## Globals
### Base components
[Base components](https://vuejs.org/v2/style-guide/#Base-component-names-strongly-recommended) (a.k.a. presentational, dumb, or pure components) that apply app-specific styling and conventions should all begin with the `_base-` prefix. Since these components are typically used in place of raw HTML element (and thus used as frequently), they're automatically globally registered for convenience. This means you don't have to import and locally register them to use them in templates.
## Docker (optional)
If you'd prefer to use Docker for development, it's recommended to install and run [Docker Desktop](https://www.docker.com/products/docker-desktop). Once the app is started, you'll be able to run commands like:
```bash
# Build and run a containerized version of your app in the background
docker-compose up --detach
```
Once your container has started, you can run any script from `package.json` inside the container by prefixing the command with `yarn docker` instead of just `yarn`. For example:
```bash
# Install dependencies in the container
yarn docker install
# Run the dev environment in the container
yarn docker dev
# Run tests in the container
yarn docker test
```
To list your containers and their statuses, you can run:
```bash
docker-compose ps
```
To stop your running containers, run:
```bash
docker-compose stop
```
If ever update the following files:
- `.dockerignore`
- `docker-compose.yml`
- `docker-dev.dockerfile`
Then you'll want to stop and remove all containers, networks, volumes, and images created for your app with:
```bash
docker-compose down --volumes --rmi all --remove-orphans
```
This command can also be useful in case something goes wrong with a container and you'd like to start over. All containers, networks, volumes, and images defined in `docker-compose.yml` will be rebuilt the next time you run `docker-compose up`.
See the docs for [Docker](https://docs.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) for more information on how to use and configure Docker tooling.

32
ui/docs/editors.md Normal file
View File

@@ -0,0 +1,32 @@
# Editor integration
- [Visual Studio Code](#visual-studio-code)
- [Configuration](#configuration)
- [FAQ](#faq)
## Visual Studio Code
This project is best developed in VS Code. With the [recommended extensions](https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions) and settings in `.vscode`, you get:
- Syntax highlighting for all files
- Intellisense for all files
- Lint-on-save for all files
- In-editor results on save for unit tests
### Configuration
To configure
- `.vscode/extensions.json`
- `.vscode/settings.json`
## FAQ
**What kinds of editor settings and extensions should be added to the project?**
All additions must:
- be specific to this project
- not interfere with any team member's workflow
For example, an extension to add syntax highlighting for an included language will almost certainly be welcome, but a setting to change the editor's color theme wouldn't be appropriate.

63
ui/docs/linting.md Normal file
View File

@@ -0,0 +1,63 @@
# Linting & formatting
- [Languages](#languages)
- [Scripts](#scripts)
- [Terminal](#terminal)
- [Pre-commit](#pre-commit)
- [Editor](#editor)
- [Configuration](#configuration)
- [FAQ](#faq)
This project uses ESLint, Stylelint, Markdownlint, and Prettier to catch errors and avoid bikeshedding by enforcing a common code style.
## Languages
- **JavaScript** is linted by ESLint and formatted by Prettier
- **HTML** (in templates and JSX) is linted by ESLint
- **CSS** is linted by Stylelint and formatted by Prettier
- **Markdown** is linted by Markdownlint and formatted by Prettier
- **JSON** is formatted by Prettier
- **Images** are minified by `imagemin-lint-staged` (only on pre-commit)
## Scripts
There are a few different contexts in which the linters run.
### Terminal
```bash
# Lint all files, fixing many violations automatically
yarn lint
```
See `package.json` to update.
### Pre-commit
Staged files are automatically linted and tested before each commit. See `lint-staged.config.js` to update. [Yorkie](https://github.com/yyx990803/yorkie) is used by `@vue/cli-plugin-eslint` to install the pre-commit hook.
### Editor
In supported editors, all files will be linted and formatted on-save. See [editors.md](editors.md) for details.
## Configuration
This boilerplate ships with opinionated defaults, but you can edit each tools configuration in the following config files:
- [ESLint](https://eslint.org/docs/user-guide/configuring)
- `.eslintrc.js`
- `.eslintignore`
- [Stylelint](https://stylelint.io/user-guide/configuration/)
- `stylelint.config.js`
- [Markdownlint](https://github.com/markdownlint/markdownlint/blob/master/docs/configuration.md)
- `.markdownlintrc`
- [Prettier](https://prettier.io/docs/en/configuration.html)
- `.prettierrc.js`
- `.prettierignore`
## FAQ
**So many configuration files! Why not move more of this to `package.json`?**
- Moving all possible configs to `package.json` can make it _really_ packed, so that quickly navigating to a specific config becomes difficult.
- When split out into their own file, many tools provide the option of exporting a config from JS. I do this wherever possible, because dynamic configurations are simply more powerful, able to respond to environment variables and much more.

17
ui/docs/production.md Normal file
View File

@@ -0,0 +1,17 @@
# Building and deploying to production
- [From the terminal](#from-the-terminal)
- [From Circle CI](#from-circle-ci)
## From the terminal
```bash
# Build for production with minification
yarn build
```
This results in your compiled application in a `dist` directory.
## From Circle CI
Update `.circleci/config.yml` to automatically deploy to staging and/or production on a successful build. See comments in that file for details.

17
ui/docs/routing.md Normal file
View File

@@ -0,0 +1,17 @@
# Routing, layouts, and views
- [Overview](#overview)
- [Layouts](#layouts)
- [Views](#views)
## Overview
This project uses [Vue Router](tech.md#vue-router), which we initialize in `src/router/index.js`, with routes defined in `src/router/routes.js`. Inside the `src/router` folder, there are also two sub-folders, both containing route-specific components: `layouts` and `views`.
## Layouts
Every view component must use a layout component as its base and register it as `Layout`, as this convention helps us mock out layout components when testing views. Layouts usually aren't very complex, often containing only shared HTML like headers, footers, and navigation to surround the main content in the view.
## Views
Each view component will be used by at least one route in `src/router/routes.js`, to provide a template for the page. They can technically include some additional properties from Vue Router [to control navigation](https://router.vuejs.org/guide/advanced/navigation-guards.html), for example to [fetch data](https://router.vuejs.org/guide/advanced/data-fetching.html#fetching-before-navigation) before creating the component, but I recommend adding these guards to `src/router/routes.js` instead, as that behavior typically has much more to do with the route (and will sometimes be shared between routes) than it does the view component.

66
ui/docs/state.md Normal file
View File

@@ -0,0 +1,66 @@
# State management
- [State management](#state-management)
- [Modules](#modules)
- [Helpers](#helpers)
- [Module Nesting](#module-nesting)
## Modules
The `src/state/modules` directory is where all shared application state lives. Any JS file added here (apart from unit tests) will be automatically registered in the store as a [namespaced module](https://vuex.vuejs.org/en/modules.html#namespacing).
Read more in the [Vuex modules](https://vuex.vuejs.org/en/modules.html) docs.
## Helpers
The state helpers in `helpers.js` are the components' interface to the Vuex store. Depending on a component's concerns, we can import a subset of these helpers to quickly bring in the data and actions we need.
You might be thinking, "Why not just automatically inject all of these into every component?" Well, then it would be difficult to figure out where a particular part of state is coming from. As our state becomes increasingly complex, the risk would also increase of accidentally using the same names for internal component state. This way, each component remains traceable, as the necessary `import` will provide a thread back to our helpers file if we ever don't understand where something is coming from.
Here's an example:
```js
import { authComputed } from '@state/helpers'
export default {
computed: {
...authComputed,
},
}
```
## Module Nesting
Vuex modules can be nested, which sometimes makes sense for organizational purposes. For example, if you created these files:
```js
// @file src/state/modules/dashboard.js
export const state = {
role: 'project-manager',
}
```
```js
// @file src/state/modules/dashboard/videos.js
export const state = {
all: [],
}
export const getters = {
favorited(state) {
return state.all.filter((video) => video.favorited)
},
}
```
Then you'd be able to access those modules with:
```js
store.state.dashboard.role
store.state.dashboard.videos.all
store.getters['dashboard/videos/favorited']
```
As you can see, placing the `videos` module in a folder called `dashboard` automatically nests it underneath the `dashboard` namespace. This works even if a `dashboard.js` file doesn't exist. You can also have as many levels of nesting as you want.

289
ui/docs/tech.md Normal file
View File

@@ -0,0 +1,289 @@
# Languages and technologies
- [Languages and technologies](#languages-and-technologies)
- [JavaScript](#javascript)
- [Polyfills](#polyfills)
- [Vue](#vue)
- [Vue Router](#vue-router)
- [Vuex (state management)](#vuex-state-management)
- [JavaScript FAQ](#javascript-faq)
- [HTML](#html)
- [Templates](#templates)
- [Render functions](#render-functions)
- [HTML FAQ](#html-faq)
- [CSS](#css)
- [SCSS](#scss)
- [Importing global modules](#importing-global-modules)
- [Referencing aliased asset URLs](#referencing-aliased-asset-urls)
- [Design variables and tooling](#design-variables-and-tooling)
- [CSS modules](#css-modules)
- [Styling subcomponents](#styling-subcomponents)
- [Sharing SCSS variables with JavaScript](#sharing-scss-variables-with-javascript)
- [Global CSS](#global-css)
- [CSS FAQ](#css-faq)
## JavaScript
Our JavaScript is compiled by Babel, using the [`@vue/babel-preset-app`](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app) as a base configuration. You can update this configuration in `.babelrc.js`.
If you're new to features such as `const`, `let`, and `=>` (arrow functions), take some time to read about the following features in Babel's ES2015 guide:
- [Arrow functions](https://babeljs.io/docs/en/learn/#arrows-and-lexical-this)
- [Template literals](https://babeljs.io/docs/en/learn/#template-strings)
- [Destructuring](https://babeljs.io/docs/en/learn/#destructuring)
- [Spread operator](https://babeljs.io/docs/en/learn/#default-rest-spread)
- [`let`/`const`](https://babeljs.io/docs/en/learn/#let-const)
- [`for`...`of`](https://babeljs.io/docs/en/learn/#iterators-forof)
Reading these sections alone will get you 99% of the way to mastering Babel code. It's also a good idea to read about Promises, if you don't yet feel comfortable with them. Here's a [good intro](https://developers.google.com/web/fundamentals/getting-started/primers/promises).
### Polyfills
This project uses Vue CLI's [modern mode](https://cli.vuejs.org/guide/browser-compatibility.html#modern-mode), which creates two bundles: one modern bundle targeting modern browsers that support [ES modules](https://jakearchibald.com/2017/es-modules-in-browsers/), and one legacy bundle targeting older browsers that do not.
For each bundle, polyfills for any JavaScript features you use are included based on the target bundle and supported browsers defined by `browserslist` in `package.json`.
### Vue
Since Vue is such a huge part of our app, I strongly recommend everyone read through at least the _Essentials_ of [Vue's guide](https://vuejs.org/v2/guide/).
### Vue Router
To understand how to manage pages with Vue Router, I recommend reading through the _Essentials_ of [those docs](https://router.vuejs.org/en/essentials/getting-started.html). Then you can read more about [routing in this application](routing.md).
### Vuex (state management)
To wrap your head around our state management, I recommend reading through [those docs](https://vuex.vuejs.org/guide), starting at _What is Vuex?_ and stopping before _Application Architecture_. Then skip down and read [_Form Handling_](https://vuex.vuejs.org/en/forms.html) and [_Testing_](https://vuex.vuejs.org/en/testing.html). Finally, read about [state management in this application](state.md).
### JavaScript FAQ
**Why not use TypeScript instead of JavaScript? Isn't that more appropriate for enterprise environments?**
At its current rate of development, I think TypeScript will eventually _become_ the standard, but I don't think it's there yet for application development. Here's my reasoning:
- The vast majority of bugs I encounter are _not_ due to type violations. The most powerful tools against bugs remain linting, tests, and code reviews - none of which are made easier by TypeScript.
- TypeScript doesn't guarantee type safety - that still requires discipline. You can still use hundreds of `any` annotations and libraries without any type definitions.
- In Visual Studio Code, users can already get a lot of useful intellisense (including type information) without having to use TypeScript. [JSDoc comments](https://jsdoc.app/about-getting-started.html) can also be added to [serve the same purpose](https://blog.usejournal.com/type-vue-without-typescript-b2b49210f0b) on an as-needed basis.
- Despite most bugs having nothing to do with type violations, developers can spend _a lot_ of time working towards full type safety, meaning teams unaccustomed to strongly typed languages may face significant drops in productivity. As I mentioned earlier, I think that time would be better spent on tests and code reviews.
- While the next version of Vuex will be designed with TypeScript in mind, the current version can be particularly painful with TypeScript.
## HTML
All HTML will exist within [`.vue` files](https://vuejs.org/v2/guide/single-file-components.html), either:
- in a `<template>`, or
- in a [`render` function](https://vuejs.org/v2/guide/render-function.html), optionally using [JSX](https://vuejs.org/v2/guide/render-function.html#JSX).
### [Templates](https://vuejs.org/v2/guide/syntax.html)
~95% of HTML will be in `.vue` files. Since Vue has a chance to parse it before the browser does, we can also do a few extra things that normally aren't possible in a browser.
For example, any element or component can be self-closing:
```html
<span class="fa fa-comment" />
```
The above simply compiles to:
```html
<span class="fa fa-comment"></span>
```
This feature is especially useful when writing components with long names, but no content:
```html
<FileUploader
title="Upload any relevant legal documents"
description="PDFs or scanned images are preferred"
icon="folder-open"
/>
```
### [Render functions](https://vuejs.org/v2/guide/render-function.html)
Render functions are _alternatives_ to templates. Components using render functions will be relatively rare, written only when we need either:
- the full expressive power of JavaScript, or
- better rendering performance through stateless, [functional components](https://vuejs.org/v2/guide/render-function.html#Functional-Components)
These components can optionally be written using an HTML-like syntax within JavaScript called [JSX](https://vuejs.org/v2/guide/render-function.html#JSX), including support for [some template features](https://github.com/vuejs/babel-preset-vue#supports-event-modifiers).
### HTML FAQ
**Why not use a preprocessor like Jade instead of HTML?**
Jade offers too little convenience (no new features we'd want, just simpler syntax) and would break `eslint-plugin-vue`'s template linting.
**If using a render function instead of a template, why not use a `.js(x)` file instead of a `.vue` file?**
There are no advantages to using a JS(X) file, other than not having to use a `<script>` tag. By sticking to `.vue` files, you can:
- leave out components' `name` property, because `vue-loader` adds a `__filename` property to exported objects as a fallback for Vue's devtools
- easily add styles if you later decide to
- easily refactor to a template if you later decide to
## CSS
For our styles, we're using SCSS and CSS modules, which you can activate by adding the `lang="scss"` and `module` attributes to style tags in Vue components:
```vue
<style lang="scss" module>
/* Styles go here */
</style>
```
### SCSS
SCSS is a superset of CSS, meaning any valid CSS is _also_ valid SCSS. This allows you to easily copy properties from other sources, without having to reformat it. It also means you can stick to writing the CSS you're still comfortable with while you're learning to use more advanced SCSS features.
I specifically recommend reading about:
- [Variables](http://sass-lang.com/guide#topic-2)
- [Nesting](http://sass-lang.com/guide#topic-3)
- [Operators](http://sass-lang.com/guide#topic-8)
Just those features cover at least 95% of use cases.
### Importing global modules
To import files from `node_modules`, Webpack's [css-loader](https://github.com/webpack-contrib/css-loader) requires adding `~` to the beginning of a module name to denote that it's a global (not relative) file reference. For example:
```scss
@import '~nprogress/nprogress.css';
```
### Referencing aliased asset URLs
Similarly to importing global modules, referencing aliased assets in _non_-module CSS also requires the `~` at the beginning of the name. For example:
```scss
background: url('~@assets/images/background.png');
```
### Design variables and tooling
All our [variables](https://sass-lang.com/guide#topic-2), [placeholder classes](https://sass-lang.com/guide#topic-7), [mixins](https://sass-lang.com/guide#topic-6), and other design tooling are in the `src/design` folder. Each of these files define variables, prefixed with the name of the file, then combined in `src/design/index.scss`. This combined file is aliased as `@design` for convenience and can be imported into SCSS using:
```scss
@import '@design';
```
This makes all our design variables available in your component or SCSS file.
> NOTE: The `src/design` folder should never contain code that compiles to actual CSS, as that CSS would be duplicated across every component the file is imported into.
### CSS modules
As mentioned earlier, every Vue component should be a CSS module. That means the classes you define are not _actually_ classes. When you write:
```vue
<style lang="scss" module>
.inputLabel {
/* ... */
}
.input {
/* ... */
}
</style>
```
You're actually defining values on a `$style` property of the Vue instance such as:
```js
$style: {
inputLabel: 'base-input_inputLabel_dsRsJ',
input: 'base-input_input_dsRsJ'
}
```
These values contain automatically generated classes with:
- the file name of the component
- the name of the class
- a random hash
Do you know what that means?! You can _never_ accidentally write styles that interfere with another component. You also don't have to come up with clever class names, unique across the entire project. You can use class names like `.input`, `.container`, `.checkbox`, or whatever else makes sense within the isolated scope of the component - just like you would with JavaScript variables.
#### Styling subcomponents
To pass a class to a child component, it's usually best to do so as a prop:
```vue
<template>
<BaseInputText :labelClass="$style.label">
</template>
<style lang="scss" module>
.label {
/* ... */
}
</style>
```
In some cases however, you may want to style a component arbitrarily deep. This should generally be avoided, because overuse can make your CSS very brittle and difficult to maintain, but sometimes it's unavoidable.
In these cases, you can use an [attribute selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) to take advantage of the fact that generated class names will always _start_ with the same characters:
```vue
<template>
<div :class="$style.container"><SomeOtherComponentContainingAnInput /></div>
</template>
<style lang="scss" module>
.container [class^='base-input_inputLabel'] {
/* ... */
}
</style>
```
In the above example, we're applying styles to the `inputLabel` class inside a `base-input` component, but only when inside the element with the `container` class.
#### Sharing SCSS variables with JavaScript
If you ever need to expose the value of an SCSS variable to your JavaScript, you _can_ with CSS module exports! For example, assuming you have this variable defined:
```scss
$size-grid-padding: 1.3rem;
```
You could import our design tooling, then use CSS modules' `:export` it:
```vue
<style lang="scss" module>
@import '@design';
:export {
grid-padding: $size-grid-padding;
}
</style>
```
Then you can access the value using `this.$style['grid-padding']`.
If you need access from outside a Vue component (e.g. in a Vuex module), you can do so in `src/design/index.scss`. See that file for specific instructions.
### Global CSS
Typically, only [`src/app.vue`](../src/app.vue) should ever contain global CSS and even that should only include base element styles and utility classes (e.g. for grid management).
### CSS FAQ
**Why use SCSS instead of plain CSS or another CSS preprocessor?**
CSS preprocessors offer a lot of additional power - just having a browser-independent way to use variables is invaluable. But SCSS has some other advantages over competing preprocessors:
- SCSS it a superset of CSS, which means:
- You can copy and paste valid CSS into SCSS and it will always be valid.
- There's a gentler learning curve, as devs can write the same CSS they're used to, gradually incorporating more SCSS features as they're needed.
- It's well-supported by both Stylelint and Prettier, eliminating nearly all arguments over code style.
**Why use CSS modules for scoping, instead of [Vue's `scoped` attribute](https://vue-loader.vuejs.org/en/features/scoped-css.html)?**
While a little more complex to begin with, CSS modules offer:
- Universality. The same scoping strategy can be used anywhere in our app, regardless of whether it's in a `.vue` file or `.scss` file.
- True protection from collisions. Using the `scoped` attribute, vendor CSS could still affect your own classes, if you both use the same names.
- Improved performance. Generated class selectors like `.base-input_inputLabel__3EAebB_0` are faster than attribute selectors, especially on an element selector like `input[data-v-3EAebB]`.
- Increased versatility. There are cases the `scoped` attribute just can't handle, such as passing a scoped class to a child component that does _not_ render HTML directly. This is fairly common for component wrappers of views driven by WebGL or Canvas, that often inject HTML overlays such as tooltips at the root of the `<body>`.

219
ui/docs/tests.md Normal file
View File

@@ -0,0 +1,219 @@
# Tests and mocking the API
- [Tests and mocking the API](#tests-and-mocking-the-api)
- [Running all tests](#running-all-tests)
- [Unit tests with Jest](#unit-tests-with-jest)
- [Running unit tests](#running-unit-tests)
- [Introduction to Jest](#introduction-to-jest)
- [Unit test files](#unit-test-files)
- [Unit test helpers](#unit-test-helpers)
- [Unit test mocks](#unit-test-mocks)
- [End-to-end tests with Cypress](#end-to-end-tests-with-cypress)
- [Running end-to-end tests](#running-end-to-end-tests)
- [Introduction to Cypress](#introduction-to-cypress)
- [Accessibility-driven end-to-end tests](#accessibility-driven-end-to-end-tests)
- [The mock API](#the-mock-api)
- [Mock authentication](#mock-authentication)
- [Testing/developing against a real server](#testingdeveloping-against-a-real-server)
## Running all tests
```bash
# Run all tests
yarn test
```
## Unit tests with Jest
### Running unit tests
```bash
# Run unit tests
yarn test:unit
# Run unit tests in watch mode
yarn test:unit:watch
```
### Introduction to Jest
For unit tests, we use Jest with the `describe`/`expect` syntax. If you're not familiar with Jest, I recommend first browsing through the existing tests to get a sense for them.
Then at the very least, read about:
- [Jest's matchers](https://facebook.github.io/jest/docs/en/expect.html) for examples of other assertions you can make
- [Testing async code](https://facebook.github.io/jest/docs/en/asynchronous.html)
- [Setup and teardown](https://facebook.github.io/jest/docs/en/setup-teardown.html)
### Unit test files
Configuration for Jest is in `jest.config.js`, support files are in `tests/unit`, but as for the tests themselves - they're first-class citizens. That means they live alongside our source files, using the same name as the file they test, but with the extension `.unit.js`.
This may seem strange at first, but it makes poor test coverage obvious from a glance, even for those less familiar with the project. It also lowers the barrier to adding tests before creating a new file, adding a new feature, or fixing a bug.
### Unit test helpers
See [`tests/unit/setup.js`](../tests/unit/setup.js) for a list of helpers, including documentation in comments.
### Unit test mocks
Jest offers many tools for mocks, including:
- [For a function](https://facebook.github.io/jest/docs/en/mock-functions.html), use `jest.fn()`.
- [For a source file](https://facebook.github.io/jest/docs/en/manual-mocks.html#mocking-user-modules), add the mock to a `__mocks__` directory adjacent to the file.
- [For a dependency in `node_modules`](https://facebook.github.io/jest/docs/en/manual-mocks.html#mocking-node-modules), add the mock to `tests/unit/__mocks__`. You can see an example of this with the `axios` mock, which intercepts requests with relative URLs to either [our mock API](#the-mock-api) or a local/live API if the `API_BASE_URL` environment variable is set.
## End-to-end tests with Cypress
### Running end-to-end tests
```bash
# Run end to end tests
yarn test:e2e
# Run the dev server with the Cypress client
yarn dev:e2e
```
### Introduction to Cypress
Cypress offers many advantages over other test frameworks, including the abilities to:
- Travel through time to dissect the source of a problem when a test fails
- Automatically record video and screenshots of your tests
- Easily test in a wide range of screen sizes
And much more! I recommend checking out our Cypress tests in `tests/e2e/specs`, then reading through at least these sections of the excellent Cypress docs:
- [Core Concepts](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Cypress-Is-Simple)
- [Best Practices](https://docs.cypress.io/guides/references/best-practices.html)
Beyond that, also know that you can access our app in Cypress on the `window`. For example, to dispatch a Vuex action that sets up some state:
```js
cy.window().then((window) => {
return window.__app__.$store.dispatch('someModule/someAction')
})
```
### Accessibility-driven end-to-end tests
Ideally, tests should only fail when either:
- something is actually broken, or
- the requirements have changed
Unfortunately, there are _a lot_ of ways to get this wrong. For example, when creating a selector for a login link:
```js
cy.get('a')
// Too general, as there could be many links
cy.get('.login-link')
// Tied to implementation detail of CSS
cy.get('#login-link')
// Tied to implementation detail of JS and prevents component reusability
cy.contains('Log in')
// Assumes the text only appears in one context
```
To create the right selector, think from the perspective of the user. What _exactly_ are they looking for? They're not looking for:
```js
cy.get('a')
// Any link
cy.get('.login-link')
// An element with a specific class
cy.get('#login-link')
// An element with a specific id
cy.contains('Log in')
// Specific text anywhere on the page
```
But rather:
```js
cy.contains('a', 'Log in')
// A link containing the text "Log in"
```
Note that we're targeting a **semantic element**, meaning that it tells the web browser (and users) something about the element's role within the page. Also note that we're trying to be **as general as possible**. We're not looking for the link in a specific place, like a navbar or sidebar (unless that's part of the requirements), and we're not overly specific with the content. The link may also contain other content, like an icon, but that won't break the test, because we only care that _some link_ contains the text "Log in" _somewhere_ inside it.
Now, some will be thinking:
> "But isn't this brittle? Wouldn't it be better to add another attribute to the link, like `data-testid="login-link`? Then we could target that attribute and even if the element or content changes, the test won't break."
I would argue that if the link's semantic element or content changes so drastically that it's no longer an anchor and doesn't even contain the text "Log in" anymore, the requirements _have_ changed, so the test _should_ break. And from an accessibility perspective, the app might indeed be broken.
For example, let's imagine you replaced "Log in" with an icon:
```html
<a href="/login">
<span class="icon icon-login"></span>
</a>
```
Now users browsing your page with a screen reader will have no way to find the login link. From their perspective, this is just a link with no content. You may be tempted to try to fix the test with something like:
```js
cy.get('a[href="/login"]')
// A link going to "/login"
```
But when you're trying to find a login link as a user, you don't just inspect the destination of unlabeled links until you find one that looks like it's possibly a login page. That would be a very slow and painful experience!
Instead, thinking from a user's perspective forces you to stay accessible, perhaps updating your generated HTML to:
```html
<a aria-label="Log in" href="/login">
<span aria-hidden="true" class="icon icon-login"></span>
</a>
```
Then the selector in your test can update as well:
```js
cy.get('a[aria-label*="Log in"]')
// A link with a label containing the text "Log in"
```
And the app now works for everyone:
- Sighted users will see an icon that they'll (hopefully) have the cultural context to interpret as "Log in".
- Non-sighted users get a label with the text "Log in" read to them.
This strategy could be called **accessibility-driven end-to-end tests**, because you're parsing your own app with the same mindset as your users. It happens to be great for accessibility, but also helps to ensure that your app always breaks when requirements change, but never when you've just changed the implementation.
## The mock API
Working against the production API can be useful sometimes, but it also has some disadvantages:
- Networks requests are slow, which slows down both development and testing.
- Development and testing become dependent on a stable network connection.
- Hitting the production API often means modifying the production database, which you typically don't want to do during automated tests.
- To work on a frontend feature, the backend for it must already be complete.
The mock API is an [Express](https://expressjs.com/) server in `tests/mock-api` you can extend to - you guessed it - mock what the real API would do, solving all the problems listed above. This solution is also backend-agnostic, making it ideal for a wide variety of projects.
### Mock authentication
See the [`users` resource](../tests/mock-api/resources/users.js) in the mock API for a list of usernames and passwords you can use in development.
### Testing/developing against a real server
In some situations, you might prefer to test against a local server while developing, or maybe just during continuous integration. To do so, you can run any development or test command with the `API_BASE_URL` environment variable. For example:
```bash
API_BASE_URL=http://localhost:3000 yarn test
```
Or similarly, with a live server:
```bash
API_BASE_URL=https://staging.example.io yarn test
```

View File

@@ -0,0 +1,44 @@
# Troubleshooting
These are some troubleshooting tips for more common issues people might run into while developing, including more information on what might be happening and how to fix the problem.
- [Errors running scripts (e.g. `yarn dev`)](#errors-running-scripts-eg-yarn-dev)
- [Visual Studio (VS) Code formatting issues](#visual-studio-vs-code-formatting-issues)
## Errors running scripts (e.g. `yarn dev`)
Make sure you've followed the instructions for [Setup and development](development.md). If you already have, try deleting the `node_modules` folder and installing fresh:
```bash
# 1. Delete all previously-installed dependencies.
rm -rf node_modules
# 2. Install dependencies fresh.
yarn install
```
If that doesn't work, it's possible that a newer version of a dependency is creating a problem. If this is the problem, you can work around it by installing dependencies from the `yarn.lock` file of a previously working branch or commit.
```bash
# 1. Delete all previously-installed dependencies.
rm -rf node_modules
# 2. Use the same yarn.lock as the `origin/master` branch. If the problem
# exists on the `origin/master` as well, instead use the last-known
# working branch or commit.
git checkout origin/master -- yarn.lock
# 2. Install dependencies fresh, using only the exact versions specified
# in the `yarn.lock` file.
yarn install --frozen-lockfile
```
If this solves your problem, you can use `yarn outdated` to see the packages that may have received updates, then upgrade them one at a time with `yarn upgrade the-package-name` to see which upgrade introduces the problem.
## Visual Studio (VS) Code formatting issues
If you're using VS Code and notice that some files are being formatted incorrectly on save, the source is probably a formatter extension you've installed. The reason you're seeing it now is that this project enables the `editor.formatOnSave` setting. Previously, that extension was probably just doing nothing. To fix the problem, you'll need to either properly configure the extension or, if it's simply broken, uninstall it.
Extensions with known issues include:
- [Visual Studio Code Format](https://marketplace.visualstudio.com/items?itemName=ryannaddy.vscode-format#review-details)