Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
04109e1
Initialize with create-react-app
clarus Oct 10, 2016
3dc4ea0
Add a random gif component
clarus Oct 10, 2016
ab067f6
Add a pair of gifs
clarus Oct 10, 2016
60c1ee3
Both simple gif and pair of gif
clarus Oct 10, 2016
333055d
Rename App.js to view.js
clarus Oct 10, 2016
a31420d
Add a common counter
clarus Oct 10, 2016
8b5df27
Upgrade the code for the new Redux Ship version
clarus Oct 15, 2016
3f4b0d5
Upgrade the dependencies
clarus Oct 15, 2016
aa6329b
Remove useless semicolon
clarus Oct 15, 2016
062d307
Add a button with toggle
clarus Oct 15, 2016
73c56a7
Use the button status
clarus Oct 15, 2016
ca4e092
Add a Pair Pair Gif
clarus Oct 15, 2016
5676e0a
Add some style
clarus Oct 15, 2016
892a2f7
No sub-titles, larger pictures
clarus Oct 15, 2016
42b2f86
Add footer
clarus Oct 15, 2016
c56a719
Add a link to the homepage
clarus Oct 15, 2016
19a7e7e
Add snapshots for the view
clarus Oct 15, 2016
0341809
Add snapshots for the models
clarus Oct 15, 2016
57d1b4b
Add a test of applyCommit
clarus Oct 15, 2016
d78a16d
Add snapshot test of the controller
clarus Oct 16, 2016
176a865
Add simulation tests for the main controller
clarus Oct 16, 2016
e3493f2
Fix mobile rendering
clarus Oct 16, 2016
53f459c
Ugrade dependency
clarus Oct 16, 2016
16f23e3
Add an intro
clarus Oct 16, 2016
5109a8b
Upgrade the snpashots to add the intro
clarus Oct 16, 2016
ddf62db
Upgrade the dependencies
clarus Oct 18, 2016
0caf04f
Write a README
clarus Oct 23, 2016
c832446
Wording
clarus Oct 23, 2016
72078c5
Move the tests to __tests__ directories
clarus Oct 23, 2016
842d7ea
Add documentation to run the tests
clarus Oct 23, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions redux-ship-clarus/.flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

[ignore]
<PROJECT_ROOT>/node_modules/fbjs/.*

[include]

[libs]
decls

[options]
15 changes: 15 additions & 0 deletions redux-ship-clarus/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
node_modules

# testing
coverage

# production
build

