Merge pull request #201 from coralproject/frontenddocs

Frontend Docs
This commit is contained in:
Kim Gardner
2017-01-10 11:44:03 -05:00
committed by GitHub
5 changed files with 879 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# Debug
How we debug errors at Coral
## React Debugging
For debugging React
### React Developer Tools
Another amazing tool for debugging React Applications. You can see where the props are, and much more.
[React Developer Tools Extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)
## Redux Debugging
For debugging Redux
### Redux Devtool Extension
Redux Devtool is an amazing debug tool. You can easily see what' happening with the state, the payloads, and more.
[Redux Devtool Chrome Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en)
[Redux Devtool Github Repo](https://github.com/zalmoxisus/redux-devtools-extension)
+390
View File
@@ -0,0 +1,390 @@
# InmutableJS
InmutableJS is a library from Facebook that provides a series of inmutable data structures. They are always immutable. The reference to them can change but the data inside of them cannot which means you can build predictable and reliable state models.
We use ImmutableJS in Talk and it becomes really easy to manage Talks application state. [Immutable.js](https://facebook.github.io/immutable-js/)
More about Immutable Data and React:
[React.js Conf 2015 - Immutable Data and React - YouTube](https://www.youtube.com/watch?v=I7IdS-PbEgI&feature=youtu.be)
## Why ImmutableJS?
- __Immutable Data is faster__
* Tracking mutation and Maintaining state is difficult
* Encourages you to think differently about how data flows through your application
## Getting Started
ImmutableJS API is pretty expense. We will try to cover the basics and more to show its power.
ImmutableJS provides many Persistent Immutable data structures including: `List()`, `Stack()`, `Map()`, `OrderedMap()`, `Set()`, `OrderedSet()` and `Record()`.
We will cover the most common data structures. `Map()` , `List()` and `Record()` and also we will describe the behaviour of `Seq()` with `Range()`
## Map()
- [Map()](https://facebook.github.io/immutable-js/docs/#/Map)
* Read values
* [get()](https://facebook.github.io/immutable-js/docs/#/Map/get)
* [has()](https://facebook.github.io/immutable-js/docs/#/Map/has)
* [first()](https://facebook.github.io/immutable-js/docs/#/Map/first)
* [last()](https://facebook.github.io/immutable-js/docs/#/Map/last)
* Read deep values
* [getIn()](https://facebook.github.io/immutable-js/docs/#/Map/getIn)
* Change Values
- [set()](https://facebook.github.io/immutable-js/docs/#/Map/set)
* [merge()](https://facebook.github.io/immutable-js/docs/#/Map/merge)
* [update()](https://facebook.github.io/immutable-js/docs/#/Map/update)
* [clear()](https://facebook.github.io/immutable-js/docs/#/Map/clear)
* [delete()](https://facebook.github.io/immutable-js/docs/#/Map/delete)
* Change deep values
* [setIn()](https://facebook.github.io/immutable-js/docs/#/Map/getIn)
* Conversion to JavaScript types
* [toJS()](https://facebook.github.io/immutable-js/docs/#/Map/toJS)
* [toArray()](https://facebook.github.io/immutable-js/docs/#/Map/toArray)
* [toObject](https://facebook.github.io/immutable-js/docs/#/Map/toObject)
* Member
* [size](https://facebook.github.io/immutable-js/docs/#/Map/size)
Creates a new Immutable Map. An Object graph. [Map - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Map)
```js
const data = {
one: {
title: One,
value: 1
},
two: {
title: Two,
value: 2
}
}
let map = Inmutable.Map(data)
```
### get()
Returns the value associated with the provided key, Since inmutable data cannot be mutated they create a new reference to the new data.
[get() - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Map/get)
```js
map.get(one).title
```
```js
let obj = { 1: one };
Object.keys(obj); // [ “1” ]
obj[1]; // “one”
obj[1]; // “one”
let map = Map(obj);
map.get(1); // “one”
map.get(1); // undefined
```
### getIn()
To get data from a deeply nested structure.
[getIn() - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Map/getIn)
*With a Map()*
```js
let map = Inmutable.Map({
title: Todo One,
text: Do todo
category: {
title: Some category,
order: 1
}
})
map.getIn([category, title]) // Some Category
```
### length - size
To get the size of a Map() or a List()
```js
map.size
```
### set()
```js
map.set(three, {title: three, value: 3})
```
### delete()
```js
map.delete(three, {title: three, value: 3})
```
### update()
```js
map.update(one, item => )
```
### clear()
Returns a new Map containing no keys or values.
```js
map.clear()
```
### merge()
Returns a new Map resulting from merging the provided iterables.
```js
let mapX = Inmutable.Map({a: 10, b: 20, c: 30})
let mapY = Inmutable.Map({a: 10, b: 20, c: 30})
mapX.merge(mapY) // { a: 50, b: 40, c: 30, d: 60 }
```
### Querying Methods
#### has
Returns a boolean if it finds the id key
```js
map.has(item.id)
```
#### first
Returns the first element of a Map
```js
map.first()
```
### Iteration Methods
We can use methods like `.filter`, `.map`, `.reduce` . However its not recommended to use `.forEach` since it can mutate the data producing side effects.
#### groupBy
Returns the first element of a Map
```js
items.groupBy(item => {
return todo.completed
});
```
### Working with Subsets of a Map()
#### slice()
Returns the last two items of a Map()
slice(<from>, <to>)
```js
items.slice(items.size-2, todos.size);
```
#### takeLast()
Returns the last two items of a Map()
```js
items.takeLast(2);
```
#### butLast()
Returns the last item
```js
items.butLast();
```
#### rest()
```js
items.rest();
```
#### skip()
Returns a Map() skipping the first 5 items
```js
items.skip(5);
```
#### skipUntil()
Returns a Map() skipping until it finds the value
```js
items.skipUntil(item => item.value === 1);
```
#### skipWhile()
Returns a Map() up until it finds 1 included.
```js
items.skipWhile(item => item.value === 1);
```
### Equality Methods
#### is()
```js
let mapX = Inmutable.Map({a: 10, b: 20, c: 30})
let mapY = Inmutable.Map({a: 10, b: 20, c: 30})
Immutable.is(mapX, mapY); // true
```
### FromJS
#### Object to Map()
Creates deeply nested Map() from a plain Javascript Object
```js
let object = {a: 10, b: 20, c: 30};
Immutable.fromJS(object); // Map()
```
#### Array to List()
Creates List() from a JS Array
```js
let array = [10,20,30];
Immutable.fromJS(object); // List()
```
#### Usage of the reviver function
The reviver function takes a key and a value. Converting JS to Map() or List()
```js
let array = [10,20,30];
Immutable.fromJS(array, (key, value) => {
return value.toMap();
}); // Map()
```
*Note: the getIn will be index based instead of object based if it comes from an array*
### List()
Most of the __Map()__ methods can be used with __List()__
But there are some differences.
### Differences between the Immutable Map() and List()
List() have the same methods that a JS Array has. But instead of mutating the array it returns a new one.
Usually we wouldnt use the push method in immutable data structures but with Immutable.List()s push methods are safe to be used.
```js
let list = Immutable.List()
list.push(3)
list.toArray() // [3]
```
#### get() and getIn()
The get method with Map() is _key_ based and with List() is _index_ based.
```js
// get()
let list = Immutable.List();
list.push(3);
list.get(0); // 3
let map = Immutable.Map();
list.set('active', true);
list.get('active'); // true
// getIn()
let map = Inmutable.List([10, 20, 30, [40, 50]])
map.getIn([3, 1]) // 50
```
#### of()
We can create a __List()__ by using the _of_ method
```js
const items = [];
const list = Immutable.List.of('red', 'green', 'blue');
```
*Using the spread operator:*
```js
const items = ['red', 'green', 'blue'];
const list = Immutable.List.of(...items);
```
### Sequences
Represents a sequence of values. [Seq() - Immutable.js](https://facebook.github.io/immutable-js/docs/#/Seq)
- Sequences are immutable — Once a sequence is created, it cannot be changed.
- Sequences are Lazy
Creating sequences with _of()_
```js
let range = [0, 1, 2 ... 999]
let sequence = Immutable.Seq.of(...range)
```
For Example: the following performs no work, because the resulting of the sequence values are never iterated:
```js
let operations = 0;
let squared = sequence.map(num => {
operations++;
return num * num;
})
operations; // 0
// Now using the sequence
squared.take(10).toArray();
operations; // 10
```
Once the sequence is used, it performs only the work necessary. It will return it only when you ask for them.
This is really powerful because it doesnt produce an overflow with infinite an infinite range.
```js
let squaredRange = Immutable.Range(1, Infinity);
squaredRange.size; // Infinity
first1000squared = squaredRange
.take(1000)
.map(n => n * n);
first1000squared.size; // 1000
```
__Seq()__ allows for the efficient chaining of operations
```js
let squaredOdds = Immutable.Range(0, Infinity)
.filter(n => n % 2 !== 0)
.map(n => n * n)
.take(1000);
console.log(
squaredOdds.toArray()
)
```
You can fin this example here: [Sequences - JS Bin](http://jsbin.com/nilekuj/edit?js,console)
[image:12FACC54-0BAF-4C93-A782-F77DB7CD04D3-813-00001ABD60F45CC4/Screen Shot 2016-12-22 at 8.23.33 AM.png]
## Memoization with Immutable JS
Immutable JS provides advanced memoization.
```js
const seq = Immutable.Range(1, Infinity)
.map(n => ({
value: n
}))
console.time(First Run);
seq.take(1000);
console.timeEnd(First Run); // First Run: 0.577ms
console.time(Second Run);
seq.take(1000);
console.timeEnd(Second Run); // Second Run: 0.165ms
```
### Play with Immutable JS
[JS Bin - Collaborative JavaScript Debugging](http://jsbin.com/nilekuj/edit?js,console)
+121
View File
@@ -0,0 +1,121 @@
# Frontend Architecture
## The Stack
- [React](#react)
- [Redux](#redux)
- [ImmutableJS](#immutablejs)
## The Architecture
Our frontend lives within [talk/client](https://github.com/coralproject/talk/tree/153193959cb4dfa5d8feaabb49811325f836ee68/client) folder. Every folder contains a plugin. In [coral-framework](https://github.com/coralproject/talk/tree/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework) you will find the core architecture of Talk.
Here is where our Redux Application, translations, components, and helpers live.
## Presentational and Container Components
We use a common simple pattern called
__Presentational and Container Components__
It basically consist in having two types of components:
- Presentational
- Containers
### Presentational Components
- __How our UI looks like__
- Are stateless components
- Render props
- Allow containment of children via `this.props.children`
- They have DOM Markup
### Container Components
* __How things work__
* They dont have markup nor styles
* They provide data and behaviour to Presentational or Container Components
* They connect via `react-redux`s `connect()` to the state.
* They `mapStateToProps` the state to the Presentational Container.
* They `mapDispatchToProps` to send actions to the Presentational Container.
* Name Convention `<Name>Container.js`
How a container looks like:
```js
/*
* mapStateToProps
* We map the part of the state that we want to use
*/
const mapStateToProps = state => ({
auth: state.auth.toJS()
});
/*
* mapDispatchToProps
* We map the actions that we want to use
*/
const mapDispatchToProps = dispatch => ({
checkLogin: () => dispatch(checkLogin())
});
/*
* connect
* We wrap our container in a connect() function
*/
export default connect(
mapStateToProps,
mapDispatchToProps
)(SignInContainer);
````
How our SignInContainer works: [talk/SignInContainer.js · GitHub](https://github.com/coralproject/talk/blob/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-sign-in/containers/SignInContainer.js)
Within our plugins we create two folders `containers` and `components` so we can differentiate them:
```
coral-sign-in/
├── containers/
│ └── SignInContainer.js
└── components/
├── SignInContent.js
└── SignUpContent.js
```
More about this architecture:
[Container Components Learn React with chantastic Medium](https://medium.com/@learnreact/container-components-c0e67432e005#.w8mzgndcg)
[Presentational and Container Components Dan Abramov Medium](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.ai4ih55v3)
## React
## Redux
We use Redux to handle the state container of Talk.
[How we to use Redux, and how we use it with Talk](https://github.com/coralproject/talk/blob/frontenddocs/docs/frontend/REDUX.md)
## ImmutableJS
We use Immutable JS to maintain our state immutable.
We found some really good tradeoffs while building Talk.
[How to use ImmutableJS and how we use it with Talk](https://github.com/coralproject/talk/blob/frontenddocs/docs/frontend/IMMUTABLEJS.md)
## Test
[How we do testing at Coral with Talk](https://github.com/coralproject/talk/blob/frontenddocs/docs/frontend/DEBUG.md)
## Lint
For linting in Talk we use `eslint:recommended`
You can find more info about the rules and best practices here:
http://eslint.org/docs/rules/#best-practices
## Lint the code
```js
npm run lint
```
## The Future of the Frontend
- Preact
- Reselect
+223
View File
@@ -0,0 +1,223 @@
# Redux
Redux is a predictable state container for JavaScript apps.
To understand Redux we need to dive into a few concepts.
- [Actions](#actions)
- [Action Creators](#actions)
- [Action Types](#actions)
- [Reducers](#reducers)
- [Stores](#store)
## The three principles
These are the three principles to build Redux applications. The following are specified in the Redux Documentation [Three Principles · Redux](http://redux.js.org/docs/introduction/ThreePrinciples.html)
### Single source of truth
The state of your whole application is stored in an object tree within a single store. We are going to represent the whole state of our application in a single Javascript Object.
### State is read-only
The only way to change the state is to emit an action, an object describing what happened.
### Changes are made with pure functions
To specify how the state tree is transformed by actions, you write pure reducers.
## Actions
Actions describe that something happened in our application. They are payloads of information that send data to your store. __They are the only source of information for the store.__
Here is an example:
```js
const ADD_COMMENT = 'ADD_COMMENT';
{
type: ADD_COMMENT,
comment: 'This is my comment.'
}
```
Actions are JavaScript objects. Every action must have a `type` property that indicates the type of action being performed. Types should be defined as constants.
Once an app becomes big enough, you may want to move them into a separate module. We store them in a `contants.js` file. [auth.js Constants](https://github.com/coralproject/talk/blob/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/constants/auth.js)
```js
import { ADD_COMMENT, REMOVE_COMMENT } from './constants'
```
We can dispatch an action by using `dispatch()`.
Our actions live within the `coral-framework/actions` folder. [talk/client/coral-framework/actions](https://github.com/coralproject/talk/tree/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/actions)
More about Actions: [Actions · Redux](http://redux.js.org/docs/basics/Actions.html)
### Async Actions
For our async operations we dispatch three actions.
- `<ACTION_TYPE>_REQUEST`
- `<ACTION_TYPE>_SUCCESS`
- `<ACTION_TYPE>_FAILURE`
#### Request
We use the postfix `_REQUEST` to know that the resource is being requested.
#### Success
We use the postfix `_SUCCESS` to know that the resource response came back successfully.
#### Failure
We use the postfix `_FAILURE` to know that the resource request failed.
## Action Creators
Action Creators are functions that return actions. This makes it easier to use, portable and testable.
```js
function addComment(comment) {
return {
type: ADD_COMMENT,
comment
}
}
```
So we can later trigger those actions by using `dispatch()`
```js
dispatch(addComment(comment))
dispatch(removeComment(comment.id))
```
## Dispatch Function
The `dispatch()` function can be accessed directly from the store as `store.dispatch()`, but more likely you'll access it using a helper like react-redux's`connect()`.
We use `connect()`in our containers. More about this in Architecture.
## Reducers
With Actions we describe that something happened in our application. But we dont specify how our state will be modified with this change.
In a Reducer we will specify how the state of our application change when an action has been dispatched.
Here we also will want to specify the `initialState`
Before building reducers its important to that you:
- Dont mutate the state
- Return the previous state in the default case.
Here is an example of an auth reducer:
```js
const initialState = {
isLoading: false,
loggedIn: false,
user: null,
error: ''
};
function auth (state = initialState, action) {
switch (action.type) {
case actions.CHECK_LOGIN_REQUEST:
return Object.assign({}, state, {
isLoading: true
});
case actions.CHECK_LOGIN_SUCCESS:
return Object.assign({}, state, {
isLoading: false,
loggedIn: true,
user: action.user,
error: ''
});
case actions.CHECK_LOGIN_FAILURE:
return Object.assign({}, state, {
isLoading: false,
error: action.error,
loggedIn: false,
user: null
});
default:
return state
}
}
```
Notice that a reducer takes the `state` as first argument and when its not defined it returns the `initialState`. As a second argument it takes the `action`. We have our state and we have the action. This is the time to specify how we modify the state.
### Reducers using ImmutableJS
We are using ImmutableJS to maintain our app state. Here is a guide on how to use ImmutableJS.
This is how a simplified version of our [auth reducer](https://github.com/coralproject/talk/blob/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/reducers/auth.js) looks like:
```js
const initialState = Map({
isLoading: false,
loggedIn: false,
user: null,
error: ‘’
});
function auth (state = initialState, action) {
switch (action.type) {
case CHECK_LOGIN_REQUEST:
return state
.set('isLoading', true);
case CHECK_LOGIN_SUCCESS:
return state
.set('isLoading', false)
.set('loggedIn', true)
.set('user', action.user)
.set('error', '');
});
case CHECK_LOGIN_FAILURE:
return state
.set('isLoading', false)
.set('error', action.error)
.set('loggedIn', false)
.set('user', null)
});
default:
return state
}
}
```
Looks cleaner, right?
Its pretty easy to follow. Here it says if a `CHECK_LOGIN_REQUEST` action has been dispatched set the `isLoading` from our state to `true`. And we can show a tiny loader to let the user now we are requesting something to the server.
Our actions live within the `coral-framework/reducers` folder. [talk/client/coral-framework/reducers ](https://github.com/coralproject/talk/tree/153193959cb4dfa5d8feaabb49811325f836ee68/client/coral-framework/reducers)
More about Reducers: [Reducers · Redux](http://redux.js.org/docs/basics/Reducers.html)
And the last thing we need to see is the __Store__
### Store
The `Store` is what holds the application state. Here we can access and update the state.
Its important to note that we will only have a single store in our application called `rootReducer` and we will use reducer composition instead of many stores.
Here is an example of how create a store with [createStore()](http://redux.js.org/docs/api/createStore.html) using a reducer:
```js
import { createStore } from 'redux'
import authReducer from './auth'
let store = createStore(authReducer)
```
We do have a lot of stores so we will need to combine all our reducers with [combineReducers()](http://redux.js.org/docs/api/combineReducers.html) within a single store
```js
import {combineReducers} from 'redux';
import authReducer from './auth'
import configReducer from './config'
import userReducer from './user'
const rootReducer = combineReducers({
authReducer,
configReducer,
userReducer
...
});
```
More about Stores: [Store · Redux](http://redux.js.org/docs/basics/Store.html)
## Useful Resources
[Redux Documentation · Redux](http://redux.js.org/)
[Getting Started with Redux](https://egghead.io/courses/getting-started-with-redux)
[Usage with React · Redux](http://redux.js.org/docs/basics/UsageWithReact.html)
+123
View File
@@ -0,0 +1,123 @@
# Test
How we do testing at Coral with Talk.
We use Nightwatch and Selenium for our E2E tests and Enzyme for our React Components.
## E2E tests
For our E2E Test we use Nightwatch and Selenium.
#### Selenium Server Setup
Selenium Server is a Java application which Nightwatch uses to connect to the various browsers.
You will need to have the Java Development Kit (JDK) installed.
[Java SE Development Kit 8 - Downloads](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
The minimum required version is 7.
You can check this by running `java -version`
#### Folder Structure
```
e2e/
├── pages
| ├── adminPage.js
| └── embedStreamPage.js
├── reports
├── tests
| ├── Admin
| ├── Commenter
| ├── Moderator
| └── Visitor
```
#### Pages
Here we will have all the selectors and commands for a Page
#### Reports
The folder that Nightwatch will use after running the tests
#### Tests
Within `tests` folder we have 4 Folders and a couple of files.
`Admin`, `Commenter`, `Moderator`, `Visitor` contains all the group tests based on the user role and their actions.
## Tests
The `pree2e` script will create 3 users: a Commenter, a Moderator, and an Admin
* Commenter
* Login
- Post a comment
* Likes a comment
* Flag a comment
* Flag a username
* Gets Permalink
* Visits Permalink
- Moderator
* Login
- Admin
* Login
- Approve Comment
- Reject Comment
* Ban User
- Visitor
* Tries to like a comment
* Tries to flag a comment
- Tries to flag a username
* Signs up
## Run the tests
Run Talk
`dotenv npm run start`
Run e2e tests
`npm run e2e`
## Advanced Nightwatch and Selenium Settings
### Adding an Integration Environment
```json
{
test_settings : {
default : {
launch_url : http://localhost”,
globals : {
myGlobalVar : some value,
otherGlobal : some other value
}
},
integration : {
launch_url : http://staging.host”,
globals : {
myGlobalVar : other value
}
}
}
}
```
`nightwatch —env integration`
### Chrome Options
[List of Chromium Command Line Switches « Peter Beverloo](http://peter.sh/experiments/chromium-command-line-switches/)
## Tags
You'll notice that each test file starts with tags. This is useful to selectively target tests to run.
_i.e nightwatch --tag login will only run login tests tagged with login_
```js
module.exports {
'@tags': ['login'],
'Test': browser => {
[...]
}
}
```
Source: http://nightwatchjs.org/guide#test-tags