Skip to content

Commit a4002a2

Browse files
authored
Merge pull request #34 from clarus/redux-ship
Redux ship
2 parents 7a90b04 + 842d7ea commit a4002a2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2457
-0
lines changed

redux-ship-clarus/.flowconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
[ignore]
3+
<PROJECT_ROOT>/node_modules/fbjs/.*
4+
5+
[include]
6+
7+
[libs]
8+
decls
9+
10+
[options]

redux-ship-clarus/.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# dependencies
4+
node_modules
5+
6+
# testing
7+
coverage
8+
9+
# production
10+
build
11+
12+
# misc
13+
.DS_Store
14+
.env
15+
npm-debug.log

redux-ship-clarus/README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Redux Ship
2+
> Implementation using [Redux Ship](https://github.com/clarus/redux-ship)
3+
4+
[Online demo](http://clarus.github.io/redux-ship/examples/scalable-frontend-with-elm-or-redux)
5+
6+
Run:
7+
```
8+
npm install
9+
npm start
10+
```
11+
12+
To run the tests:
13+
```
14+
npm test
15+
```
16+
17+
This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
18+
19+
## Idea
20+
The main idea for the composition in Redux Ship is to separate the actions modifying the Redux state into two categories:
21+
* the patches, which represent elementary and local updates;
22+
* the commits, which are a set of patches applied at the same time.
23+
24+
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).
25+
26+
For example, when we get a new gif url, we emit the single commit:
27+
```js
28+
yield* Ship.commit({
29+
type: 'LoadSuccess',
30+
gifUrl,
31+
});
32+
```
33+
which is then translated into a patch by the function `applyCommit`, to both update the image and increment the counter:
34+
```js
35+
export function applyCommit(state: State, commit: Commit): Patch {
36+
switch (commit.type) {
37+
case 'LoadStart':
38+
return {randomGif: commit};
39+
case 'LoadSuccess':
40+
return {
41+
counter: state.counter.count >= 10 && state.button.status === 'green' ?
42+
{type: 'IncrementByTwo'} :
43+
{type: 'IncrementByOne'},
44+
randomGif: commit,
45+
};
46+
default:
47+
return {};
48+
}
49+
}
50+
```
51+
Let us say that we get the following patch:
52+
```js
53+
{
54+
counter: {type: 'IncrementByOne'},
55+
randomGif: {
56+
type: 'LoadSuccess',
57+
gifUrl: 'http://media0.giphy.com/media/10rW4Xw9eO0RmU/giphy.gif',
58+
}
59+
}
60+
```
61+
This patch is formed of two sub-patches, `counter` and `randomGif`, which are dispatched to their corresponding reducers.
62+
63+
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:
64+
```js
65+
return yield* Ship.map(
66+
commit => ({type: 'First', commit}),
67+
state => ({
68+
button: state.button,
69+
counter: state.counter,
70+
randomGif: state.randomGifPair.first,
71+
}),
72+
RandomGifController.control(action.action)
73+
);
74+
```
75+
76+
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:
77+
```js
78+
{
79+
"type": "RandomGifPair",
80+
"commit": {
81+
"type": "First",
82+
"commit": {
83+
"type": "LoadSuccess",
84+
"gifUrl": "http://media4.giphy.com/media/a34HjLEsKchWM/giphy.gif"
85+
}
86+
}
87+
}
88+
```
89+
and a patch:
90+
```js
91+
{
92+
"counter": {
93+
"type": "IncrementByOne"
94+
},
95+
"randomGifPair": {
96+
"first": {
97+
"type": "LoadSuccess",
98+
"gifUrl": "http://media4.giphy.com/media/a34HjLEsKchWM/giphy.gif"
99+
}
100+
}
101+
}
102+
```
103+
Doing so, we aim to explicit the fact that we:
104+
* reuse a component into another component (there is a commit in the the commit);
105+
* have a component which modifies two sub-states (the patch is formed of two sub-patches).
106+
107+
We hope that being explicit in this way helps to write scalable and understable applications.

redux-ship-clarus/decls/jest.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// flow-typed signature: e2130120dcdc34bf09ff82449b0d508c
2+
// flow-typed version: 230d7577ce/jest_v12.0.x/flow_>=v0.23.x
3+
4+
type JestMockFn = {
5+
(...args: Array<any>): any;
6+
mock: {
7+
calls: Array<Array<any>>;
8+
instances: mixed;
9+
};
10+
mockClear(): Function;
11+
mockImplementation(fn: Function): JestMockFn;
12+
mockImplementationOnce(fn: Function): JestMockFn;
13+
mockReturnThis(): void;
14+
mockReturnValue(value: any): JestMockFn;
15+
mockReturnValueOnce(value: any): JestMockFn;
16+
}
17+
18+
type JestAsymmetricEqualityType = {
19+
asymmetricMatch(value: mixed): boolean;
20+
}
21+
22+
type JestCallsType = {
23+
allArgs(): mixed;
24+
all(): mixed;
25+
any(): boolean;
26+
count(): number;
27+
first(): mixed;
28+
mostRecent(): mixed;
29+
reset(): void;
30+
}
31+
32+
type JestClockType = {
33+
install(): void;
34+
mockDate(date: Date): void;
35+
tick(): void;
36+
uninstall(): void;
37+
}
38+
39+
type JestExpectType = {
40+
not: JestExpectType;
41+
lastCalledWith(...args: Array<any>): void;
42+
toBe(value: any): void;
43+
toBeCalled(): void;
44+
toBeCalledWith(...args: Array<any>): void;
45+
toBeCloseTo(num: number, delta: any): void;
46+
toBeDefined(): void;
47+
toBeFalsy(): void;
48+
toBeGreaterThan(number: number): void;
49+
toBeLessThan(number: number): void;
50+
toBeNull(): void;
51+
toBeTruthy(): void;
52+
toBeUndefined(): void;
53+
toContain(str: string): void;
54+
toEqual(value: any): void;
55+
toHaveBeenCalled(): void,
56+
toHaveBeenCalledWith(...args: Array<any>): void;
57+
toMatch(regexp: RegExp): void;
58+
toMatchSnapshot(): void;
59+
toThrow(message?: string | Error): void;
60+
toThrowError(message?: string): void;
61+
}
62+
63+
type JestSpyType = {
64+
calls: JestCallsType;
65+
}
66+
67+
declare function afterAll(fn: Function): void;
68+
declare function afterEach(fn: Function): void;
69+
declare function beforeAll(fn: Function): void;
70+
declare function beforeEach(fn: Function): void;
71+
declare function describe(name: string, fn: Function): void;
72+
declare function fdescribe(name: string, fn: Function): void;
73+
declare function fit(name: string, fn: Function): ?Promise<void>;
74+
declare function it(name: string, fn: Function): ?Promise<void>;
75+
declare function pit(name: string, fn: Function): Promise<void>;
76+
declare function test(name: string, fn: Function): ?Promise<void>;
77+
declare function xdescribe(name: string, fn: Function): void;
78+
declare function xit(name: string, fn: Function): ?Promise<void>;
79+
80+
declare function expect(value: any): JestExpectType;
81+
82+
// TODO handle return type
83+
// http://jasmine.github.io/2.4/introduction.html#section-Spies
84+
declare function spyOn(value: mixed, method: string): Object;
85+
86+
declare var jest: {
87+
autoMockOff(): void;
88+
autoMockOn(): void;
89+
clearAllTimers(): void;
90+
currentTestPath(): void;
91+
disableAutomock(): void;
92+
doMock(moduleName: string, moduleFactory?: any): void;
93+
dontMock(moduleName: string): void;
94+
enableAutomock(): void;
95+
fn(implementation?: Function): JestMockFn;
96+
genMockFromModule(moduleName: string): any;
97+
mock(moduleName: string, moduleFactory?: any): void;
98+
runAllTicks(): void;
99+
runAllTimers(): void;
100+
runOnlyPendingTimers(): void;
101+
setMock(moduleName: string, moduleExports: any): void;
102+
unmock(moduleName: string): void;
103+
useFakeTimers(): void;
104+
useRealTimers(): void;
105+
}
106+
107+
declare var jasmine: {
108+
DEFAULT_TIMEOUT_INTERVAL: number;
109+
any(value: mixed): JestAsymmetricEqualityType;
110+
anything(): void;
111+
arrayContaining(value: mixed[]): void;
112+
clock(): JestClockType;
113+
createSpy(name: string): JestSpyType;
114+
objectContaining(value: Object): void;
115+
stringMatching(value: string): void;
116+
}

redux-ship-clarus/package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "redux-ship-clarus",
3+
"version": "0.1.0",
4+
"private": true,
5+
"homepage": "http://clarus.github.io/redux-ship/examples/scalable-frontend-with-elm-or-redux",
6+
"devDependencies": {
7+
"react-scripts": "0.6.1"
8+
},
9+
"dependencies": {
10+
"babel-polyfill": "^6.16.0",
11+
"react": "^15.3.2",
12+
"react-dom": "^15.3.2",
13+
"react-test-renderer": "^15.3.2",
14+
"redux": "^3.6.0",
15+
"redux-ship": "0.0.13",
16+
"redux-ship-logger": "0.0.9"
17+
},
18+
"scripts": {
19+
"start": "react-scripts start",
20+
"build": "react-scripts build",
21+
"test": "react-scripts test --env=jsdom",
22+
"eject": "react-scripts eject"
23+
}
24+
}
24.3 KB
Binary file not shown.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
7+
<!--
8+
Notice the use of %PUBLIC_URL% in the tag above.
9+
It will be replaced with the URL of the `public` folder during the build.
10+
Only files inside the `public` folder can be referenced from the HTML.
11+
12+
Unlike "/favicon.ico" or "favico.ico", "%PUBLIC_URL%/favicon.ico" will
13+
work correctly both with client-side routing and a non-root public URL.
14+
Learn how to configure a non-root public URL by running `npm run build`.
15+
-->
16+
<title>React App</title>
17+
</head>
18+
<body>
19+
<div id="root"></div>
20+
<!--
21+
This HTML file is a template.
22+
If you open it directly in the browser, you will see an empty page.
23+
24+
You can add webfonts, meta tags, or analytics to this file.
25+
The build step will place the bundled scripts into the <body> tag.
26+
27+
To begin the development, run `npm start`.
28+
To create a production bundle, use `npm run build`.
29+
-->
30+
</body>
31+
</html>

0 commit comments

Comments
 (0)