# misc
.DS_Store
.env
npm-debug.log
107 changes: 107 additions & 0 deletions redux-ship-clarus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Redux Ship
> Implementation using [Redux Ship](https://github.com/clarus/redux-ship)

[Online demo](http://clarus.github.io/redux-ship/examples/scalable-frontend-with-elm-or-redux)

Run:
```
npm install
npm start
```

To run the tests:
```
npm test
```

This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).

## Idea
The main idea for the composition in Redux Ship is to separate the actions modifying the Redux state into two categories:
* the patches, which represent elementary and local updates;
* the commits, which are a set of patches applied at the same time.

The aim of this separation is to be more explicit about what we are doing. The naming commit / patch is an analogy with Git: a commit represents the "what", a patch represent the "how". Another difference is that there can be many patches into a commit (as many as modified files in the case of Git).

For example, when we get a new gif url, we emit the single commit:
```js
yield* Ship.commit({
type: 'LoadSuccess',
gifUrl,
});
```
which is then translated into a patch by the function `applyCommit`, to both update the image and increment the counter:
```js
export function applyCommit(state: State, commit: Commit): Patch {
switch (commit.type) {
case 'LoadStart':
return {randomGif: commit};
case 'LoadSuccess':
return {
counter: state.counter.count >= 10 && state.button.status === 'green' ?
{type: 'IncrementByTwo'} :
{type: 'IncrementByOne'},
randomGif: commit,
};
default:
return {};
}
}
```
Let us say that we get the following patch:
```js
{
counter: {type: 'IncrementByOne'},
randomGif: {
type: 'LoadSuccess',
gifUrl: 'http://media0.giphy.com/media/10rW4Xw9eO0RmU/giphy.gif',
}
}
```
This patch is formed of two sub-patches, `counter` and `randomGif`, which are dispatched to their corresponding reducers.

To compose two random gif components into a pair, we use a mechanism similar to the Elm architecture with a `map` function lifting a sub-component to the parent level:
```js
return yield* Ship.map(
commit => ({type: 'First', commit}),
state => ({
button: state.button,
counter: state.counter,
randomGif: state.randomGifPair.first,
}),
RandomGifController.control(action.action)
);
```

In the [redux-ship-logger](https://github.com/clarus/redux-ship-logger), the load of a new gif in the pair component shows both a commit:
```js
{
"type": "RandomGifPair",
"commit": {
"type": "First",
"commit": {
"type": "LoadSuccess",
"gifUrl": "http://media4.giphy.com/media/a34HjLEsKchWM/giphy.gif"
}
}
}
```
and a patch:
```js
{
"counter": {
"type": "IncrementByOne"
},
"randomGifPair": {
"first": {
"type": "LoadSuccess",
"gifUrl": "http://media4.giphy.com/media/a34HjLEsKchWM/giphy.gif"
}
}
}
```
Doing so, we aim to explicit the fact that we:
* reuse a component into another component (there is a commit in the the commit);
* have a component which modifies two sub-states (the patch is formed of two sub-patches).

We hope that being explicit in this way helps to write scalable and understable applications.
116 changes: 116 additions & 0 deletions redux-ship-clarus/decls/jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// flow-typed signature: e2130120dcdc34bf09ff82449b0d508c
// flow-typed version: 230d7577ce/jest_v12.0.x/flow_>=v0.23.x

type JestMockFn = {
(...args: Array<any>): any;
mock: {
calls: Array<Array<any>>;
instances: mixed;
};
mockClear(): Function;
mockImplementation(fn: Function): JestMockFn;
mockImplementationOnce(fn: Function): JestMockFn;
mockReturnThis(): void;
mockReturnValue(value: any): JestMockFn;
mockReturnValueOnce(value: any): JestMockFn;
}

type JestAsymmetricEqualityType = {
asymmetricMatch(value: mixed): boolean;
}

type JestCallsType = {
allArgs(): mixed;
all(): mixed;
any(): boolean;
count(): number;
first(): mixed;
mostRecent(): mixed;
reset(): void;
}

type JestClockType = {
install(): void;
mockDate(date: Date): void;
tick(): void;
uninstall(): void;
}

type JestExpectType = {
not: JestExpectType;
lastCalledWith(...args: Array<any>): void;
toBe(value: any): void;
toBeCalled(): void;
toBeCalledWith(...args: Array<any>): void;
toBeCloseTo(num: number, delta: any): void;
toBeDefined(): void;
toBeFalsy(): void;
toBeGreaterThan(number: number): void;
toBeLessThan(number: number): void;
toBeNull(): void;
toBeTruthy(): void;
toBeUndefined(): void;
toContain(str: string): void;
toEqual(value: any): void;
toHaveBeenCalled(): void,
toHaveBeenCalledWith(...args: Array<any>): void;
toMatch(regexp: RegExp): void;
toMatchSnapshot(): void;
toThrow(message?: string | Error): void;
toThrowError(message?: string): void;
}

type JestSpyType = {
calls: JestCallsType;
}

declare function afterAll(fn: Function): void;
declare function afterEach(fn: Function): void;
declare function beforeAll(fn: Function): void;
declare function beforeEach(fn: Function): void;
declare function describe(name: string, fn: Function): void;
declare function fdescribe(name: string, fn: Function): void;
declare function fit(name: string, fn: Function): ?Promise<void>;
declare function it(name: string, fn: Function): ?Promise<void>;
declare function pit(name: string, fn: Function): Promise<void>;
declare function test(name: string, fn: Function): ?Promise<void>;
declare function xdescribe(name: string, fn: Function): void;
declare function xit(name: string, fn: Function): ?Promise<void>;

declare function expect(value: any): JestExpectType;

// TODO handle return type
// http://jasmine.github.io/2.4/introduction.html#section-Spies
declare function spyOn(value: mixed, method: string): Object;

declare var jest: {
autoMockOff(): void;
autoMockOn(): void;
clearAllTimers(): void;
currentTestPath(): void;
disableAutomock(): void;
doMock(moduleName: string, moduleFactory?: any): void;
dontMock(moduleName: string): void;
enableAutomock(): void;
fn(implementation?: Function): JestMockFn;
genMockFromModule(moduleName: string): any;
mock(moduleName: string, moduleFactory?: any): void;
runAllTicks(): void;
runAllTimers(): void;
runOnlyPendingTimers(): void;
setMock(moduleName: string, moduleExports: any): void;
unmock(moduleName: string): void;
useFakeTimers(): void;
useRealTimers(): void;
}

declare var jasmine: {
DEFAULT_TIMEOUT_INTERVAL: number;
any(value: mixed): JestAsymmetricEqualityType;
anything(): void;
arrayContaining(value: mixed[]): void;
clock(): JestClockType;
createSpy(name: string): JestSpyType;
objectContaining(value: Object): void;
stringMatching(value: string): void;
}
24 changes: 24 additions & 0 deletions redux-ship-clarus/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "redux-ship-clarus",
"version": "0.1.0",
"private": true,
"homepage": "http://clarus.github.io/redux-ship/examples/scalable-frontend-with-elm-or-redux",
"devDependencies": {
"react-scripts": "0.6.1"
},
"dependencies": {
"babel-polyfill": "^6.16.0",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-test-renderer": "^15.3.2",
"redux": "^3.6.0",
"redux-ship": "0.0.13",
"redux-ship-logger": "0.0.9"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Binary file added redux-ship-clarus/public/favicon.ico
Binary file not shown.
31 changes: 31 additions & 0 deletions redux-ship-clarus/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favico.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start`.
To create a production bundle, use `npm run build`.
-->
</body>
</html>
Loading