mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2026-03-11 15:12:40 +03:00
Compare commits
341 Commits
remotedev-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd709cf7ed | ||
|
|
c6fd44977c | ||
|
|
648138652a | ||
|
|
12849a4982 | ||
|
|
804d6bd061 | ||
|
|
3f90241dd7 | ||
|
|
804e729e79 | ||
|
|
d61d31a690 | ||
|
|
12e561c24f | ||
|
|
d574d6c038 | ||
|
|
c752dde196 | ||
|
|
ab0a944f93 | ||
|
|
ed6eb100eb | ||
|
|
bdb08fca6c | ||
|
|
e0f127ceea | ||
|
|
55bcccb4b8 | ||
|
|
0cd341fe35 | ||
|
|
62cb6ddc25 | ||
|
|
ed6d65bd30 | ||
|
|
8f5cf1b5bc | ||
|
|
21d5699045 | ||
|
|
d609bfb14d | ||
|
|
b350e7bae9 | ||
|
|
35aa092d45 | ||
|
|
102c21d43e | ||
|
|
b8219c2afc | ||
|
|
1e701e45cb | ||
|
|
78ed3b1efe | ||
|
|
fbdbb56d19 | ||
|
|
c7e77652c2 | ||
|
|
558e2ecfd3 | ||
|
|
d0e04165ab | ||
|
|
aebfca4f93 | ||
|
|
56de415db0 | ||
|
|
36f1f36471 | ||
|
|
1039d6c827 | ||
|
|
ebaf7cf102 | ||
|
|
a4343c3116 | ||
|
|
75cafed485 | ||
|
|
a663401e42 | ||
|
|
60a027e016 | ||
|
|
85e0eb9a67 | ||
|
|
8e6014f9d3 | ||
|
|
70c98a0c05 | ||
|
|
ebaf3e0516 | ||
|
|
10f112c0ee | ||
|
|
ca3782c545 | ||
|
|
9d4924ef1d | ||
|
|
16087352e1 | ||
|
|
f80b555457 | ||
|
|
c7a9b746d5 | ||
|
|
f0330162e6 | ||
|
|
531aa338e7 | ||
|
|
8308db6832 | ||
|
|
4e44c2ca50 | ||
|
|
4e81dc99c4 | ||
|
|
e9e6b33a6d | ||
|
|
20883e5bdf | ||
|
|
0e9528ea37 | ||
|
|
e327727f91 | ||
|
|
4a616983aa | ||
|
|
13e65e0f73 | ||
|
|
c3e35815d8 | ||
|
|
b1e2d4bd58 | ||
|
|
68f82b6994 | ||
|
|
30308f54ab | ||
|
|
5b28b69261 | ||
|
|
7a48268a74 | ||
|
|
fa20f09fc1 | ||
|
|
1e0d09251b | ||
|
|
2b7e1b890a | ||
|
|
119864cd95 | ||
|
|
dfa6c937c3 | ||
|
|
8b554db5bf | ||
|
|
842419b802 | ||
|
|
1e3fb5e27a | ||
|
|
da5e9f0c1a | ||
|
|
54c40135e5 | ||
|
|
61632768a7 | ||
|
|
585d6b9220 | ||
|
|
6468c48b75 | ||
|
|
41f3c1a7b6 | ||
|
|
c82e511929 | ||
|
|
832f4dc195 | ||
|
|
4b35476610 | ||
|
|
7873dd23ae | ||
|
|
9444ca7e87 | ||
|
|
b2f01026cb | ||
|
|
04c234dd49 | ||
|
|
c394803622 | ||
|
|
fd56ac7d92 | ||
|
|
65dce0864e | ||
|
|
e02cfd327c | ||
|
|
9ddcef672f | ||
|
|
3f52f16473 | ||
|
|
9f12777827 | ||
|
|
5dc8611c38 | ||
|
|
1c5df1ee32 | ||
|
|
3fc48a226b | ||
|
|
17b55ef99f | ||
|
|
73a01cc5a7 | ||
|
|
07e8f2c3ad | ||
|
|
8c563d71bb | ||
|
|
d730ea185f | ||
|
|
6df66b7320 | ||
|
|
3c90662cfa | ||
|
|
763bf937a4 | ||
|
|
8ec2b303ee | ||
|
|
91f21b2ffc | ||
|
|
ff60266836 | ||
|
|
6830118951 | ||
|
|
cc7ec13fb9 | ||
|
|
e46bbcaef3 | ||
|
|
4a8fbc675a | ||
|
|
c9bed44e0e | ||
|
|
750046c4d9 | ||
|
|
f42403c579 | ||
|
|
519ec090c6 | ||
|
|
4e71048997 | ||
|
|
5b33056bc5 | ||
|
|
da0051706e | ||
|
|
6ea51af67c | ||
|
|
9a46407254 | ||
|
|
07bd4476b2 | ||
|
|
b9993eb5f7 | ||
|
|
5228d46328 | ||
|
|
2002a81071 | ||
|
|
c065b03caa | ||
|
|
81a9ee33cc | ||
|
|
3c47910110 | ||
|
|
1c6f45fb47 | ||
|
|
054b27a9f8 | ||
|
|
415257cd41 | ||
|
|
14201108e7 | ||
|
|
90737b7e26 | ||
|
|
54c6f26c81 | ||
|
|
6e400f68b3 | ||
|
|
2c65192b4f | ||
|
|
88a02a8332 | ||
|
|
04858cd514 | ||
|
|
b25bf1304b | ||
|
|
41fae27637 | ||
|
|
fdce076757 | ||
|
|
b934e80d23 | ||
|
|
50d7682776 | ||
|
|
344387c9c6 | ||
|
|
80c570d6d0 | ||
|
|
aca0cd09a1 | ||
|
|
75dbf74963 | ||
|
|
997f7b636d | ||
|
|
eb3ac09b03 | ||
|
|
3c39eb49f2 | ||
|
|
61c09e1cc3 | ||
|
|
f1d61580a8 | ||
|
|
c5aef77b85 | ||
|
|
908e1c11bd | ||
|
|
d591c18fc8 | ||
|
|
bc4b0755c3 | ||
|
|
2c8f0a0544 | ||
|
|
68d4440e38 | ||
|
|
c52962d532 | ||
|
|
18b86498e2 | ||
|
|
01ee4e99be | ||
|
|
c133d59461 | ||
|
|
fd9f9504f0 | ||
|
|
e49708d831 | ||
|
|
f64cbda982 | ||
|
|
abd03a70c7 | ||
|
|
b3e8f209fd | ||
|
|
238a38fb21 | ||
|
|
76183cfa43 | ||
|
|
9fa9a6ff79 | ||
|
|
2a93de46a1 | ||
|
|
83b2c19a11 | ||
|
|
61ec00f505 | ||
|
|
7f41fcf0fc | ||
|
|
73688e117a | ||
|
|
4164b6279e | ||
|
|
9b2f8720c9 | ||
|
|
0bd66c7388 | ||
|
|
8682d05b0b | ||
|
|
bd463b19ec | ||
|
|
0462470dca | ||
|
|
9a78f414ae | ||
|
|
652bcfa7f8 | ||
|
|
2163bc3f09 | ||
|
|
6cf528b4a0 | ||
|
|
ae3f4e59f5 | ||
|
|
fce9074175 | ||
|
|
3184647fa9 | ||
|
|
4c73661e78 | ||
|
|
23f1c47224 | ||
|
|
2c1a74cdff | ||
|
|
baed7ccd07 | ||
|
|
443b993f6a | ||
|
|
65126657cb | ||
|
|
42e11518f3 | ||
|
|
5e81525818 | ||
|
|
1735536bd6 | ||
|
|
51e46328a5 | ||
|
|
b55cdf0aea | ||
|
|
cacf7cf182 | ||
|
|
a1fe1a4018 | ||
|
|
08336c06ca | ||
|
|
76711b7ee1 | ||
|
|
96ac1f291a | ||
|
|
a4382ecb9c | ||
|
|
d9cdc25cb2 | ||
|
|
819000df9b | ||
|
|
35fb9b12f4 | ||
|
|
d5bbd78c52 | ||
|
|
25aeba0bd8 | ||
|
|
846ded1c74 | ||
|
|
04d141932d | ||
|
|
1fc9f76e50 | ||
|
|
249b3a030b | ||
|
|
612a23488d | ||
|
|
367fce39e6 | ||
|
|
57755f6f7d | ||
|
|
9c1b1b84b8 | ||
|
|
6842c895d5 | ||
|
|
911b063fba | ||
|
|
c8cf847681 | ||
|
|
af486b01e9 | ||
|
|
bbb1a40395 | ||
|
|
18cde73771 | ||
|
|
191d419773 | ||
|
|
629419bd1b | ||
|
|
b56a3aa0dc | ||
|
|
f3878541c2 | ||
|
|
075e9f6099 | ||
|
|
138f4f37b6 | ||
|
|
8f166edc73 | ||
|
|
9545a46c5d | ||
|
|
cb4772fa2c | ||
|
|
ab7df4cdd6 | ||
|
|
2eddfd60a8 | ||
|
|
dc6b584ad0 | ||
|
|
5c6aa8aa0a | ||
|
|
4c6add7aa2 | ||
|
|
f282e26a59 | ||
|
|
d0b89ca577 | ||
|
|
d76df9985f | ||
|
|
3b7bca2dbb | ||
|
|
6b80162ca9 | ||
|
|
3c44f223f5 | ||
|
|
fe9a3674e0 | ||
|
|
eba12e74f0 | ||
|
|
8257f616f5 | ||
|
|
9e4e054f8b | ||
|
|
31aabfa82c | ||
|
|
7b90e75e03 | ||
|
|
8f3b306a66 | ||
|
|
57af776834 | ||
|
|
c3497c1b17 | ||
|
|
eff9410828 | ||
|
|
264f076252 | ||
|
|
a0db02b31e | ||
|
|
eff9bee92d | ||
|
|
baf484adbc | ||
|
|
c9f2491d7e | ||
|
|
a3a6eb4cb3 | ||
|
|
4dd0dd2d19 | ||
|
|
8aa37659b3 | ||
|
|
28a08cde92 | ||
|
|
1f1d188601 | ||
|
|
81e3efd070 | ||
|
|
91dd5df25b | ||
|
|
1b4c36c46a | ||
|
|
32d1ee0894 | ||
|
|
e2e65dbeab | ||
|
|
c42e33437e | ||
|
|
d4adb601da | ||
|
|
fa7af18888 | ||
|
|
00664dcd3e | ||
|
|
b79b2c3bbc | ||
|
|
e9afa8fb27 | ||
|
|
5cfe3e5522 | ||
|
|
decc035570 | ||
|
|
6f9ef2e941 | ||
|
|
3205269f8c | ||
|
|
d165cc7bd9 | ||
|
|
c03264c54d | ||
|
|
6954eb9580 | ||
|
|
178002de65 | ||
|
|
ae1bc3aaae | ||
|
|
158ba2ce12 | ||
|
|
b54bc75cbb | ||
|
|
37fee5574a | ||
|
|
57d97026f3 | ||
|
|
2e4929b3b7 | ||
|
|
d95a5ff5ca | ||
|
|
7f5bddbdc2 | ||
|
|
6fc18ed74e | ||
|
|
8e99d35749 | ||
|
|
a3f86a42df | ||
|
|
a7d612fbdc | ||
|
|
8979004b53 | ||
|
|
3f5719ec1f | ||
|
|
c723e4a3c8 | ||
|
|
057b5e6de7 | ||
|
|
ba56d5b96d | ||
|
|
e8da9f0945 | ||
|
|
487775b206 | ||
|
|
2ab5bd833e | ||
|
|
262ea85ce1 | ||
|
|
d57d015de9 | ||
|
|
42531c503e | ||
|
|
11be87f31a | ||
|
|
65205f9078 | ||
|
|
963f1963e7 | ||
|
|
e7e43f16b0 | ||
|
|
27da2edc1a | ||
|
|
46911b13dd | ||
|
|
c781ac0624 | ||
|
|
b649e13f5b | ||
|
|
c6464ef371 | ||
|
|
669c87b9f4 | ||
|
|
8a216f531e | ||
|
|
986acf4e73 | ||
|
|
508506fe90 | ||
|
|
cacd101481 | ||
|
|
c280ec5988 | ||
|
|
20024cfb5b | ||
|
|
9695e499f2 | ||
|
|
840aa45c19 | ||
|
|
7e6d04380b | ||
|
|
e57bcb3933 | ||
|
|
f523d06499 | ||
|
|
bca760097b | ||
|
|
ee64102c1d | ||
|
|
64ed81b09a | ||
|
|
50f84218cd | ||
|
|
801e1a4cba | ||
|
|
02e211463b | ||
|
|
2ca07f5c37 | ||
|
|
92f0e217d4 | ||
|
|
c50bdc02e8 | ||
|
|
4e0620c131 | ||
|
|
546c98d406 | ||
|
|
391ab8b565 |
|
|
@ -6,5 +6,8 @@
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": []
|
"ignore": [],
|
||||||
|
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||||
|
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
.gitattributes
vendored
15
.gitattributes
vendored
|
|
@ -1,14 +1 @@
|
||||||
*.js text eol=lf
|
* text=auto eol=lf
|
||||||
*.jsx text eol=lf
|
|
||||||
*.ts text eol=lf
|
|
||||||
*.tsx text eol=lf
|
|
||||||
*.json text eol=lf
|
|
||||||
*.css text eol=lf
|
|
||||||
*.html text eol=lf
|
|
||||||
*.md text eol=lf
|
|
||||||
*.yml text eol=lf
|
|
||||||
*.graphql text eol=lf
|
|
||||||
.eslintrc text eol=lf
|
|
||||||
.prettierrc text eol=lf
|
|
||||||
.babelrc text eol=lf
|
|
||||||
.stylelintrc text eol=lf
|
|
||||||
|
|
|
||||||
13
.github/workflows/CI.yml
vendored
13
.github/workflows/CI.yml
vendored
|
|
@ -8,15 +8,12 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: 'ubuntu-22.04'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v6
|
||||||
with:
|
- uses: pnpm/action-setup@v4
|
||||||
fetch-depth: 0
|
- uses: actions/setup-node@v6
|
||||||
- uses: nrwl/nx-set-shas@v3
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
@ -29,6 +26,6 @@ jobs:
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm run lint:all
|
run: pnpm run lint:all
|
||||||
- name: Test
|
- name: Test
|
||||||
uses: GabrielBB/xvfb-action@v1
|
uses: coactions/setup-xvfb@v1
|
||||||
with:
|
with:
|
||||||
run: pnpm run test:all
|
run: pnpm run test:all
|
||||||
|
|
|
||||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -10,18 +10,18 @@ permissions: write-all
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: 'ubuntu-22.04'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
@ -40,19 +40,19 @@ jobs:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
- name: Archive Chrome Extension
|
- name: Archive Chrome Extension
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: chrome
|
name: chrome
|
||||||
path: extension/chrome/dist
|
path: extension/chrome/dist
|
||||||
|
|
||||||
- name: Archive Edge Extension
|
- name: Archive Edge Extension
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: edge
|
name: edge
|
||||||
path: extension/edge/dist
|
path: extension/edge/dist
|
||||||
|
|
||||||
- name: Archive Firefox Extension
|
- name: Archive Firefox Extension
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: firefox
|
name: firefox
|
||||||
path: extension/firefox/dist
|
path: extension/firefox/dist
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,3 +9,4 @@ coverage
|
||||||
.idea
|
.idea
|
||||||
.eslintcache
|
.eslintcache
|
||||||
!packages/redux-devtools-slider-monitor/examples/todomvc/dist/index.html
|
!packages/redux-devtools-slider-monitor/examples/todomvc/dist/index.html
|
||||||
|
.nx
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ It can be used as a browser extension (for [Chrome](https://chrome.google.com/we
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
This is a monorepo powered by [pnpm](https://pnpm.io/) and [Nx](https://nx.dev/). [Install pnpm](https://pnpm.io/installation) and run `pnpm install` to get started. Each package's dependencies need to be built before the package itself can be built. You can either build all the packages (i.e., `pnpm run build:all`) or use Nx commands to build only the packages necessary for the packages you're working on (i.e., `pnpm nx build remotedev-redux-devtools-extension`).
|
This is a monorepo powered by [pnpm](https://pnpm.io/). [Install pnpm](https://pnpm.io/installation) and run `pnpm install` to get started. Each package's dependencies need to be built before the package itself can be built. You can either build all the packages (i.e., `pnpm run build:all`) or use pnpm workspace commands to build only the packages necessary for the packages you're working on (i.e., `pnpm --filter "remotedev-redux-devtools-extension" build`).
|
||||||
|
|
||||||
## Backers
|
## Backers
|
||||||
|
|
||||||
|
|
|
||||||
5
eslint.js.config.base.mjs
Normal file
5
eslint.js.config.base.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default defineConfig([eslint.configs.recommended, eslintConfigPrettier]);
|
||||||
41
eslint.js.react.jest.config.base.mjs
Normal file
41
eslint.js.react.jest.config.base.mjs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import react from 'eslint-plugin-react';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import jest from 'eslint-plugin-jest';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
...eslint.configs.recommended,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
...react.configs.flat.recommended,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
...reactHooks.configs.flat.recommended,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
...jest.configs['flat/recommended'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
...jest.configs['jest/style'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
...eslintConfigPrettier,
|
||||||
|
},
|
||||||
|
]);
|
||||||
57
eslint.ts.config.base.mjs
Normal file
57
eslint.ts.config.base.mjs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default (tsconfigRootDir, files = ['**/*.ts'], project = true) =>
|
||||||
|
defineConfig([
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
...eslint.configs.recommended,
|
||||||
|
},
|
||||||
|
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
||||||
|
files,
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
||||||
|
files,
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project,
|
||||||
|
tsconfigRootDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
...eslintConfigPrettier,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unsafe-return': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-call': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||||
|
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||||
|
'@typescript-eslint/no-base-to-string': 'off',
|
||||||
|
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/prefer-for-of': 'off',
|
||||||
|
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
||||||
|
'@typescript-eslint/class-literal-property-style': 'off',
|
||||||
|
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
||||||
|
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/array-type': 'off',
|
||||||
|
'@typescript-eslint/prefer-function-type': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
66
eslint.ts.jest.config.base.mjs
Normal file
66
eslint.ts.jest.config.base.mjs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import jest from 'eslint-plugin-jest';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default (tsconfigRootDir) =>
|
||||||
|
defineConfig([
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
...eslint.configs.recommended,
|
||||||
|
},
|
||||||
|
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.test.json'],
|
||||||
|
tsconfigRootDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
...jest.configs['flat/recommended'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
...jest.configs['jest/style'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
...eslintConfigPrettier,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unsafe-return': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-call': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||||
|
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||||
|
'@typescript-eslint/no-base-to-string': 'off',
|
||||||
|
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/prefer-for-of': 'off',
|
||||||
|
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
||||||
|
'@typescript-eslint/class-literal-property-style': 'off',
|
||||||
|
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
||||||
|
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/array-type': 'off',
|
||||||
|
'@typescript-eslint/prefer-function-type': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
88
eslint.ts.react.config.base.mjs
Normal file
88
eslint.ts.react.config.base.mjs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import react from 'eslint-plugin-react';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default (
|
||||||
|
tsconfigRootDir,
|
||||||
|
files = ['**/*.ts', '**/*.tsx'],
|
||||||
|
project = true,
|
||||||
|
) =>
|
||||||
|
defineConfig([
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
...eslint.configs.recommended,
|
||||||
|
},
|
||||||
|
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
||||||
|
files,
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
||||||
|
files,
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project,
|
||||||
|
tsconfigRootDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
...react.configs.flat.recommended,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
...reactHooks.configs.flat.recommended,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
...eslintConfigPrettier,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files,
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unsafe-return': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-call': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||||
|
'@typescript-eslint/no-misused-promises': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
checksVoidReturn: {
|
||||||
|
attributes: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||||
|
'@typescript-eslint/no-base-to-string': 'off',
|
||||||
|
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/prefer-for-of': 'off',
|
||||||
|
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
||||||
|
'@typescript-eslint/class-literal-property-style': 'off',
|
||||||
|
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
||||||
|
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/array-type': 'off',
|
||||||
|
'@typescript-eslint/prefer-function-type': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
84
eslint.ts.react.jest.config.base.mjs
Normal file
84
eslint.ts.react.jest.config.base.mjs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import react from 'eslint-plugin-react';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import jest from 'eslint-plugin-jest';
|
||||||
|
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default (tsconfigRootDir) =>
|
||||||
|
defineConfig([
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...eslint.configs.recommended,
|
||||||
|
},
|
||||||
|
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...config,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.test.json'],
|
||||||
|
tsconfigRootDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...react.configs.flat.recommended,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...reactHooks.configs.flat.recommended,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...jest.configs['flat/recommended'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...jest.configs['jest/style'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
...eslintConfigPrettier,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unsafe-return': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-call': 'off',
|
||||||
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||||
|
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||||
|
'@typescript-eslint/no-base-to-string': 'off',
|
||||||
|
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/prefer-for-of': 'off',
|
||||||
|
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
||||||
|
'@typescript-eslint/class-literal-property-style': 'off',
|
||||||
|
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
||||||
|
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
||||||
|
'@typescript-eslint/array-type': 'off',
|
||||||
|
'@typescript-eslint/prefer-function-type': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"parser": "@babel/eslint-parser"
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["@typescript-eslint"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"plugins": ["jest"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
|
||||||
"plugin:jest/recommended",
|
|
||||||
"plugin:jest/style",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"jsx": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"plugins": ["@typescript-eslint", "react"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"react": {
|
|
||||||
"version": "detect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
|
||||||
"@typescript-eslint/no-misused-promises": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"checksVoidReturn": {
|
|
||||||
"attributes": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"plugins": ["jest"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"plugin:jest/recommended",
|
|
||||||
"plugin:jest/style",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
examples
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"extends": "eslint-config-airbnb",
|
|
||||||
"globals": {
|
|
||||||
"chrome": true,
|
|
||||||
"__DEVELOPMENT__": true
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"react/jsx-uses-react": 2,
|
|
||||||
"react/jsx-uses-vars": 2,
|
|
||||||
"react/react-in-jsx-scope": 2,
|
|
||||||
"react/jsx-quotes": 0,
|
|
||||||
"block-scoped-var": 0,
|
|
||||||
"padded-blocks": 0,
|
|
||||||
"quotes": [1, "single"],
|
|
||||||
"comma-style": [2, "last"],
|
|
||||||
"no-use-before-define": [0, "nofunc"],
|
|
||||||
"func-names": 0,
|
|
||||||
"prefer-const": 0,
|
|
||||||
"comma-dangle": 0,
|
|
||||||
"id-length": 0,
|
|
||||||
"indent": [2, 2, { "SwitchCase": 1 }],
|
|
||||||
"new-cap": [2, { "capIsNewExceptions": ["Test"] }],
|
|
||||||
"default-case": 0
|
|
||||||
},
|
|
||||||
"plugins": ["react"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,178 @@
|
||||||
# remotedev-redux-devtools-extension
|
# remotedev-redux-devtools-extension
|
||||||
|
|
||||||
|
## 3.2.12
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [3f90241]
|
||||||
|
- Updated dependencies [d61d31a]
|
||||||
|
- Updated dependencies [804e729]
|
||||||
|
- Updated dependencies [12849a4]
|
||||||
|
- Updated dependencies [804d6bd]
|
||||||
|
- Updated dependencies [6481386]
|
||||||
|
- @redux-devtools/instrument@3.0.0
|
||||||
|
- @redux-devtools/ui@3.0.0
|
||||||
|
- @redux-devtools/slider-monitor@7.0.0
|
||||||
|
- @redux-devtools/core@5.0.0
|
||||||
|
- @redux-devtools/app@8.0.0
|
||||||
|
- @redux-devtools/serialize@1.0.0
|
||||||
|
- @redux-devtools/utils@4.0.0
|
||||||
|
|
||||||
|
## 3.2.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [6163276]
|
||||||
|
- @redux-devtools/app@7.0.0
|
||||||
|
- @redux-devtools/slider-monitor@6.0.0
|
||||||
|
- @redux-devtools/ui@2.0.0
|
||||||
|
|
||||||
|
## 3.2.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @redux-devtools/app@6.2.2
|
||||||
|
|
||||||
|
## 3.2.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [91f21b2]
|
||||||
|
- @redux-devtools/core@4.1.1
|
||||||
|
- @redux-devtools/slider-monitor@5.1.1
|
||||||
|
- @redux-devtools/utils@3.1.1
|
||||||
|
- @redux-devtools/app@6.2.1
|
||||||
|
|
||||||
|
## 3.2.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [6830118]
|
||||||
|
- react-json-tree@0.20.0
|
||||||
|
- @redux-devtools/app@6.2.0
|
||||||
|
- @redux-devtools/slider-monitor@6.0.0
|
||||||
|
- @redux-devtools/ui@1.4.0
|
||||||
|
- @redux-devtools/core@4.1.0
|
||||||
|
- @redux-devtools/utils@4.0.0
|
||||||
|
|
||||||
|
## 3.2.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b25bf13: Send state from background when monitor connects
|
||||||
|
|
||||||
|
## 3.2.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 50d7682: Fix DevTools from losing connection
|
||||||
|
|
||||||
|
## 3.2.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- eb3ac09: Add logging to background service worker
|
||||||
|
|
||||||
|
## 3.2.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- f1d6158: Fix mocking Chrome API for Electron
|
||||||
|
|
||||||
|
## 3.2.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- fd9f950: Fix monitoring on opening panel
|
||||||
|
- e49708d: Fix manifest.json for Edge
|
||||||
|
|
||||||
|
## 3.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- abd03a7: Fix: only send data to extension if DevTools are open
|
||||||
|
|
||||||
|
## 3.2.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 83b2c19: Upgrade to Manifest V3
|
||||||
|
|
||||||
|
## 3.1.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 73688e1: Fix releasing Firefox extension
|
||||||
|
|
||||||
|
## 3.1.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 2163bc3: Split large messages sent from background page to devpanel
|
||||||
|
|
||||||
|
## 3.1.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [bbb1a40]
|
||||||
|
- react-json-tree@0.19.0
|
||||||
|
- @redux-devtools/slider-monitor@5.0.1
|
||||||
|
- @redux-devtools/ui@1.3.2
|
||||||
|
|
||||||
|
## 3.1.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 191d419: Convert d3 packages to ESM
|
||||||
|
- Updated dependencies [191d419]
|
||||||
|
- @redux-devtools/app@6.0.1
|
||||||
|
|
||||||
|
## 3.1.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [5cfe3e5]
|
||||||
|
- Updated dependencies [decc035]
|
||||||
|
- @redux-devtools/app@6.0.0
|
||||||
|
- @redux-devtools/slider-monitor@5.0.0
|
||||||
|
- @redux-devtools/core@4.0.0
|
||||||
|
- @redux-devtools/utils@3.0.0
|
||||||
|
|
||||||
|
## 3.1.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [158ba2c]
|
||||||
|
- @redux-devtools/app@5.0.0
|
||||||
|
|
||||||
|
## 3.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 65205f90: Replace Action<unknown> with Action<string>
|
||||||
|
- Updated dependencies [65205f90]
|
||||||
|
- @redux-devtools/app@4.0.1
|
||||||
|
- @redux-devtools/core@3.13.2
|
||||||
|
|
||||||
|
## 3.1.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e57bcb39]
|
||||||
|
- @redux-devtools/app@4.0.0
|
||||||
|
|
||||||
|
## 3.1.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- bca76009: Fix missing CSS for code editor
|
||||||
|
|
||||||
|
## 3.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 64ed81b0: Fix extension in Firefox and Chrome Incognito
|
||||||
|
|
||||||
## 3.1.1
|
## 3.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
|
|
||||||
- from [Chrome Web Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd);
|
- from [Chrome Web Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd);
|
||||||
- or download `extension.zip` from [last releases](https://github.com/zalmoxisus/redux-devtools-extension/releases), unzip, open `chrome://extensions` url and turn on developer mode from top left and then click; on `Load Unpacked` and select the extracted folder for use
|
- or download `extension.zip` from [last releases](https://github.com/zalmoxisus/redux-devtools-extension/releases), unzip, open `chrome://extensions` url and turn on developer mode from top left and then click; on `Load Unpacked` and select the extracted folder for use
|
||||||
- or build it with `npm i && npm run build:extension` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./build/extension`;
|
- or build it with `npm i && npm run build:extension` and [load the extension's folder](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked) `./build/extension`;
|
||||||
- or run it in dev mode with `npm i && npm start` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./dev`.
|
- or run it in dev mode with `npm i && npm start` and [load the extension's folder](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked) `./dev`.
|
||||||
|
|
||||||
### 2. For Firefox
|
### 2. For Firefox
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ const composeEnhancers =
|
||||||
compose;
|
compose;
|
||||||
```
|
```
|
||||||
|
|
||||||
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtoolsextension-package-from-npm), which contains all the definitions, or just use `(window as any)` (see [Recipes](/docs/Recipes.md#using-in-a-typescript-project) for an example).
|
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtoolsextension-package-from-npm), which contains all the definitions, or just use `(window as any)` (see [Recipes](docs/Recipes.md#using-in-a-typescript-project) for an example).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
@ -228,7 +228,7 @@ See [integrations](docs/Integrations.md) and [the blog post](https://medium.com/
|
||||||
- [Methods (advanced API)](docs/API/Methods.md)
|
- [Methods (advanced API)](docs/API/Methods.md)
|
||||||
- [FAQ](docs/FAQ.md)
|
- [FAQ](docs/FAQ.md)
|
||||||
- Features
|
- Features
|
||||||
- [Trace actions calls](/docs/Features/Trace.md)
|
- [Trace actions calls](docs/Features/Trace.md)
|
||||||
- [Troubleshooting](docs/Troubleshooting.md)
|
- [Troubleshooting](docs/Troubleshooting.md)
|
||||||
- [Articles](docs/Articles.md)
|
- [Articles](docs/Articles.md)
|
||||||
- [Videos](docs/Videos.md)
|
- [Videos](docs/Videos.md)
|
||||||
|
|
|
||||||
55
extension/build.mjs
Normal file
55
extension/build.mjs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as esbuild from 'esbuild';
|
||||||
|
import pug from 'pug';
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const prod = !args.includes('--dev');
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
bundle: true,
|
||||||
|
logLevel: 'info',
|
||||||
|
outdir: 'dist',
|
||||||
|
minify: prod,
|
||||||
|
sourcemap: !prod,
|
||||||
|
define: {
|
||||||
|
'process.env.NODE_ENV': prod ? '"production"' : '"development"',
|
||||||
|
'process.env.BABEL_ENV': prod ? '"production"' : '"development"',
|
||||||
|
},
|
||||||
|
entryPoints: [
|
||||||
|
{ out: 'background.bundle', in: 'src/background/index.ts' },
|
||||||
|
{ out: 'options.bundle', in: 'src/options/index.tsx' },
|
||||||
|
{ out: 'remote.bundle', in: 'src/remote/index.tsx' },
|
||||||
|
{ out: 'devpanel.bundle', in: 'src/devpanel/index.tsx' },
|
||||||
|
{ out: 'devtools.bundle', in: 'src/devtools/index.ts' },
|
||||||
|
{ out: 'content.bundle', in: 'src/contentScript/index.ts' },
|
||||||
|
{ out: 'page.bundle', in: 'src/pageScript/index.ts' },
|
||||||
|
],
|
||||||
|
loader: {
|
||||||
|
'.woff2': 'file',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
console.log('Creating HTML files...');
|
||||||
|
const htmlFiles = ['devpanel', 'devtools', 'options', 'remote'];
|
||||||
|
for (const htmlFile of htmlFiles) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
`dist/${htmlFile}.html`,
|
||||||
|
pug.renderFile(`src/${htmlFile}/${htmlFile}.pug`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Copying manifest.json...');
|
||||||
|
fs.copyFileSync('chrome/manifest.json', 'dist/manifest.json');
|
||||||
|
|
||||||
|
console.log('Copying assets...');
|
||||||
|
fs.cpSync('src/assets', 'dist', { recursive: true });
|
||||||
|
|
||||||
|
console.log('Copying dist for each browser...');
|
||||||
|
fs.cpSync('dist', 'chrome/dist', { recursive: true });
|
||||||
|
fs.copyFileSync('chrome/manifest.json', 'chrome/dist/manifest.json');
|
||||||
|
fs.cpSync('dist', 'edge/dist', { recursive: true });
|
||||||
|
fs.copyFileSync('edge/manifest.json', 'edge/dist/manifest.json');
|
||||||
|
fs.cpSync('dist', 'firefox/dist', { recursive: true });
|
||||||
|
fs.copyFileSync('firefox/manifest.json', 'firefox/dist/manifest.json');
|
||||||
|
|
@ -1,28 +1,22 @@
|
||||||
{
|
{
|
||||||
"version": "3.1.1",
|
"version": "3.2.10",
|
||||||
"name": "Redux DevTools",
|
"name": "Redux DevTools",
|
||||||
"description": "Redux DevTools for debugging application's state changes.",
|
"description": "Redux DevTools for debugging application's state changes.",
|
||||||
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
||||||
"manifest_version": 2,
|
"manifest_version": 3,
|
||||||
"page_action": {
|
"action": {
|
||||||
"default_icon": "img/logo/gray.png",
|
"default_icon": "img/logo/gray.png",
|
||||||
"default_title": "Redux DevTools",
|
"default_title": "Redux DevTools",
|
||||||
"default_popup": "window.html#popup"
|
"default_popup": "devpanel.html#popup"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"devtools-left": {
|
"devtools-window": {
|
||||||
"description": "DevTools window to left"
|
"description": "DevTools window"
|
||||||
},
|
|
||||||
"devtools-right": {
|
|
||||||
"description": "DevTools window to right"
|
|
||||||
},
|
|
||||||
"devtools-bottom": {
|
|
||||||
"description": "DevTools window to bottom"
|
|
||||||
},
|
},
|
||||||
"devtools-remote": {
|
"devtools-remote": {
|
||||||
"description": "Remote DevTools"
|
"description": "Remote DevTools"
|
||||||
},
|
},
|
||||||
"_execute_page_action": {
|
"_execute_action": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+E"
|
"default": "Ctrl+Shift+E"
|
||||||
}
|
}
|
||||||
|
|
@ -34,36 +28,37 @@
|
||||||
"128": "img/logo/128x128.png"
|
"128": "img/logo/128x128.png"
|
||||||
},
|
},
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html",
|
"page": "options.html"
|
||||||
"chrome_style": true
|
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background.bundle.js"],
|
"service_worker": "background.bundle.js"
|
||||||
"persistent": false
|
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["<all_urls>"],
|
"matches": ["<all_urls>"],
|
||||||
"exclude_globs": ["https://www.google*"],
|
"exclude_globs": ["https://www.google*"],
|
||||||
"js": ["content.bundle.js", "pagewrap.bundle.js"],
|
"js": ["content.bundle.js"],
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": true
|
"all_frames": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": ["<all_urls>"],
|
||||||
|
"exclude_globs": ["https://www.google*"],
|
||||||
|
"js": ["page.bundle.js"],
|
||||||
|
"run_at": "document_start",
|
||||||
|
"all_frames": true,
|
||||||
|
"world": "MAIN"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"devtools_page": "devtools.html",
|
"devtools_page": "devtools.html",
|
||||||
"web_accessible_resources": ["page.bundle.js"],
|
|
||||||
"externally_connectable": {
|
"externally_connectable": {
|
||||||
"ids": ["*"]
|
"ids": ["*"]
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": ["notifications", "contextMenus", "storage"],
|
||||||
"notifications",
|
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||||
"contextMenus",
|
"content_security_policy": {
|
||||||
"storage",
|
"extension_pages": "script-src 'self'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
|
||||||
"file:///*",
|
},
|
||||||
"http://*/*",
|
|
||||||
"https://*/*"
|
|
||||||
],
|
|
||||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;",
|
|
||||||
"update_url": "https://clients2.google.com/service/update2/crx",
|
"update_url": "https://clients2.google.com/service/update2/crx",
|
||||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdJEPwY92xUACA9CcDBDBmbdbp8Ap3cKQ0DJTUuVQvqb4FQAv8RtKY3iUjGvdwuAcSJQIZwHXcP2aNDH3TiFik/NhRK2GRW8X3OZyTdkuDueABGP2KEX8q1WQDgjX/rPIinGYztUrvoICw/UerMPwNW62jwGoVU3YhAGf+15CgX2Y6a4tppnf/+1mPedKPidh0RsM+aJY98rX+r1SPAHPcGzMjocLkqcT75DZBXer8VQN14tOOzRCd6T6oy7qm7eWru8lJwcY66qMQvhk0osqEod2G3nA7aTWpmqPFS66VEiecP9PgZlp8gQdgZ3dFhA62exydlD55JuRhiMIR63yQIDAQAB"
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdJEPwY92xUACA9CcDBDBmbdbp8Ap3cKQ0DJTUuVQvqb4FQAv8RtKY3iUjGvdwuAcSJQIZwHXcP2aNDH3TiFik/NhRK2GRW8X3OZyTdkuDueABGP2KEX8q1WQDgjX/rPIinGYztUrvoICw/UerMPwNW62jwGoVU3YhAGf+15CgX2Y6a4tppnf/+1mPedKPidh0RsM+aJY98rX+r1SPAHPcGzMjocLkqcT75DZBXer8VQN14tOOzRCd6T6oy7qm7eWru8lJwcY66qMQvhk0osqEod2G3nA7aTWpmqPFS66VEiecP9PgZlp8gQdgZ3dFhA62exydlD55JuRhiMIR63yQIDAQAB"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ _number_ - maximum stack trace frames to be stored (in case `trace` option was p
|
||||||
_boolean_ or _object_ which contains:
|
_boolean_ or _object_ which contains:
|
||||||
|
|
||||||
- **options** `object or boolean`:
|
- **options** `object or boolean`:
|
||||||
|
|
||||||
- `undefined` - will use regular `JSON.stringify` to send data (it's the fast mode).
|
- `undefined` - will use regular `JSON.stringify` to send data (it's the fast mode).
|
||||||
- `false` - will handle also circular references.
|
- `false` - will handle also circular references.
|
||||||
- `true` - will handle also date, regex, undefined, primitives, error objects, symbols, maps, sets and functions.
|
- `true` - will handle also date, regex, undefined, primitives, error objects, symbols, maps, sets and functions.
|
||||||
|
|
|
||||||
37
extension/docs/Architecture.md
Normal file
37
extension/docs/Architecture.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Architecture Notes
|
||||||
|
|
||||||
|
This document exists to keep track of how the different parts of the Redux DevTools interact, since it's easy to forget how it all works together. This is intended for internal purposes and is just a collection of notes to myself.
|
||||||
|
|
||||||
|
## Entry Points
|
||||||
|
|
||||||
|
### Window
|
||||||
|
|
||||||
|
This is the default view that is shown in the Redux DevTools popup, the Chrome DevTools tab (if direct access to the background page is available), and new popup windows that are created. It has direct access to the background page via `chrome.runtime.getBackgroundPage`.
|
||||||
|
|
||||||
|
### DevPanel
|
||||||
|
|
||||||
|
This is the view that is shown in the Chrome DevTools tab if direct access to the background page is not available.
|
||||||
|
|
||||||
|
Initially this was the view that was always used for the Chrome DevTools tab, but when support to directly access the background page from the DevTools tab was added, [the Window View became the preferred view](https://github.com/zalmoxisus/redux-devtools-extension/pull/580).
|
||||||
|
|
||||||
|
### Remote
|
||||||
|
|
||||||
|
This does not interact with the other parts of the extension at all, it just renders the `App` component from `@redux-devtools/app`.
|
||||||
|
|
||||||
|
It can be triggered by hitting the "Remote" button in any of the other views, which calls `chrome.windows.create` and creates a new window.
|
||||||
|
|
||||||
|
### DevTools
|
||||||
|
|
||||||
|
This is the script that adds the Redux panel in the Chrome DevTools using `chrome.devtools.panels.create`.
|
||||||
|
|
||||||
|
It creates a Window View if it has direct access to the background page, otherwise it creates a DevPanel View.
|
||||||
|
|
||||||
|
Note that this used to always show the DevPanel View, but [started using the Window View by default](https://github.com/zalmoxisus/redux-devtools-extension/pull/580) once direct access to the background page was added to Chrome DevTools tabs.
|
||||||
|
|
||||||
|
### Content Script
|
||||||
|
|
||||||
|
Passes messages between the injected page script and the background page.
|
||||||
|
|
||||||
|
It listens for messages from the injected page script using `window.addEventListener('message', ...)`. It knows the message is from the injected page script if `message.source` is `'@devtools-page'`. See the Chrome DevTools docs where this approach [is documented](https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools#evaluated-scripts-to-devtools).
|
||||||
|
|
||||||
|
It creates a connection to the background page using `chrome.runtime.connect` with the name `'tab'` when it receives the first message from the injected page script.
|
||||||
|
|
@ -1,28 +1,22 @@
|
||||||
{
|
{
|
||||||
"version": "3.1.1",
|
"version": "3.2.10",
|
||||||
"name": "Redux DevTools",
|
"name": "Redux DevTools",
|
||||||
"description": "Redux DevTools for debugging application's state changes.",
|
"description": "Redux DevTools for debugging application's state changes.",
|
||||||
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
||||||
"manifest_version": 2,
|
"manifest_version": 3,
|
||||||
"page_action": {
|
"action": {
|
||||||
"default_icon": "img/logo/gray.png",
|
"default_icon": "img/logo/gray.png",
|
||||||
"default_title": "Redux DevTools",
|
"default_title": "Redux DevTools",
|
||||||
"default_popup": "window.html#popup"
|
"default_popup": "devpanel.html#popup"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"devtools-left": {
|
"devtools-window": {
|
||||||
"description": "DevTools window to left"
|
"description": "DevTools window"
|
||||||
},
|
|
||||||
"devtools-right": {
|
|
||||||
"description": "DevTools window to right"
|
|
||||||
},
|
|
||||||
"devtools-bottom": {
|
|
||||||
"description": "DevTools window to bottom"
|
|
||||||
},
|
},
|
||||||
"devtools-remote": {
|
"devtools-remote": {
|
||||||
"description": "Remote DevTools"
|
"description": "Remote DevTools"
|
||||||
},
|
},
|
||||||
"_execute_page_action": {
|
"_execute_action": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+E"
|
"default": "Ctrl+Shift+E"
|
||||||
}
|
}
|
||||||
|
|
@ -34,34 +28,35 @@
|
||||||
"128": "img/logo/128x128.png"
|
"128": "img/logo/128x128.png"
|
||||||
},
|
},
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html",
|
"page": "options.html"
|
||||||
"chrome_style": true
|
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background.bundle.js"],
|
"service_worker": "background.bundle.js"
|
||||||
"persistent": false
|
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["<all_urls>"],
|
"matches": ["<all_urls>"],
|
||||||
"exclude_globs": ["https://www.google*"],
|
"exclude_globs": ["https://www.google*"],
|
||||||
"js": ["content.bundle.js", "pagewrap.bundle.js"],
|
"js": ["content.bundle.js"],
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": true
|
"all_frames": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": ["<all_urls>"],
|
||||||
|
"exclude_globs": ["https://www.google*"],
|
||||||
|
"js": ["page.bundle.js"],
|
||||||
|
"run_at": "document_start",
|
||||||
|
"all_frames": true,
|
||||||
|
"world": "MAIN"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"devtools_page": "devtools.html",
|
"devtools_page": "devtools.html",
|
||||||
"web_accessible_resources": ["page.bundle.js"],
|
|
||||||
"externally_connectable": {
|
"externally_connectable": {
|
||||||
"ids": ["*"]
|
"ids": ["*"]
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": ["notifications", "contextMenus", "storage"],
|
||||||
"notifications",
|
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||||
"contextMenus",
|
"content_security_policy": {
|
||||||
"storage",
|
"extension_pages": "script-src 'self'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
|
||||||
"file:///*",
|
}
|
||||||
"http://*/*",
|
|
||||||
"https://*/*"
|
|
||||||
],
|
|
||||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
extension/eslint.config.mjs
Normal file
39
extension/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import globals from 'globals';
|
||||||
|
import eslintJs from '../eslint.js.config.base.mjs';
|
||||||
|
import eslintTsReact from '../eslint.ts.react.config.base.mjs';
|
||||||
|
import eslintJsReactJest from '../eslint.js.react.jest.config.base.mjs';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...eslintJs,
|
||||||
|
...eslintTsReact(import.meta.dirname),
|
||||||
|
...eslintJsReactJest,
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'chrome',
|
||||||
|
'dist',
|
||||||
|
'edge',
|
||||||
|
'examples',
|
||||||
|
'firefox',
|
||||||
|
'jest.config.ts',
|
||||||
|
'test/electron/fixture/dist',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['build.mjs'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.nodeBuiltin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['test/**/*.js', 'test/**/*.jsx'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
EUI: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/**
|
|
||||||
* Runs an ordered set of commands within each of the build directories.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { spawnSync } from 'child_process';
|
|
||||||
|
|
||||||
var exampleDirs = fs.readdirSync(__dirname).filter((file) => {
|
|
||||||
return fs.statSync(path.join(__dirname, file)).isDirectory();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ordering is important here. `npm install` must come first.
|
|
||||||
var cmdArgs = [
|
|
||||||
{ cmd: 'npm', args: ['install'] },
|
|
||||||
{ cmd: 'webpack', args: ['index.js'] },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const dir of exampleDirs) {
|
|
||||||
for (const cmdArg of cmdArgs) {
|
|
||||||
// declare opts in this scope to avoid https://github.com/joyent/node/issues/9158
|
|
||||||
const opts = {
|
|
||||||
cwd: path.join(__dirname, dir),
|
|
||||||
stdio: 'inherit',
|
|
||||||
};
|
|
||||||
let result = {};
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
result = spawnSync(cmdArg.cmd + '.cmd', cmdArg.args, opts);
|
|
||||||
} else {
|
|
||||||
result = spawnSync(cmdArg.cmd, cmdArg.args, opts);
|
|
||||||
}
|
|
||||||
if (result.status !== 0) {
|
|
||||||
throw new Error('Building examples exited with non-zero');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["es2015", "stage-0", "react"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
|
|
||||||
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
|
|
||||||
|
|
||||||
let t;
|
|
||||||
|
|
||||||
export function increment() {
|
|
||||||
return {
|
|
||||||
type: INCREMENT_COUNTER,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decrement() {
|
|
||||||
return {
|
|
||||||
type: DECREMENT_COUNTER,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function autoIncrement(delay = 10) {
|
|
||||||
return (dispatch) => {
|
|
||||||
if (t) {
|
|
||||||
clearInterval(t);
|
|
||||||
t = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
t = setInterval(() => {
|
|
||||||
dispatch(increment());
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function incrementAsync(delay = 1000) {
|
|
||||||
return (dispatch) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
dispatch(increment());
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
class Counter extends Component {
|
|
||||||
render() {
|
|
||||||
const { increment, autoIncrement, incrementAsync, decrement, counter } =
|
|
||||||
this.props;
|
|
||||||
return (
|
|
||||||
<p>
|
|
||||||
Clicked: {counter} times <button onClick={increment}>+</button>{' '}
|
|
||||||
<button onClick={decrement}>-</button>{' '}
|
|
||||||
<button onClick={incrementAsync}>Increment async</button>{' '}
|
|
||||||
<button onClick={autoIncrement}>Auto increment</button>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Counter.propTypes = {
|
|
||||||
increment: PropTypes.func.isRequired,
|
|
||||||
autoIncrement: PropTypes.func.isRequired,
|
|
||||||
incrementAsync: PropTypes.func.isRequired,
|
|
||||||
decrement: PropTypes.func.isRequired,
|
|
||||||
counter: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Counter;
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Counter from '../components/Counter';
|
|
||||||
import * as CounterActions from '../actions/counter';
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
counter: state.counter,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(CounterActions, dispatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Redux counter example</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import App from './containers/App';
|
|
||||||
import configureStore from './store/configureStore';
|
|
||||||
|
|
||||||
const store = configureStore();
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<App />
|
|
||||||
</Provider>,
|
|
||||||
document.getElementById('root'),
|
|
||||||
);
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "redux-counter-example",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "Redux counter example",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node server.js",
|
|
||||||
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-core/register --require ./test/setup.js",
|
|
||||||
"test:watch": "npm test -- --watch"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/rackt/redux.git"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/rackt/redux/issues"
|
|
||||||
},
|
|
||||||
"homepage": "http://rackt.github.io/redux",
|
|
||||||
"dependencies": {
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"react": "^16.7.0",
|
|
||||||
"react-dom": "^16.7.0",
|
|
||||||
"react-redux": "^6.0.0",
|
|
||||||
"redux": "^4.0.1",
|
|
||||||
"redux-devtools-extension": "^2.13.7",
|
|
||||||
"redux-thunk": "^2.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-cli": "^6.3.17",
|
|
||||||
"babel-core": "^6.3.17",
|
|
||||||
"babel-loader": "^7.0.0",
|
|
||||||
"babel-preset-es2015": "^6.0.0",
|
|
||||||
"babel-preset-react": "6.3.13",
|
|
||||||
"babel-preset-stage-0": "^6.3.13",
|
|
||||||
"express": "^4.13.3",
|
|
||||||
"redux-immutable-state-invariant": "^2.1.0",
|
|
||||||
"webpack": "^4.0.0",
|
|
||||||
"webpack-dev-server": "^3.0.0",
|
|
||||||
"webpack-hot-middleware": "^2.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter';
|
|
||||||
|
|
||||||
export default function counter(state = 0, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case INCREMENT_COUNTER:
|
|
||||||
return state + 1;
|
|
||||||
case DECREMENT_COUNTER:
|
|
||||||
return state - 1;
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { combineReducers } from 'redux';
|
|
||||||
import counter from './counter';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
|
||||||
counter,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default rootReducer;
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
var webpack = require('webpack');
|
|
||||||
var webpackDevMiddleware = require('webpack-dev-middleware');
|
|
||||||
var webpackHotMiddleware = require('webpack-hot-middleware');
|
|
||||||
var config = require('./webpack.config');
|
|
||||||
|
|
||||||
var app = new require('express')();
|
|
||||||
var port = 4001;
|
|
||||||
|
|
||||||
var compiler = webpack(config);
|
|
||||||
app.use(
|
|
||||||
webpackDevMiddleware(compiler, {
|
|
||||||
noInfo: true,
|
|
||||||
publicPath: config.output.publicPath,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
app.use(webpackHotMiddleware(compiler));
|
|
||||||
|
|
||||||
app.get('/', function (req, res) {
|
|
||||||
res.sendFile(__dirname + '/index.html');
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(port, function (error) {
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
} else {
|
|
||||||
console.info(
|
|
||||||
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
|
|
||||||
port,
|
|
||||||
port,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
|
||||||
import thunk from 'redux-thunk';
|
|
||||||
import invariant from 'redux-immutable-state-invariant';
|
|
||||||
import reducer from '../reducers';
|
|
||||||
import * as actionCreators from '../actions/counter';
|
|
||||||
|
|
||||||
export default function configureStore(preloadedState) {
|
|
||||||
const composeEnhancers = composeWithDevTools({
|
|
||||||
actionCreators,
|
|
||||||
trace: true,
|
|
||||||
traceLimit: 25,
|
|
||||||
});
|
|
||||||
const store = createStore(
|
|
||||||
reducer,
|
|
||||||
preloadedState,
|
|
||||||
composeEnhancers(applyMiddleware(invariant(), thunk)),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (module.hot) {
|
|
||||||
// Enable Webpack hot module replacement for reducers
|
|
||||||
module.hot.accept('../reducers', () => {
|
|
||||||
store.replaceReducer(require('../reducers').default);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import { applyMiddleware } from 'redux';
|
|
||||||
import thunk from 'redux-thunk';
|
|
||||||
import * as actions from '../../actions/counter';
|
|
||||||
|
|
||||||
const middlewares = [thunk];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Creates a mock of Redux store with middleware.
|
|
||||||
*/
|
|
||||||
function mockStore(getState, expectedActions, onLastAction) {
|
|
||||||
if (!Array.isArray(expectedActions)) {
|
|
||||||
throw new Error('expectedActions should be an array of expected actions.');
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof onLastAction !== 'undefined' &&
|
|
||||||
typeof onLastAction !== 'function'
|
|
||||||
) {
|
|
||||||
throw new Error('onLastAction should either be undefined or function.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockStoreWithoutMiddleware() {
|
|
||||||
return {
|
|
||||||
getState() {
|
|
||||||
return typeof getState === 'function' ? getState() : getState;
|
|
||||||
},
|
|
||||||
|
|
||||||
dispatch(action) {
|
|
||||||
const expectedAction = expectedActions.shift();
|
|
||||||
expect(action).toEqual(expectedAction);
|
|
||||||
if (onLastAction && !expectedActions.length) {
|
|
||||||
onLastAction();
|
|
||||||
}
|
|
||||||
return action;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockStoreWithMiddleware = applyMiddleware(...middlewares)(
|
|
||||||
mockStoreWithoutMiddleware,
|
|
||||||
);
|
|
||||||
|
|
||||||
return mockStoreWithMiddleware();
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('actions', () => {
|
|
||||||
it('increment should create increment action', () => {
|
|
||||||
expect(actions.increment()).toEqual({ type: actions.INCREMENT_COUNTER });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('decrement should create decrement action', () => {
|
|
||||||
expect(actions.decrement()).toEqual({ type: actions.DECREMENT_COUNTER });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('incrementIfOdd should create increment action', (done) => {
|
|
||||||
const expectedActions = [{ type: actions.INCREMENT_COUNTER }];
|
|
||||||
const store = mockStore({ counter: 1 }, expectedActions, done);
|
|
||||||
store.dispatch(actions.incrementIfOdd());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('incrementIfOdd shouldnt create increment action if counter is even', (done) => {
|
|
||||||
const expectedActions = [];
|
|
||||||
const store = mockStore({ counter: 2 }, expectedActions);
|
|
||||||
store.dispatch(actions.incrementIfOdd());
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('incrementAsync should create increment action', (done) => {
|
|
||||||
const expectedActions = [{ type: actions.INCREMENT_COUNTER }];
|
|
||||||
const store = mockStore({ counter: 0 }, expectedActions, done);
|
|
||||||
store.dispatch(actions.incrementAsync(100));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils from 'react-addons-test-utils';
|
|
||||||
import Counter from '../../components/Counter';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
const actions = {
|
|
||||||
increment: expect.createSpy(),
|
|
||||||
incrementIfOdd: expect.createSpy(),
|
|
||||||
incrementAsync: expect.createSpy(),
|
|
||||||
decrement: expect.createSpy(),
|
|
||||||
};
|
|
||||||
const component = TestUtils.renderIntoDocument(
|
|
||||||
<Counter counter={1} {...actions} />,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
component: component,
|
|
||||||
actions: actions,
|
|
||||||
buttons: TestUtils.scryRenderedDOMComponentsWithTag(component, 'button'),
|
|
||||||
p: TestUtils.findRenderedDOMComponentWithTag(component, 'p'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Counter component', () => {
|
|
||||||
it('should display count', () => {
|
|
||||||
const { p } = setup();
|
|
||||||
expect(p.textContent).toMatch(/^Clicked: 1 times/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('first button should call increment', () => {
|
|
||||||
const { buttons, actions } = setup();
|
|
||||||
TestUtils.Simulate.click(buttons[0]);
|
|
||||||
expect(actions.increment).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('second button should call decrement', () => {
|
|
||||||
const { buttons, actions } = setup();
|
|
||||||
TestUtils.Simulate.click(buttons[1]);
|
|
||||||
expect(actions.decrement).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('third button should call incrementIfOdd', () => {
|
|
||||||
const { buttons, actions } = setup();
|
|
||||||
TestUtils.Simulate.click(buttons[2]);
|
|
||||||
expect(actions.incrementIfOdd).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fourth button should call incrementAsync', () => {
|
|
||||||
const { buttons, actions } = setup();
|
|
||||||
TestUtils.Simulate.click(buttons[3]);
|
|
||||||
expect(actions.incrementAsync).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils from 'react-addons-test-utils';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import App from '../../containers/App';
|
|
||||||
import configureStore from '../../store/configureStore';
|
|
||||||
|
|
||||||
function setup(initialState) {
|
|
||||||
const store = configureStore(initialState);
|
|
||||||
const app = TestUtils.renderIntoDocument(
|
|
||||||
<Provider store={store}>
|
|
||||||
<App />
|
|
||||||
</Provider>,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
app: app,
|
|
||||||
buttons: TestUtils.scryRenderedDOMComponentsWithTag(app, 'button'),
|
|
||||||
p: TestUtils.findRenderedDOMComponentWithTag(app, 'p'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('containers', () => {
|
|
||||||
describe('App', () => {
|
|
||||||
it('should display initial count', () => {
|
|
||||||
const { p } = setup();
|
|
||||||
expect(p.textContent).toMatch(/^Clicked: 0 times/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display updated count after increment button click', () => {
|
|
||||||
const { buttons, p } = setup();
|
|
||||||
TestUtils.Simulate.click(buttons[0]);
|
|
||||||
expect(p.textContent).toMatch(/^Clicked: 1 times/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display updated count after decrement button click', () => {
|
|
||||||
const { buttons, p } = setup();
|
|
||||||
TestUtils.Simulate.click(buttons[1]);
|
|
||||||
expect(p.textContent).toMatch(/^Clicked: -1 times/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shouldnt change if even and if odd button clicked', () => {
|
|
||||||
const { buttons, p } = setup();
|
|
||||||
TestUtils.Simulate.click(buttons[2]);
|
|
||||||
expect(p.textContent).toMatch(/^Clicked: 0 times/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should change if odd and if odd button clicked', () => {
|
|
||||||
const { buttons, p } = setup({ counter: 1 });
|
|
||||||
TestUtils.Simulate.click(buttons[2]);
|
|
||||||
expect(p.textContent).toMatch(/^Clicked: 2 times/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import counter from '../../reducers/counter';
|
|
||||||
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../../actions/counter';
|
|
||||||
|
|
||||||
describe('reducers', () => {
|
|
||||||
describe('counter', () => {
|
|
||||||
it('should handle initial state', () => {
|
|
||||||
expect(counter(undefined, {})).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle INCREMENT_COUNTER', () => {
|
|
||||||
expect(counter(1, { type: INCREMENT_COUNTER })).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle DECREMENT_COUNTER', () => {
|
|
||||||
expect(counter(1, { type: DECREMENT_COUNTER })).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle unknown action type', () => {
|
|
||||||
expect(counter(1, { type: 'unknown' })).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { jsdom } from 'jsdom';
|
|
||||||
|
|
||||||
global.document = jsdom('<!doctype html><html><body></body></html>');
|
|
||||||
global.window = document.defaultView;
|
|
||||||
global.navigator = global.window.navigator;
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
var path = require('path');
|
|
||||||
var webpack = require('webpack');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mode: 'development',
|
|
||||||
devtool: 'source-map',
|
|
||||||
entry: ['webpack-hot-middleware/client', './index'],
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'dist'),
|
|
||||||
filename: 'bundle.js',
|
|
||||||
publicPath: '/static/',
|
|
||||||
},
|
|
||||||
plugins: [new webpack.HotModuleReplacementPlugin()],
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loaders: ['babel-loader'],
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["es2015", "stage-0", "react"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
|
|
||||||
const withDevTools =
|
|
||||||
// process.env.NODE_ENV === 'development' &&
|
|
||||||
typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__;
|
|
||||||
|
|
||||||
class Counter extends Component {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.state = { counter: 0 };
|
|
||||||
|
|
||||||
this.increment = this.increment.bind(this);
|
|
||||||
this.decrement = this.decrement.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
if (withDevTools) {
|
|
||||||
this.devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect();
|
|
||||||
this.unsubscribe = this.devTools.subscribe((message) => {
|
|
||||||
// Implement monitors actions.
|
|
||||||
// For example time traveling:
|
|
||||||
if (
|
|
||||||
message.type === 'DISPATCH' &&
|
|
||||||
message.payload.type === 'JUMP_TO_STATE'
|
|
||||||
) {
|
|
||||||
this.setState(message.state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (withDevTools) {
|
|
||||||
this.unsubscribe(); // Use if you have other subscribers from other components.
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.disconnect(); // If there aren't other subscribers.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
increment() {
|
|
||||||
const state = { counter: this.state.counter + 1 };
|
|
||||||
if (withDevTools) this.devTools.send('increment', state);
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
decrement() {
|
|
||||||
const state = { counter: this.state.counter - 1 };
|
|
||||||
if (withDevTools) this.devTools.send('decrement', state);
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { counter } = this.state;
|
|
||||||
return (
|
|
||||||
<p>
|
|
||||||
Clicked: {counter} times <button onClick={this.increment}>+</button>{' '}
|
|
||||||
<button onClick={this.decrement}>-</button>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Counter;
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>React counter example</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import Counter from './components/Counter';
|
|
||||||
|
|
||||||
render(<Counter />, document.getElementById('root'));
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
{
|
|
||||||
"name": "react-counter-example",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "React counter example",
|
|
||||||
"scripts": {
|
|
||||||
"start": "webpack-dev-server --progress"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/zalmoxisus/redux-devtools-extension.git"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/zalmoxisus/redux-devtools-extension/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/zalmoxisus/redux-devtools-extension",
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^16.0.0",
|
|
||||||
"react-dom": "^16.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-cli": "^6.3.17",
|
|
||||||
"babel-core": "^6.3.17",
|
|
||||||
"babel-loader": "^7.0.0",
|
|
||||||
"babel-preset-es2015": "^6.0.0",
|
|
||||||
"babel-preset-react": "6.3.13",
|
|
||||||
"babel-preset-stage-0": "^6.3.13",
|
|
||||||
"webpack": "^4.0.0",
|
|
||||||
"webpack-cli": "^3.2.0",
|
|
||||||
"webpack-dev-server": "^3.0.0",
|
|
||||||
"webpack-hot-middleware": "^2.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
var path = require('path');
|
|
||||||
var webpack = require('webpack');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mode: 'development',
|
|
||||||
devtool: 'source-map',
|
|
||||||
entry: ['./index'],
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'dist'),
|
|
||||||
filename: 'bundle.js',
|
|
||||||
publicPath: '/static/',
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loaders: ['babel-loader'],
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
devServer: {
|
|
||||||
port: 4004,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["es2015", "stage-0", "react"],
|
|
||||||
"plugins": ["add-module-exports", "transform-decorators-legacy"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import * as types from '../constants/ActionTypes';
|
|
||||||
|
|
||||||
export function addTodo(text) {
|
|
||||||
return { type: types.ADD_TODO, text };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteTodo(id) {
|
|
||||||
return { type: types.DELETE_TODO, id };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editTodo(id, text) {
|
|
||||||
return { type: types.EDIT_TODO, id, text };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function completeTodo(id) {
|
|
||||||
return { type: types.COMPLETE_TODO, id };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function completeAll() {
|
|
||||||
return { type: types.COMPLETE_ALL };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearCompleted() {
|
|
||||||
return { type: types.CLEAR_COMPLETED };
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import {
|
|
||||||
SHOW_ALL,
|
|
||||||
SHOW_COMPLETED,
|
|
||||||
SHOW_ACTIVE,
|
|
||||||
} from '../constants/TodoFilters';
|
|
||||||
|
|
||||||
const FILTER_TITLES = {
|
|
||||||
[SHOW_ALL]: 'All',
|
|
||||||
[SHOW_ACTIVE]: 'Active',
|
|
||||||
[SHOW_COMPLETED]: 'Completed',
|
|
||||||
};
|
|
||||||
|
|
||||||
class Footer extends Component {
|
|
||||||
renderTodoCount() {
|
|
||||||
const { activeCount } = this.props;
|
|
||||||
const itemWord = activeCount === 1 ? 'item' : 'items';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className="todo-count">
|
|
||||||
<strong>{activeCount || 'No'}</strong> {itemWord} left
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFilterLink(filter) {
|
|
||||||
const title = FILTER_TITLES[filter];
|
|
||||||
const { filter: selectedFilter, onShow } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
className={classnames({ selected: filter === selectedFilter })}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
onClick={() => onShow(filter)}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderClearButton() {
|
|
||||||
const { completedCount, onClearCompleted } = this.props;
|
|
||||||
if (completedCount > 0) {
|
|
||||||
return (
|
|
||||||
<button className="clear-completed" onClick={onClearCompleted}>
|
|
||||||
Clear completed
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<footer className="footer">
|
|
||||||
{this.renderTodoCount()}
|
|
||||||
<ul className="filters">
|
|
||||||
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map((filter) => (
|
|
||||||
<li key={filter}>{this.renderFilterLink(filter)}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{this.renderClearButton()}
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Footer.propTypes = {
|
|
||||||
completedCount: PropTypes.number.isRequired,
|
|
||||||
activeCount: PropTypes.number.isRequired,
|
|
||||||
filter: PropTypes.string.isRequired,
|
|
||||||
onClearCompleted: PropTypes.func.isRequired,
|
|
||||||
onShow: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Footer;
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import React, { PropTypes, Component } from 'react';
|
|
||||||
import TodoTextInput from './TodoTextInput';
|
|
||||||
|
|
||||||
class Header extends Component {
|
|
||||||
handleSave(text) {
|
|
||||||
if (text.length !== 0) {
|
|
||||||
this.props.addTodo(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { path } = this.props;
|
|
||||||
return (
|
|
||||||
<header className="header">
|
|
||||||
<h1 style={{ fontSize: 80 }}>{path}</h1>
|
|
||||||
<TodoTextInput
|
|
||||||
newTodo
|
|
||||||
onSave={this.handleSave.bind(this)}
|
|
||||||
placeholder="What needs to be done?"
|
|
||||||
/>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Header.propTypes = {
|
|
||||||
addTodo: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import TodoItem from './TodoItem';
|
|
||||||
import Footer from './Footer';
|
|
||||||
import {
|
|
||||||
SHOW_ALL,
|
|
||||||
SHOW_COMPLETED,
|
|
||||||
SHOW_ACTIVE,
|
|
||||||
} from '../constants/TodoFilters';
|
|
||||||
|
|
||||||
const TODO_FILTERS = {
|
|
||||||
[SHOW_ALL]: () => true,
|
|
||||||
[SHOW_ACTIVE]: (todo) => !todo.completed,
|
|
||||||
[SHOW_COMPLETED]: (todo) => todo.completed,
|
|
||||||
};
|
|
||||||
|
|
||||||
class MainSection extends Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = { filter: SHOW_ALL };
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClearCompleted() {
|
|
||||||
const atLeastOneCompleted = this.props.todos.some((todo) => todo.completed);
|
|
||||||
if (atLeastOneCompleted) {
|
|
||||||
this.props.actions.clearCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleShow(filter) {
|
|
||||||
this.setState({ filter });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderToggleAll(completedCount) {
|
|
||||||
const { todos, actions } = this.props;
|
|
||||||
if (todos.length > 0) {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
className="toggle-all"
|
|
||||||
type="checkbox"
|
|
||||||
checked={completedCount === todos.length}
|
|
||||||
onChange={actions.completeAll}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFooter(completedCount) {
|
|
||||||
const { todos } = this.props;
|
|
||||||
const { filter } = this.state;
|
|
||||||
const activeCount = todos.length - completedCount;
|
|
||||||
|
|
||||||
if (todos.length) {
|
|
||||||
return (
|
|
||||||
<Footer
|
|
||||||
completedCount={completedCount}
|
|
||||||
activeCount={activeCount}
|
|
||||||
filter={filter}
|
|
||||||
onClearCompleted={this.handleClearCompleted.bind(this)}
|
|
||||||
onShow={this.handleShow.bind(this)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { todos, actions } = this.props;
|
|
||||||
const { filter } = this.state;
|
|
||||||
|
|
||||||
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
|
||||||
const completedCount = todos.reduce(
|
|
||||||
(count, todo) => (todo.completed ? count + 1 : count),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="main">
|
|
||||||
{this.renderToggleAll(completedCount)}
|
|
||||||
<ul className="todo-list">
|
|
||||||
{filteredTodos.map((todo) => (
|
|
||||||
<TodoItem key={todo.id} todo={todo} {...actions} />
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{this.renderFooter(completedCount)}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MainSection.propTypes = {
|
|
||||||
todos: PropTypes.array.isRequired,
|
|
||||||
actions: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MainSection;
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import TodoTextInput from './TodoTextInput';
|
|
||||||
|
|
||||||
class TodoItem extends Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = {
|
|
||||||
editing: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDoubleClick() {
|
|
||||||
this.setState({ editing: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSave(id, text) {
|
|
||||||
if (text.length === 0) {
|
|
||||||
this.props.deleteTodo(id);
|
|
||||||
} else {
|
|
||||||
this.props.editTodo(id, text);
|
|
||||||
}
|
|
||||||
this.setState({ editing: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { todo, completeTodo, deleteTodo } = this.props;
|
|
||||||
|
|
||||||
let element;
|
|
||||||
if (this.state.editing) {
|
|
||||||
element = (
|
|
||||||
<TodoTextInput
|
|
||||||
text={todo.text}
|
|
||||||
editing={this.state.editing}
|
|
||||||
onSave={(text) => this.handleSave(todo.id, text)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
element = (
|
|
||||||
<div className="view">
|
|
||||||
<input
|
|
||||||
className="toggle"
|
|
||||||
type="checkbox"
|
|
||||||
checked={todo.completed}
|
|
||||||
onChange={() => completeTodo(todo.id)}
|
|
||||||
/>
|
|
||||||
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
|
||||||
{todo.text}
|
|
||||||
</label>
|
|
||||||
<button className="destroy" onClick={() => deleteTodo(todo.id)} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={classnames({
|
|
||||||
completed: todo.completed,
|
|
||||||
editing: this.state.editing,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{element}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TodoItem.propTypes = {
|
|
||||||
todo: PropTypes.object.isRequired,
|
|
||||||
editTodo: PropTypes.func.isRequired,
|
|
||||||
deleteTodo: PropTypes.func.isRequired,
|
|
||||||
completeTodo: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TodoItem;
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
class TodoTextInput extends Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = {
|
|
||||||
text: this.props.text || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(e) {
|
|
||||||
const text = e.target.value.trim();
|
|
||||||
if (e.which === 13) {
|
|
||||||
this.props.onSave(text);
|
|
||||||
if (this.props.newTodo) {
|
|
||||||
this.setState({ text: '' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange(e) {
|
|
||||||
this.setState({ text: e.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBlur(e) {
|
|
||||||
if (!this.props.newTodo) {
|
|
||||||
this.props.onSave(e.target.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
className={classnames({
|
|
||||||
edit: this.props.editing,
|
|
||||||
'new-todo': this.props.newTodo,
|
|
||||||
})}
|
|
||||||
type="text"
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
autoFocus="true"
|
|
||||||
value={this.state.text}
|
|
||||||
onBlur={this.handleBlur.bind(this)}
|
|
||||||
onChange={this.handleChange.bind(this)}
|
|
||||||
onKeyDown={this.handleSubmit.bind(this)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TodoTextInput.propTypes = {
|
|
||||||
onSave: PropTypes.func.isRequired,
|
|
||||||
text: PropTypes.string,
|
|
||||||
placeholder: PropTypes.string,
|
|
||||||
editing: PropTypes.bool,
|
|
||||||
newTodo: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TodoTextInput;
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export const ADD_TODO = 'ADD_TODO';
|
|
||||||
export const DELETE_TODO = 'DELETE_TODO';
|
|
||||||
export const EDIT_TODO = 'EDIT_TODO';
|
|
||||||
export const COMPLETE_TODO = 'COMPLETE_TODO';
|
|
||||||
export const COMPLETE_ALL = 'COMPLETE_ALL';
|
|
||||||
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED';
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export const SHOW_ALL = 'show_all';
|
|
||||||
export const SHOW_COMPLETED = 'show_completed';
|
|
||||||
export const SHOW_ACTIVE = 'show_active';
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Header from '../components/Header';
|
|
||||||
import MainSection from '../components/MainSection';
|
|
||||||
import * as TodoActions from '../actions/todos';
|
|
||||||
|
|
||||||
class App extends Component {
|
|
||||||
render() {
|
|
||||||
const { todos, path, actions } = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Header addTodo={actions.addTodo} path={path} />
|
|
||||||
<MainSection todos={todos} actions={actions} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App.propTypes = {
|
|
||||||
todos: PropTypes.array.isRequired,
|
|
||||||
actions: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
todos: state.todos,
|
|
||||||
path: state.router.location.pathname,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
actions: bindActionCreators(TodoActions, dispatch),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { Route, Redirect } from 'react-router';
|
|
||||||
import { ReduxRouter } from 'redux-router';
|
|
||||||
import Wrapper from './Wrapper';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
class Root extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<ReduxRouter>
|
|
||||||
<Redirect from="/" to="Standard Todo" />
|
|
||||||
<Route path="/" component={Wrapper}>
|
|
||||||
<Route path="/:id" component={App} />
|
|
||||||
</Route>
|
|
||||||
</ReduxRouter>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Root;
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { pushState } from 'redux-router';
|
|
||||||
import { Route, Link } from 'react-router';
|
|
||||||
import * as TodoActions from '../actions/todos';
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
pushState: bindActionCreators(pushState, dispatch),
|
|
||||||
actions: bindActionCreators(TodoActions, dispatch),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@connect((state) => ({}), mapDispatchToProps)
|
|
||||||
class Wrapper extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.handleClick = this.handleClick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const { actions, pushState } = this.props;
|
|
||||||
const path = event.target.innerText;
|
|
||||||
|
|
||||||
pushState(null, path);
|
|
||||||
console.log('Navigate to', path);
|
|
||||||
|
|
||||||
if (this.timeout) clearInterval(this.timeout);
|
|
||||||
if (path === 'AutoTodo') {
|
|
||||||
console.log('!');
|
|
||||||
this.timeout = setInterval(() => {
|
|
||||||
actions.addTodo('Auto generated task');
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: 20,
|
|
||||||
backgroundColor: '#eee',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<a href="#" onClick={this.handleClick}>
|
|
||||||
Standard Todo
|
|
||||||
</a>{' '}
|
|
||||||
|{' '}
|
|
||||||
<a href="#" onClick={this.handleClick}>
|
|
||||||
AutoTodo
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{this.props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Wrapper;
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Redux TodoMVC example</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="todoapp" id="root"></div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import 'babel-polyfill';
|
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import Root from './containers/Root';
|
|
||||||
import configureStore from './store/configureStore';
|
|
||||||
import 'todomvc-app-css/index.css';
|
|
||||||
|
|
||||||
const store = configureStore();
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<Root />
|
|
||||||
</Provider>,
|
|
||||||
document.getElementById('root'),
|
|
||||||
);
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
{
|
|
||||||
"name": "redux-todomvc-example",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "Redux TodoMVC example",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node server.js",
|
|
||||||
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-core/register --require ./test/setup.js",
|
|
||||||
"test:watch": "npm test -- --watch"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/rackt/redux.git"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/rackt/redux/issues"
|
|
||||||
},
|
|
||||||
"homepage": "http://rackt.github.io/redux",
|
|
||||||
"dependencies": {
|
|
||||||
"classnames": "^2.1.2",
|
|
||||||
"history": "^1.13.1",
|
|
||||||
"react": "^0.14.0",
|
|
||||||
"react-dom": "^0.14.0",
|
|
||||||
"react-redux": "^4.0.0",
|
|
||||||
"react-router": "^1.0.2",
|
|
||||||
"redux": "^3.0.0",
|
|
||||||
"redux-router": "^1.0.0-beta5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-core": "^6.3.15",
|
|
||||||
"babel-loader": "^6.2.0",
|
|
||||||
"babel-plugin-add-module-exports": "^0.1.1",
|
|
||||||
"babel-plugin-react-transform": "^2.0.0-beta1",
|
|
||||||
"babel-plugin-transform-decorators-legacy": "^1.2.0",
|
|
||||||
"babel-polyfill": "^6.3.14",
|
|
||||||
"babel-preset-es2015": "^6.3.13",
|
|
||||||
"babel-preset-react": "^6.3.13",
|
|
||||||
"babel-preset-stage-0": "^6.3.13",
|
|
||||||
"expect": "^1.8.0",
|
|
||||||
"express": "^4.13.3",
|
|
||||||
"jsdom": "^5.6.1",
|
|
||||||
"mocha": "^2.2.5",
|
|
||||||
"node-libs-browser": "^0.5.2",
|
|
||||||
"raw-loader": "^0.5.1",
|
|
||||||
"react-addons-test-utils": "^0.14.0",
|
|
||||||
"react-transform-hmr": "^1.0.0",
|
|
||||||
"style-loader": "^0.12.3",
|
|
||||||
"todomvc-app-css": "^2.0.1",
|
|
||||||
"webpack": "^1.9.11",
|
|
||||||
"webpack-dev-middleware": "^1.2.0",
|
|
||||||
"webpack-hot-middleware": "^2.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { combineReducers } from 'redux';
|
|
||||||
import { routerStateReducer } from 'redux-router';
|
|
||||||
import todos from './todos';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
|
||||||
todos,
|
|
||||||
router: routerStateReducer,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default rootReducer;
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import {
|
|
||||||
ADD_TODO,
|
|
||||||
DELETE_TODO,
|
|
||||||
EDIT_TODO,
|
|
||||||
COMPLETE_TODO,
|
|
||||||
COMPLETE_ALL,
|
|
||||||
CLEAR_COMPLETED,
|
|
||||||
} from '../constants/ActionTypes';
|
|
||||||
|
|
||||||
const initialState = [
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function todos(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case ADD_TODO:
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
|
|
||||||
completed: false,
|
|
||||||
text: action.text,
|
|
||||||
},
|
|
||||||
...state,
|
|
||||||
];
|
|
||||||
|
|
||||||
case DELETE_TODO:
|
|
||||||
return state.filter((todo) => todo.id !== action.id);
|
|
||||||
|
|
||||||
case EDIT_TODO:
|
|
||||||
return state.map((todo) =>
|
|
||||||
todo.id === action.id
|
|
||||||
? Object.assign({}, todo, { text: action.text })
|
|
||||||
: todo,
|
|
||||||
);
|
|
||||||
|
|
||||||
case COMPLETE_TODO:
|
|
||||||
return state.map((todo) =>
|
|
||||||
todo.id === action.id
|
|
||||||
? Object.assign({}, todo, { completed: !todo.completed })
|
|
||||||
: todo,
|
|
||||||
);
|
|
||||||
|
|
||||||
case COMPLETE_ALL:
|
|
||||||
const areAllMarked = state.every((todo) => todo.completed);
|
|
||||||
return state.map((todo) =>
|
|
||||||
Object.assign({}, todo, {
|
|
||||||
completed: !areAllMarked,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
case CLEAR_COMPLETED:
|
|
||||||
return state.filter((todo) => todo.completed === false);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
var webpack = require('webpack');
|
|
||||||
var webpackDevMiddleware = require('webpack-dev-middleware');
|
|
||||||
var webpackHotMiddleware = require('webpack-hot-middleware');
|
|
||||||
var config = require('./webpack.config');
|
|
||||||
|
|
||||||
var app = new require('express')();
|
|
||||||
var port = 4002;
|
|
||||||
|
|
||||||
var compiler = webpack(config);
|
|
||||||
app.use(
|
|
||||||
webpackDevMiddleware(compiler, {
|
|
||||||
noInfo: true,
|
|
||||||
publicPath: config.output.publicPath,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
app.use(webpackHotMiddleware(compiler));
|
|
||||||
|
|
||||||
app.get('/', function (req, res) {
|
|
||||||
res.sendFile(__dirname + '/index.html');
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(port, function (error) {
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
} else {
|
|
||||||
console.info(
|
|
||||||
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
|
|
||||||
port,
|
|
||||||
port,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { createStore, compose } from 'redux';
|
|
||||||
import {
|
|
||||||
reduxReactRouter,
|
|
||||||
routerStateReducer,
|
|
||||||
ReduxRouter,
|
|
||||||
} from 'redux-router';
|
|
||||||
//import createHistory from 'history/lib/createBrowserHistory';
|
|
||||||
import createHistory from 'history/lib/createHashHistory';
|
|
||||||
import rootReducer from '../reducers';
|
|
||||||
|
|
||||||
export default function configureStore(initialState) {
|
|
||||||
let finalCreateStore = compose(
|
|
||||||
reduxReactRouter({ createHistory }),
|
|
||||||
global.devToolsExtension ? global.devToolsExtension() : (f) => f,
|
|
||||||
)(createStore);
|
|
||||||
|
|
||||||
const store = finalCreateStore(rootReducer, initialState);
|
|
||||||
|
|
||||||
if (module.hot) {
|
|
||||||
// Enable Webpack hot module replacement for reducers
|
|
||||||
module.hot.accept('../reducers', () => {
|
|
||||||
const nextReducer = require('../reducers');
|
|
||||||
store.replaceReducer(nextReducer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import * as types from '../../constants/ActionTypes';
|
|
||||||
import * as actions from '../../actions/todos';
|
|
||||||
|
|
||||||
describe('todo actions', () => {
|
|
||||||
it('addTodo should create ADD_TODO action', () => {
|
|
||||||
expect(actions.addTodo('Use Redux')).toEqual({
|
|
||||||
type: types.ADD_TODO,
|
|
||||||
text: 'Use Redux',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('deleteTodo should create DELETE_TODO action', () => {
|
|
||||||
expect(actions.deleteTodo(1)).toEqual({
|
|
||||||
type: types.DELETE_TODO,
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('editTodo should create EDIT_TODO action', () => {
|
|
||||||
expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({
|
|
||||||
type: types.EDIT_TODO,
|
|
||||||
id: 1,
|
|
||||||
text: 'Use Redux everywhere',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('completeTodo should create COMPLETE_TODO action', () => {
|
|
||||||
expect(actions.completeTodo(1)).toEqual({
|
|
||||||
type: types.COMPLETE_TODO,
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('completeAll should create COMPLETE_ALL action', () => {
|
|
||||||
expect(actions.completeAll()).toEqual({
|
|
||||||
type: types.COMPLETE_ALL,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clearCompleted should create CLEAR_COMPLETED action', () => {
|
|
||||||
expect(actions.clearCompleted('Use Redux')).toEqual({
|
|
||||||
type: types.CLEAR_COMPLETED,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils from 'react-addons-test-utils';
|
|
||||||
import Footer from '../../components/Footer';
|
|
||||||
import { SHOW_ALL, SHOW_ACTIVE } from '../../constants/TodoFilters';
|
|
||||||
|
|
||||||
function setup(propOverrides) {
|
|
||||||
const props = Object.assign(
|
|
||||||
{
|
|
||||||
completedCount: 0,
|
|
||||||
activeCount: 0,
|
|
||||||
filter: SHOW_ALL,
|
|
||||||
onClearCompleted: expect.createSpy(),
|
|
||||||
onShow: expect.createSpy(),
|
|
||||||
},
|
|
||||||
propOverrides,
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
|
||||||
renderer.render(<Footer {...props} />);
|
|
||||||
const output = renderer.getRenderOutput();
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
output: output,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTextContent(elem) {
|
|
||||||
const children = Array.isArray(elem.props.children)
|
|
||||||
? elem.props.children
|
|
||||||
: [elem.props.children];
|
|
||||||
|
|
||||||
return children.reduce(function concatText(out, child) {
|
|
||||||
// Children are either elements or text strings
|
|
||||||
return out + (child.props ? getTextContent(child) : child);
|
|
||||||
}, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('components', () => {
|
|
||||||
describe('Footer', () => {
|
|
||||||
it('should render container', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
expect(output.type).toBe('footer');
|
|
||||||
expect(output.props.className).toBe('footer');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display active count when 0', () => {
|
|
||||||
const { output } = setup({ activeCount: 0 });
|
|
||||||
const [count] = output.props.children;
|
|
||||||
expect(getTextContent(count)).toBe('No items left');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display active count when above 0', () => {
|
|
||||||
const { output } = setup({ activeCount: 1 });
|
|
||||||
const [count] = output.props.children;
|
|
||||||
expect(getTextContent(count)).toBe('1 item left');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render filters', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
const [, filters] = output.props.children;
|
|
||||||
expect(filters.type).toBe('ul');
|
|
||||||
expect(filters.props.className).toBe('filters');
|
|
||||||
expect(filters.props.children.length).toBe(3);
|
|
||||||
filters.props.children.forEach(function checkFilter(filter, i) {
|
|
||||||
expect(filter.type).toBe('li');
|
|
||||||
const a = filter.props.children;
|
|
||||||
expect(a.props.className).toBe(i === 0 ? 'selected' : '');
|
|
||||||
expect(a.props.children).toBe(
|
|
||||||
{
|
|
||||||
0: 'All',
|
|
||||||
1: 'Active',
|
|
||||||
2: 'Completed',
|
|
||||||
}[i],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onShow when a filter is clicked', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
const [, filters] = output.props.children;
|
|
||||||
const filterLink = filters.props.children[1].props.children;
|
|
||||||
filterLink.props.onClick({});
|
|
||||||
expect(props.onShow).toHaveBeenCalledWith(SHOW_ACTIVE);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shouldnt show clear button when no completed todos', () => {
|
|
||||||
const { output } = setup({ completedCount: 0 });
|
|
||||||
const [, , clear] = output.props.children;
|
|
||||||
expect(clear).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render clear button when completed todos', () => {
|
|
||||||
const { output } = setup({ completedCount: 1 });
|
|
||||||
const [, , clear] = output.props.children;
|
|
||||||
expect(clear.type).toBe('button');
|
|
||||||
expect(clear.props.children).toBe('Clear completed');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onClearCompleted on clear button click', () => {
|
|
||||||
const { output, props } = setup({ completedCount: 1 });
|
|
||||||
const [, , clear] = output.props.children;
|
|
||||||
clear.props.onClick({});
|
|
||||||
expect(props.onClearCompleted).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils from 'react-addons-test-utils';
|
|
||||||
import Header from '../../components/Header';
|
|
||||||
import TodoTextInput from '../../components/TodoTextInput';
|
|
||||||
|
|
||||||
function setup() {
|
|
||||||
const props = {
|
|
||||||
addTodo: expect.createSpy(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
|
||||||
renderer.render(<Header {...props} />);
|
|
||||||
const output = renderer.getRenderOutput();
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
output: output,
|
|
||||||
renderer: renderer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('components', () => {
|
|
||||||
describe('Header', () => {
|
|
||||||
it('should render correctly', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
|
|
||||||
expect(output.type).toBe('header');
|
|
||||||
expect(output.props.className).toBe('header');
|
|
||||||
|
|
||||||
const [h1, input] = output.props.children;
|
|
||||||
|
|
||||||
expect(h1.type).toBe('h1');
|
|
||||||
expect(h1.props.children).toBe('todos');
|
|
||||||
|
|
||||||
expect(input.type).toBe(TodoTextInput);
|
|
||||||
expect(input.props.newTodo).toBe(true);
|
|
||||||
expect(input.props.placeholder).toBe('What needs to be done?');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call call addTodo if length of text is greater than 0', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
const input = output.props.children[1];
|
|
||||||
input.props.onSave('');
|
|
||||||
expect(props.addTodo.calls.length).toBe(0);
|
|
||||||
input.props.onSave('Use Redux');
|
|
||||||
expect(props.addTodo.calls.length).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils from 'react-addons-test-utils';
|
|
||||||
import MainSection from '../../components/MainSection';
|
|
||||||
import TodoItem from '../../components/TodoItem';
|
|
||||||
import Footer from '../../components/Footer';
|
|
||||||
import { SHOW_ALL, SHOW_COMPLETED } from '../../constants/TodoFilters';
|
|
||||||
|
|
||||||
function setup(propOverrides) {
|
|
||||||
const props = Object.assign(
|
|
||||||
{
|
|
||||||
todos: [
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: true,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: {
|
|
||||||
editTodo: expect.createSpy(),
|
|
||||||
deleteTodo: expect.createSpy(),
|
|
||||||
completeTodo: expect.createSpy(),
|
|
||||||
completeAll: expect.createSpy(),
|
|
||||||
clearCompleted: expect.createSpy(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
propOverrides,
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
|
||||||
renderer.render(<MainSection {...props} />);
|
|
||||||
const output = renderer.getRenderOutput();
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
output: output,
|
|
||||||
renderer: renderer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('components', () => {
|
|
||||||
describe('MainSection', () => {
|
|
||||||
it('should render container', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
expect(output.type).toBe('section');
|
|
||||||
expect(output.props.className).toBe('main');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('toggle all input', () => {
|
|
||||||
it('should render', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
const [toggle] = output.props.children;
|
|
||||||
expect(toggle.type).toBe('input');
|
|
||||||
expect(toggle.props.type).toBe('checkbox');
|
|
||||||
expect(toggle.props.checked).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be checked if all todos completed', () => {
|
|
||||||
const { output } = setup({
|
|
||||||
todos: [
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: true,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const [toggle] = output.props.children;
|
|
||||||
expect(toggle.props.checked).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call completeAll on change', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
const [toggle] = output.props.children;
|
|
||||||
toggle.props.onChange({});
|
|
||||||
expect(props.actions.completeAll).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('footer', () => {
|
|
||||||
it('should render', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
const [, , footer] = output.props.children;
|
|
||||||
expect(footer.type).toBe(Footer);
|
|
||||||
expect(footer.props.completedCount).toBe(1);
|
|
||||||
expect(footer.props.activeCount).toBe(1);
|
|
||||||
expect(footer.props.filter).toBe(SHOW_ALL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('onShow should set the filter', () => {
|
|
||||||
const { output, renderer } = setup();
|
|
||||||
const [, , footer] = output.props.children;
|
|
||||||
footer.props.onShow(SHOW_COMPLETED);
|
|
||||||
const updated = renderer.getRenderOutput();
|
|
||||||
const [, , updatedFooter] = updated.props.children;
|
|
||||||
expect(updatedFooter.props.filter).toBe(SHOW_COMPLETED);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('onClearCompleted should call clearCompleted', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
const [, , footer] = output.props.children;
|
|
||||||
footer.props.onClearCompleted();
|
|
||||||
expect(props.actions.clearCompleted).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('onClearCompleted shouldnt call clearCompleted if no todos completed', () => {
|
|
||||||
const { output, props } = setup({
|
|
||||||
todos: [
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const [, , footer] = output.props.children;
|
|
||||||
footer.props.onClearCompleted();
|
|
||||||
expect(props.actions.clearCompleted.calls.length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('todo list', () => {
|
|
||||||
it('should render', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
const [, list] = output.props.children;
|
|
||||||
expect(list.type).toBe('ul');
|
|
||||||
expect(list.props.children.length).toBe(2);
|
|
||||||
list.props.children.forEach((item, i) => {
|
|
||||||
expect(item.type).toBe(TodoItem);
|
|
||||||
expect(item.props.todo).toBe(props.todos[i]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter items', () => {
|
|
||||||
const { output, renderer, props } = setup();
|
|
||||||
const [, , footer] = output.props.children;
|
|
||||||
footer.props.onShow(SHOW_COMPLETED);
|
|
||||||
const updated = renderer.getRenderOutput();
|
|
||||||
const [, updatedList] = updated.props.children;
|
|
||||||
expect(updatedList.props.children.length).toBe(1);
|
|
||||||
expect(updatedList.props.children[0].props.todo).toBe(props.todos[1]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils from 'react-addons-test-utils';
|
|
||||||
import TodoItem from '../../components/TodoItem';
|
|
||||||
import TodoTextInput from '../../components/TodoTextInput';
|
|
||||||
|
|
||||||
function setup(editing = false) {
|
|
||||||
const props = {
|
|
||||||
todo: {
|
|
||||||
id: 0,
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
},
|
|
||||||
editTodo: expect.createSpy(),
|
|
||||||
deleteTodo: expect.createSpy(),
|
|
||||||
completeTodo: expect.createSpy(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
|
||||||
|
|
||||||
renderer.render(<TodoItem {...props} />);
|
|
||||||
|
|
||||||
let output = renderer.getRenderOutput();
|
|
||||||
|
|
||||||
if (editing) {
|
|
||||||
const label = output.props.children.props.children[1];
|
|
||||||
label.props.onDoubleClick({});
|
|
||||||
output = renderer.getRenderOutput();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
output: output,
|
|
||||||
renderer: renderer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('components', () => {
|
|
||||||
describe('TodoItem', () => {
|
|
||||||
it('initial render', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
|
|
||||||
expect(output.type).toBe('li');
|
|
||||||
expect(output.props.className).toBe('');
|
|
||||||
|
|
||||||
const div = output.props.children;
|
|
||||||
|
|
||||||
expect(div.type).toBe('div');
|
|
||||||
expect(div.props.className).toBe('view');
|
|
||||||
|
|
||||||
const [input, label, button] = div.props.children;
|
|
||||||
|
|
||||||
expect(input.type).toBe('input');
|
|
||||||
expect(input.props.checked).toBe(false);
|
|
||||||
|
|
||||||
expect(label.type).toBe('label');
|
|
||||||
expect(label.props.children).toBe('Use Redux');
|
|
||||||
|
|
||||||
expect(button.type).toBe('button');
|
|
||||||
expect(button.props.className).toBe('destroy');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('input onChange should call completeTodo', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
const input = output.props.children.props.children[0];
|
|
||||||
input.props.onChange({});
|
|
||||||
expect(props.completeTodo).toHaveBeenCalledWith(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('button onClick should call deleteTodo', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
const button = output.props.children.props.children[2];
|
|
||||||
button.props.onClick({});
|
|
||||||
expect(props.deleteTodo).toHaveBeenCalledWith(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('label onDoubleClick should put component in edit state', () => {
|
|
||||||
const { output, renderer } = setup();
|
|
||||||
const label = output.props.children.props.children[1];
|
|
||||||
label.props.onDoubleClick({});
|
|
||||||
const updated = renderer.getRenderOutput();
|
|
||||||
expect(updated.type).toBe('li');
|
|
||||||
expect(updated.props.className).toBe('editing');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('edit state render', () => {
|
|
||||||
const { output } = setup(true);
|
|
||||||
|
|
||||||
expect(output.type).toBe('li');
|
|
||||||
expect(output.props.className).toBe('editing');
|
|
||||||
|
|
||||||
const input = output.props.children;
|
|
||||||
expect(input.type).toBe(TodoTextInput);
|
|
||||||
expect(input.props.text).toBe('Use Redux');
|
|
||||||
expect(input.props.editing).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('TodoTextInput onSave should call editTodo', () => {
|
|
||||||
const { output, props } = setup(true);
|
|
||||||
output.props.children.props.onSave('Use Redux');
|
|
||||||
expect(props.editTodo).toHaveBeenCalledWith(0, 'Use Redux');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('TodoTextInput onSave should call deleteTodo if text is empty', () => {
|
|
||||||
const { output, props } = setup(true);
|
|
||||||
output.props.children.props.onSave('');
|
|
||||||
expect(props.deleteTodo).toHaveBeenCalledWith(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('TodoTextInput onSave should exit component from edit state', () => {
|
|
||||||
const { output, renderer } = setup(true);
|
|
||||||
output.props.children.props.onSave('Use Redux');
|
|
||||||
const updated = renderer.getRenderOutput();
|
|
||||||
expect(updated.type).toBe('li');
|
|
||||||
expect(updated.props.className).toBe('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import React from 'react';
|
|
||||||
import TestUtils from 'react-addons-test-utils';
|
|
||||||
import TodoTextInput from '../../components/TodoTextInput';
|
|
||||||
|
|
||||||
function setup(propOverrides) {
|
|
||||||
const props = Object.assign(
|
|
||||||
{
|
|
||||||
onSave: expect.createSpy(),
|
|
||||||
text: 'Use Redux',
|
|
||||||
placeholder: 'What needs to be done?',
|
|
||||||
editing: false,
|
|
||||||
newTodo: false,
|
|
||||||
},
|
|
||||||
propOverrides,
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
|
||||||
|
|
||||||
renderer.render(<TodoTextInput {...props} />);
|
|
||||||
|
|
||||||
let output = renderer.getRenderOutput();
|
|
||||||
|
|
||||||
output = renderer.getRenderOutput();
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: props,
|
|
||||||
output: output,
|
|
||||||
renderer: renderer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('components', () => {
|
|
||||||
describe('TodoTextInput', () => {
|
|
||||||
it('should render correctly', () => {
|
|
||||||
const { output } = setup();
|
|
||||||
expect(output.props.placeholder).toEqual('What needs to be done?');
|
|
||||||
expect(output.props.value).toEqual('Use Redux');
|
|
||||||
expect(output.props.className).toEqual('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render correctly when editing=true', () => {
|
|
||||||
const { output } = setup({ editing: true });
|
|
||||||
expect(output.props.className).toEqual('edit');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render correctly when newTodo=true', () => {
|
|
||||||
const { output } = setup({ newTodo: true });
|
|
||||||
expect(output.props.className).toEqual('new-todo');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update value on change', () => {
|
|
||||||
const { output, renderer } = setup();
|
|
||||||
output.props.onChange({ target: { value: 'Use Radox' } });
|
|
||||||
const updated = renderer.getRenderOutput();
|
|
||||||
expect(updated.props.value).toEqual('Use Radox');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onSave on return key press', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
output.props.onKeyDown({ which: 13, target: { value: 'Use Redux' } });
|
|
||||||
expect(props.onSave).toHaveBeenCalledWith('Use Redux');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset state on return key press if newTodo', () => {
|
|
||||||
const { output, renderer } = setup({ newTodo: true });
|
|
||||||
output.props.onKeyDown({ which: 13, target: { value: 'Use Redux' } });
|
|
||||||
const updated = renderer.getRenderOutput();
|
|
||||||
expect(updated.props.value).toEqual('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call onSave on blur', () => {
|
|
||||||
const { output, props } = setup();
|
|
||||||
output.props.onBlur({ target: { value: 'Use Redux' } });
|
|
||||||
expect(props.onSave).toHaveBeenCalledWith('Use Redux');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shouldnt call onSave on blur if newTodo', () => {
|
|
||||||
const { output, props } = setup({ newTodo: true });
|
|
||||||
output.props.onBlur({ target: { value: 'Use Redux' } });
|
|
||||||
expect(props.onSave.calls.length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,325 +0,0 @@
|
||||||
import expect from 'expect';
|
|
||||||
import todos from '../../reducers/todos';
|
|
||||||
import * as types from '../../constants/ActionTypes';
|
|
||||||
|
|
||||||
describe('todos reducer', () => {
|
|
||||||
it('should handle initial state', () => {
|
|
||||||
expect(todos(undefined, {})).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle ADD_TODO', () => {
|
|
||||||
expect(
|
|
||||||
todos([], {
|
|
||||||
type: types.ADD_TODO,
|
|
||||||
text: 'Run the tests',
|
|
||||||
}),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.ADD_TODO,
|
|
||||||
text: 'Run the tests',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.ADD_TODO,
|
|
||||||
text: 'Fix the tests',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Fix the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle DELETE_TODO', () => {
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.DELETE_TODO,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle EDIT_TODO', () => {
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.EDIT_TODO,
|
|
||||||
text: 'Fix the tests',
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Fix the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle COMPLETE_TODO', () => {
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.COMPLETE_TODO,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: true,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle COMPLETE_ALL', () => {
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: true,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.COMPLETE_ALL,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: true,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: true,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Unmark if all todos are currently completed
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: true,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: true,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.COMPLETE_ALL,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle CLEAR_COMPLETED', () => {
|
|
||||||
expect(
|
|
||||||
todos(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'Run the tests',
|
|
||||||
completed: true,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
type: types.CLEAR_COMPLETED,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Use Redux',
|
|
||||||
completed: false,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not generate duplicate ids after CLEAR_COMPLETED', () => {
|
|
||||||
expect(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
type: types.COMPLETE_TODO,
|
|
||||||
id: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: types.CLEAR_COMPLETED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: types.ADD_TODO,
|
|
||||||
text: 'Write more tests',
|
|
||||||
},
|
|
||||||
].reduce(todos, [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
completed: false,
|
|
||||||
text: 'Use Redux',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
completed: false,
|
|
||||||
text: 'Write tests',
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
).toEqual([
|
|
||||||
{
|
|
||||||
text: 'Write more tests',
|
|
||||||
completed: false,
|
|
||||||
id: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Write tests',
|
|
||||||
completed: false,
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { jsdom } from 'jsdom';
|
|
||||||
|
|
||||||
global.document = jsdom('<!doctype html><html><body></body></html>');
|
|
||||||
global.window = document.defaultView;
|
|
||||||
global.navigator = global.window.navigator;
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
var path = require('path');
|
|
||||||
var webpack = require('webpack');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
devtool: 'cheap-module-eval-source-map',
|
|
||||||
entry: ['webpack-hot-middleware/client', './index'],
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'dist'),
|
|
||||||
filename: 'bundle.js',
|
|
||||||
publicPath: '/static/',
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.optimize.OccurenceOrderPlugin(),
|
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
new webpack.NoErrorsPlugin(),
|
|
||||||
],
|
|
||||||
module: {
|
|
||||||
loaders: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loaders: ['babel'],
|
|
||||||
exclude: /node_modules/,
|
|
||||||
include: __dirname,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css?$/,
|
|
||||||
loaders: ['style', 'raw'],
|
|
||||||
include: __dirname,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["es2015", "stage-0", "react"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Redux Saga Counter example</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="/static/bundle.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"name": "redux-counter-example",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "Redux counter example",
|
|
||||||
"scripts": {
|
|
||||||
"start": "webpack-dev-server --progress"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/rackt/redux.git"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/rackt/redux/issues"
|
|
||||||
},
|
|
||||||
"homepage": "http://rackt.github.io/redux",
|
|
||||||
"dependencies": {
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"react": "^16.0.0",
|
|
||||||
"react-dom": "^16.0.0",
|
|
||||||
"react-redux": "^6.0.0",
|
|
||||||
"redux": "^4.0.0",
|
|
||||||
"redux-saga": "^0.10.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-cli": "^6.3.17",
|
|
||||||
"babel-core": "^6.3.17",
|
|
||||||
"babel-loader": "^7.0.0",
|
|
||||||
"babel-preset-es2015": "^6.0.0",
|
|
||||||
"babel-preset-react": "6.3.13",
|
|
||||||
"babel-preset-stage-0": "^6.3.13",
|
|
||||||
"webpack": "^4.0.0",
|
|
||||||
"webpack-cli": "^3.2.0",
|
|
||||||
"webpack-dev-server": "^3.1.0",
|
|
||||||
"webpack-hot-middleware": "^2.24.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const Counter = ({
|
|
||||||
value,
|
|
||||||
onIncrement,
|
|
||||||
onIncrementAsync,
|
|
||||||
onDecrement,
|
|
||||||
onIncrementIfOdd,
|
|
||||||
}) => (
|
|
||||||
<p>
|
|
||||||
Clicked: {value} times <button onClick={onIncrement}>+</button>{' '}
|
|
||||||
<button onClick={onDecrement}>-</button>{' '}
|
|
||||||
<button onClick={onIncrementIfOdd}>Increment if odd</button>{' '}
|
|
||||||
<button onClick={onIncrementAsync}>Increment async</button>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
|
|
||||||
Counter.propTypes = {
|
|
||||||
value: PropTypes.number.isRequired,
|
|
||||||
onIncrement: PropTypes.func.isRequired,
|
|
||||||
onDecrement: PropTypes.func.isRequired,
|
|
||||||
onIncrementAsync: PropTypes.func.isRequired,
|
|
||||||
onIncrementIfOdd: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Counter;
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import 'babel-polyfill';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
|
||||||
import createSagaMiddleware from 'redux-saga';
|
|
||||||
// import sagaMonitor from './sagaMonitor'
|
|
||||||
|
|
||||||
import Counter from './components/Counter';
|
|
||||||
import reducer from './reducers';
|
|
||||||
import rootSaga from './sagas';
|
|
||||||
|
|
||||||
const sagaMiddleware = createSagaMiddleware(/* {sagaMonitor} */);
|
|
||||||
const composeEnhancers =
|
|
||||||
(window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
|
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
|
||||||
trace: true,
|
|
||||||
traceLimit: 25,
|
|
||||||
})) ||
|
|
||||||
compose;
|
|
||||||
const store = createStore(
|
|
||||||
reducer,
|
|
||||||
composeEnhancers(applyMiddleware(sagaMiddleware)),
|
|
||||||
);
|
|
||||||
sagaMiddleware.run(rootSaga);
|
|
||||||
|
|
||||||
const action = (type) => store.dispatch({ type });
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
ReactDOM.render(
|
|
||||||
<Counter
|
|
||||||
value={store.getState()}
|
|
||||||
onIncrement={() => action('INCREMENT')}
|
|
||||||
onDecrement={() => action('DECREMENT')}
|
|
||||||
onIncrementIfOdd={() => action('INCREMENT_IF_ODD')}
|
|
||||||
onIncrementAsync={() => action('INCREMENT_ASYNC')}
|
|
||||||
/>,
|
|
||||||
document.getElementById('root'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render();
|
|
||||||
store.subscribe(render);
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
export default function counter(state = 0, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'INCREMENT':
|
|
||||||
return state + 1;
|
|
||||||
case 'INCREMENT_IF_ODD':
|
|
||||||
return state % 2 !== 0 ? state + 1 : state;
|
|
||||||
case 'DECREMENT':
|
|
||||||
return state - 1;
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/* eslint-disable no-constant-condition */
|
|
||||||
|
|
||||||
import { takeEvery } from 'redux-saga';
|
|
||||||
import { put, call } from 'redux-saga/effects';
|
|
||||||
import { delay } from 'redux-saga';
|
|
||||||
|
|
||||||
export function* incrementAsync() {
|
|
||||||
yield call(delay, 1000);
|
|
||||||
yield put({ type: 'INCREMENT' });
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function* rootSaga() {
|
|
||||||
yield* takeEvery('INCREMENT_ASYNC', incrementAsync);
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
var path = require('path');
|
|
||||||
var webpack = require('webpack');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mode: 'development',
|
|
||||||
devtool: 'source-map',
|
|
||||||
entry: [path.join(__dirname, 'src', 'main')],
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'dist'),
|
|
||||||
filename: 'bundle.js',
|
|
||||||
publicPath: '/static/',
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loaders: ['babel-loader'],
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
devServer: {
|
|
||||||
port: 4003,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["es2015", "stage-0", "react"]
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './todos';
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import * as types from '../constants/ActionTypes';
|
|
||||||
|
|
||||||
export function addTodo(text) {
|
|
||||||
return { type: types.ADD_TODO, text };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteTodo(id) {
|
|
||||||
return { type: types.DELETE_TODO, id };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editTodo(id, text) {
|
|
||||||
return { type: types.EDIT_TODO, id, text };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function completeTodo(id) {
|
|
||||||
return { type: types.COMPLETE_TODO, id };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function completeAll() {
|
|
||||||
return { type: types.COMPLETE_ALL };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearCompleted() {
|
|
||||||
return { type: types.CLEAR_COMPLETED };
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import {
|
|
||||||
SHOW_ALL,
|
|
||||||
SHOW_COMPLETED,
|
|
||||||
SHOW_ACTIVE,
|
|
||||||
} from '../constants/TodoFilters';
|
|
||||||
|
|
||||||
const FILTER_TITLES = {
|
|
||||||
[SHOW_ALL]: 'All',
|
|
||||||
[SHOW_ACTIVE]: 'Active',
|
|
||||||
[SHOW_COMPLETED]: 'Completed',
|
|
||||||
};
|
|
||||||
|
|
||||||
class Footer extends Component {
|
|
||||||
renderTodoCount() {
|
|
||||||
const { activeCount } = this.props;
|
|
||||||
const itemWord = activeCount === 1 ? 'item' : 'items';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className="todo-count">
|
|
||||||
<strong>{activeCount || 'No'}</strong> {itemWord} left
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFilterLink(filter) {
|
|
||||||
const title = FILTER_TITLES[filter];
|
|
||||||
const { filter: selectedFilter, onShow } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
className={classnames({ selected: filter === selectedFilter })}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
onClick={() => onShow(filter)}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderClearButton() {
|
|
||||||
const { completedCount, onClearCompleted } = this.props;
|
|
||||||
if (completedCount > 0) {
|
|
||||||
return (
|
|
||||||
<button className="clear-completed" onClick={onClearCompleted}>
|
|
||||||
Clear completed
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<footer className="footer">
|
|
||||||
{this.renderTodoCount()}
|
|
||||||
<ul className="filters">
|
|
||||||
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map((filter) => (
|
|
||||||
<li key={filter}>{this.renderFilterLink(filter)}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{this.renderClearButton()}
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Footer.propTypes = {
|
|
||||||
completedCount: PropTypes.number.isRequired,
|
|
||||||
activeCount: PropTypes.number.isRequired,
|
|
||||||
filter: PropTypes.string.isRequired,
|
|
||||||
onClearCompleted: PropTypes.func.isRequired,
|
|
||||||
onShow: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Footer;
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import TodoTextInput from './TodoTextInput';
|
|
||||||
|
|
||||||
class Header extends Component {
|
|
||||||
handleSave(text) {
|
|
||||||
if (text.length !== 0) {
|
|
||||||
this.props.addTodo(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<header className="header">
|
|
||||||
<h1>todos</h1>
|
|
||||||
<TodoTextInput
|
|
||||||
newTodo
|
|
||||||
onSave={this.handleSave.bind(this)}
|
|
||||||
placeholder="What needs to be done?"
|
|
||||||
/>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Header.propTypes = {
|
|
||||||
addTodo: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import TodoItem from './TodoItem';
|
|
||||||
import Footer from './Footer';
|
|
||||||
import {
|
|
||||||
SHOW_ALL,
|
|
||||||
SHOW_COMPLETED,
|
|
||||||
SHOW_ACTIVE,
|
|
||||||
} from '../constants/TodoFilters';
|
|
||||||
|
|
||||||
const TODO_FILTERS = {
|
|
||||||
[SHOW_ALL]: () => true,
|
|
||||||
[SHOW_ACTIVE]: (todo) => !todo.completed,
|
|
||||||
[SHOW_COMPLETED]: (todo) => todo.completed,
|
|
||||||
};
|
|
||||||
|
|
||||||
class MainSection extends Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = { filter: SHOW_ALL };
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClearCompleted() {
|
|
||||||
const atLeastOneCompleted = this.props.todos.some((todo) => todo.completed);
|
|
||||||
if (atLeastOneCompleted) {
|
|
||||||
this.props.actions.clearCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleShow(filter) {
|
|
||||||
this.setState({ filter });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderToggleAll(completedCount) {
|
|
||||||
const { todos, actions } = this.props;
|
|
||||||
if (todos.length > 0) {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
className="toggle-all"
|
|
||||||
type="checkbox"
|
|
||||||
checked={completedCount === todos.length}
|
|
||||||
onChange={actions.completeAll}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFooter(completedCount) {
|
|
||||||
const { todos } = this.props;
|
|
||||||
const { filter } = this.state;
|
|
||||||
const activeCount = todos.length - completedCount;
|
|
||||||
|
|
||||||
if (todos.length) {
|
|
||||||
return (
|
|
||||||
<Footer
|
|
||||||
completedCount={completedCount}
|
|
||||||
activeCount={activeCount}
|
|
||||||
filter={filter}
|
|
||||||
onClearCompleted={this.handleClearCompleted.bind(this)}
|
|
||||||
onShow={this.handleShow.bind(this)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { todos, actions } = this.props;
|
|
||||||
const { filter } = this.state;
|
|
||||||
|
|
||||||
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
|
||||||
const completedCount = todos.reduce(
|
|
||||||
(count, todo) => (todo.completed ? count + 1 : count),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="main">
|
|
||||||
{this.renderToggleAll(completedCount)}
|
|
||||||
<ul className="todo-list">
|
|
||||||
{filteredTodos.map((todo) => (
|
|
||||||
<TodoItem key={todo.id} todo={todo} {...actions} />
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
{this.renderFooter(completedCount)}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MainSection.propTypes = {
|
|
||||||
todos: PropTypes.array.isRequired,
|
|
||||||
actions: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MainSection;
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import TodoTextInput from './TodoTextInput';
|
|
||||||
|
|
||||||
class TodoItem extends Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = {
|
|
||||||
editing: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDoubleClick() {
|
|
||||||
this.setState({ editing: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSave(id, text) {
|
|
||||||
if (text.length === 0) {
|
|
||||||
this.props.deleteTodo(id);
|
|
||||||
} else {
|
|
||||||
this.props.editTodo(id, text);
|
|
||||||
}
|
|
||||||
this.setState({ editing: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { todo, completeTodo, deleteTodo } = this.props;
|
|
||||||
|
|
||||||
let element;
|
|
||||||
if (this.state.editing) {
|
|
||||||
element = (
|
|
||||||
<TodoTextInput
|
|
||||||
text={todo.text}
|
|
||||||
editing={this.state.editing}
|
|
||||||
onSave={(text) => this.handleSave(todo.id, text)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
element = (
|
|
||||||
<div className="view">
|
|
||||||
<input
|
|
||||||
className="toggle"
|
|
||||||
type="checkbox"
|
|
||||||
checked={todo.completed}
|
|
||||||
onChange={() => completeTodo(todo.id)}
|
|
||||||
/>
|
|
||||||
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
|
||||||
{todo.text}
|
|
||||||
</label>
|
|
||||||
<button className="destroy" onClick={() => deleteTodo(todo.id)} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={classnames({
|
|
||||||
completed: todo.completed,
|
|
||||||
editing: this.state.editing,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{element}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TodoItem.propTypes = {
|
|
||||||
todo: PropTypes.object.isRequired,
|
|
||||||
editTodo: PropTypes.func.isRequired,
|
|
||||||
deleteTodo: PropTypes.func.isRequired,
|
|
||||||
completeTodo: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TodoItem;
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
class TodoTextInput extends Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = {
|
|
||||||
text: this.props.text || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(e) {
|
|
||||||
const text = e.target.value.trim();
|
|
||||||
if (e.which === 13) {
|
|
||||||
this.props.onSave(text);
|
|
||||||
if (this.props.newTodo) {
|
|
||||||
this.setState({ text: '' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange(e) {
|
|
||||||
this.setState({ text: e.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBlur(e) {
|
|
||||||
if (!this.props.newTodo) {
|
|
||||||
this.props.onSave(e.target.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
className={classnames({
|
|
||||||
edit: this.props.editing,
|
|
||||||
'new-todo': this.props.newTodo,
|
|
||||||
})}
|
|
||||||
type="text"
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
autoFocus={true}
|
|
||||||
value={this.state.text}
|
|
||||||
onBlur={this.handleBlur.bind(this)}
|
|
||||||
onChange={this.handleChange.bind(this)}
|
|
||||||
onKeyDown={this.handleSubmit.bind(this)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TodoTextInput.propTypes = {
|
|
||||||
onSave: PropTypes.func.isRequired,
|
|
||||||
text: PropTypes.string,
|
|
||||||
placeholder: PropTypes.string,
|
|
||||||
editing: PropTypes.bool,
|
|
||||||
newTodo: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TodoTextInput;
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export const ADD_TODO = 'ADD_TODO';
|
|
||||||
export const DELETE_TODO = 'DELETE_TODO';
|
|
||||||
export const EDIT_TODO = 'EDIT_TODO';
|
|
||||||
export const COMPLETE_TODO = 'COMPLETE_TODO';
|
|
||||||
export const COMPLETE_ALL = 'COMPLETE_ALL';
|
|
||||||
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED';
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export const SHOW_ALL = 'show_all';
|
|
||||||
export const SHOW_COMPLETED = 'show_completed';
|
|
||||||
export const SHOW_ACTIVE = 'show_active';
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Header from '../components/Header';
|
|
||||||
import MainSection from '../components/MainSection';
|
|
||||||
import * as TodoActions from '../actions/todos';
|
|
||||||
|
|
||||||
class App extends Component {
|
|
||||||
render() {
|
|
||||||
const { todos, actions } = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Header addTodo={actions.addTodo} />
|
|
||||||
<MainSection todos={todos} actions={actions} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App.propTypes = {
|
|
||||||
todos: PropTypes.array.isRequired,
|
|
||||||
actions: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
todos: state.todos,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
actions: bindActionCreators(TodoActions, dispatch),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Redux TodoMVC example</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="todoapp" id="root"></div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import 'babel-polyfill';
|
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import App from './containers/App';
|
|
||||||
import configureStore from './store/configureStore';
|
|
||||||
import 'todomvc-app-css/index.css';
|
|
||||||
|
|
||||||
const store = configureStore();
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<App />
|
|
||||||
</Provider>,
|
|
||||||
document.getElementById('root'),
|
|
||||||
);
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user