Compare commits

..

No commits in common. "main" and "@redux-devtools/rtk-query-monitor@2.0.2" have entirely different histories.

1115 changed files with 54633 additions and 36801 deletions

View File

@ -1,13 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}

View File

@ -7,5 +7,6 @@ build
coverage coverage
node_modules node_modules
__snapshots__ __snapshots__
.yarn/*
storybook-static storybook-static
.vscode/* .vscode/*

15
.gitattributes vendored
View File

@ -1 +1,14 @@
* text=auto eol=lf *.js text 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

View File

@ -8,24 +8,15 @@ on:
jobs: jobs:
build: build:
runs-on: 'ubuntu-22.04' runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v2
- uses: pnpm/action-setup@v4 - run: yarn install
- uses: actions/setup-node@v6 - run: yarn format:check
- run: yarn build:all
- run: yarn lint:all
- name: Run yarn test:all
uses: GabrielBB/xvfb-action@v1
with: with:
node-version: 'lts/*' run: yarn test:all
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Check formatting
run: pnpm run format:check
- name: Build
run: pnpm run build:all
- name: Lint
run: pnpm run lint:all
- name: Test
uses: coactions/setup-xvfb@v1
with:
run: pnpm run test:all

View File

@ -1,58 +0,0 @@
name: Release
on:
push:
branches:
- main
permissions: write-all
jobs:
release:
name: Release
runs-on: 'ubuntu-22.04'
steps:
- name: Checkout Repo
uses: actions/checkout@v6
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 'lts/*'
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
version: pnpm changeset version
publish: pnpm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Archive Chrome Extension
uses: actions/upload-artifact@v7
with:
name: chrome
path: extension/chrome/dist
- name: Archive Edge Extension
uses: actions/upload-artifact@v7
with:
name: edge
path: extension/edge/dist
- name: Archive Firefox Extension
uses: actions/upload-artifact@v7
with:
name: firefox
path: extension/firefox/dist

8
.gitignore vendored
View File

@ -9,4 +9,10 @@ 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 .yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*

View File

@ -8,7 +8,8 @@ coverage
node_modules node_modules
__snapshots__ __snapshots__
dev dev
.yarn/*
.pnp.*
**/demo/public/** **/demo/public/**
storybook-static storybook-static
.vscode/* .vscode/*
pnpm-lock.yaml

10
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"eslint.nodePath": ".yarn/sdks",
"prettier.prettierPath": ".yarn/sdks/prettier/index.js",
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

768
.yarn/releases/yarn-3.1.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

20
.yarn/sdks/eslint/bin/eslint.js vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/bin/eslint.js your application uses
module.exports = absRequire(`eslint/bin/eslint.js`);

6
.yarn/sdks/eslint/package.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"name": "eslint",
"version": "8.6.0-sdk",
"main": "./lib/api.js",
"type": "commonjs"
}

5
.yarn/sdks/integrations.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!
integrations:
- vscode

20
.yarn/sdks/prettier/index.js vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/index.js
require(absPnpApiPath).setup();
}
}
// Defer to the real prettier/index.js your application uses
module.exports = absRequire(`prettier/index.js`);

6
.yarn/sdks/prettier/package.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"name": "prettier",
"version": "2.5.1-sdk",
"main": "./index.js",
"type": "commonjs"
}

20
.yarn/sdks/typescript/bin/tsc vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsc
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsc your application uses
module.exports = absRequire(`typescript/bin/tsc`);

20
.yarn/sdks/typescript/bin/tsserver vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsserver
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsserver your application uses
module.exports = absRequire(`typescript/bin/tsserver`);

6
.yarn/sdks/typescript/package.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"name": "typescript",
"version": "4.5.4-sdk",
"main": "./lib/typescript.js",
"type": "commonjs"
}

33
.yarnrc.yml Normal file
View File

@ -0,0 +1,33 @@
yarnPath: .yarn/releases/yarn-3.1.1.cjs
packageExtensions:
'http-proxy-middleware@^2.0.0':
dependencies:
'@types/express': '^4.17.13'
'@storybook/react@*':
dependencies:
'@types/node': '*'
'@storybook/theming@*':
dependencies:
'@emotion/serialize': '^0.11.16'
'@emotion/utils': '^0.11.3'
'apollo-server-core@^3.4.0':
dependencies:
'@types/node': '*'
'tarn@^3.0.1':
dependencies:
'@types/node': '*'
'@chakra-ui/switch@^1.2.10':
peerDependencies:
'framer-motion': '3.x || 4.x || 5.x'
'@chakra-ui/media-query@^1.1.2':
peerDependencies:
'@emotion/react': '^11.0.0'
'@emotion/styled': '^11.0.0'
'@chakra-ui/skeleton@^1.1.18':
peerDependencies:
'@emotion/react': '^11.0.0'
'@emotion/styled': '^11.0.0'
'@chakra-ui/accordion@^1.3.6':
peerDependencies:
'framer-motion': '3.x || 4.x || 5.x'

4
CHANGELOG.md Normal file
View File

@ -0,0 +1,4 @@
# Change log
This project adheres to [Semantic Versioning](http://semver.org/).
See all notable changes on [Releases](https://github.com/gaearon/redux-devtools/releases) page.

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-present Dan Abramov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,7 +1,4 @@
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/reduxjs/redux-devtools/CI) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/reduxjs/redux-devtools/CI) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=round-square)](https://github.com/reduxjs/redux-devtools/pulls)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=round-square)](https://github.com/reduxjs/redux-devtools/pulls)
[![OpenCollective](https://opencollective.com/redux-devtools-extension/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/redux-devtools-extension/sponsors/badge.svg)](#sponsors)
# Redux DevTools # Redux DevTools
@ -11,7 +8,7 @@ It can be used as a browser extension (for [Chrome](https://chrome.google.com/we
![image](https://user-images.githubusercontent.com/7957859/48663602-3aac4900-ea9b-11e8-921f-97059cbb599c.png) ![image](https://user-images.githubusercontent.com/7957859/48663602-3aac4900-ea9b-11e8-921f-97059cbb599c.png)
## Documentation ### Documentation
- [Browser Extension Installation and Configuration](https://github.com/reduxjs/redux-devtools/tree/main/extension#installation) - [Browser Extension Installation and Configuration](https://github.com/reduxjs/redux-devtools/tree/main/extension#installation)
- [Manual Integration as a React Component](./docs/Walkthrough.md#manual-integration) - [Manual Integration as a React Component](./docs/Walkthrough.md#manual-integration)
@ -22,80 +19,6 @@ It can be used as a browser extension (for [Chrome](https://chrome.google.com/we
- [Recipes](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Recipes.md) - [Recipes](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Recipes.md)
- [FAQ](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/FAQ.md) - [FAQ](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/FAQ.md)
## Development
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
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/redux-devtools-extension#backer)]
<a href="https://opencollective.com/redux-devtools-extension/backer/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/1/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/2/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/3/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/4/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/5/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/6/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/7/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/8/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/9/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/10/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/11/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/12/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/13/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/14/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/15/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/16/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/17/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/18/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/19/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/20/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/21/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/22/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/23/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/24/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/25/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/26/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/27/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/28/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/backer/29/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/redux-devtools-extension#sponsor)]
<a href="https://opencollective.com/redux-devtools-extension/sponsor/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/1/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/2/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/3/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/4/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/5/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/6/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/7/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/8/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/9/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/10/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/11/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/12/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/13/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/14/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/15/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/16/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/17/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/18/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/19/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/20/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/21/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/22/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/23/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/24/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/25/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/26/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/27/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/28/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/redux-devtools-extension/sponsor/29/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/29/avatar.svg"></a>
### License ### License
MIT MIT

View File

@ -52,7 +52,7 @@ const DevTools = createDevTools(
defaultIsVisible={true} defaultIsVisible={true}
> >
<LogMonitor theme="tomorrow" /> <LogMonitor theme="tomorrow" />
</DockMonitor>, </DockMonitor>
); );
export default DevTools; export default DevTools;
@ -88,7 +88,7 @@ const enhancer = compose(
// Middleware you want to use in development: // Middleware you want to use in development:
applyMiddleware(d1, d2, d3), applyMiddleware(d1, d2, d3),
// Required! Enable Redux DevTools with the monitors you chose // Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(), DevTools.instrument()
); );
export default function configureStore(initialState) { export default function configureStore(initialState) {
@ -100,8 +100,8 @@ export default function configureStore(initialState) {
if (module.hot) { if (module.hot) {
module.hot.accept('../reducers', () => module.hot.accept('../reducers', () =>
store.replaceReducer( store.replaceReducer(
require('../reducers') /*.default if you use Babel 6+ */, require('../reducers') /*.default if you use Babel 6+ */
), )
); );
} }
@ -121,7 +121,7 @@ const enhancer = compose(
// Required! Enable Redux DevTools with the monitors you chose // Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(), DevTools.instrument(),
// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions // Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions
persistState(getDebugSessionKey()), persistState(getDebugSessionKey())
); );
function getDebugSessionKey() { function getDebugSessionKey() {
@ -200,7 +200,7 @@ const enhancer = compose(
// Required! Enable Redux DevTools with the monitors you chose // Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(), DevTools.instrument(),
// Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions // Optional. Lets you write ?debug_session=<key> in address bar to persist debug sessions
persistState(getDebugSessionKey()), persistState(getDebugSessionKey())
); );
function getDebugSessionKey() { function getDebugSessionKey() {
@ -219,8 +219,8 @@ export default function configureStore(initialState) {
if (module.hot) { if (module.hot) {
module.hot.accept('../reducers', () => module.hot.accept('../reducers', () =>
store.replaceReducer( store.replaceReducer(
require('../reducers') /*.default if you use Babel 6+ */, require('../reducers') /*.default if you use Babel 6+ */
), )
); );
} }
@ -333,7 +333,7 @@ render(
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider>, </Provider>,
document.getElementById('root'), document.getElementById('root')
); );
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
@ -353,7 +353,7 @@ export default function showDevTools(store) {
const popup = window.open( const popup = window.open(
null, null,
'Redux DevTools', 'Redux DevTools',
'menubar=no,location=no,resizable=yes,scrollbars=no,status=no', 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no'
); );
// Reload in case it already exists // Reload in case it already exists
popup.location.reload(); popup.location.reload();
@ -362,7 +362,7 @@ export default function showDevTools(store) {
popup.document.write('<div id="react-devtools-root"></div>'); popup.document.write('<div id="react-devtools-root"></div>');
render( render(
<DevTools store={store} />, <DevTools store={store} />,
popup.document.getElementById('react-devtools-root'), popup.document.getElementById('react-devtools-root')
); );
}, 10); }, 10);
} }

View File

@ -1,5 +0,0 @@
import { defineConfig } from 'eslint/config';
import eslint from '@eslint/js';
import eslintConfigPrettier from 'eslint-config-prettier';
export default defineConfig([eslint.configs.recommended, eslintConfigPrettier]);

View File

@ -1,41 +0,0 @@
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,
},
]);

View File

@ -1,57 +0,0 @@
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',
},
},
]);

View File

@ -1,66 +0,0 @@
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',
},
},
]);

View File

@ -1,88 +0,0 @@
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',
},
},
]);

View File

@ -1,84 +0,0 @@
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',
},
},
]);

3
eslintrc.js.base.json Normal file
View File

@ -0,0 +1,3 @@
{
"parser": "@babel/eslint-parser"
}

17
eslintrc.ts.base.json Normal file
View File

@ -0,0 +1,17 @@
{
"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"
}
}

View File

@ -0,0 +1,18 @@
{
"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"
}
}

View File

@ -0,0 +1,29 @@
{
"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"
}
}

View File

@ -0,0 +1,20 @@
{
"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"
}
}

7
extension/.babelrc Normal file
View File

@ -0,0 +1,7 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]
}

8
extension/.bookignore Normal file
View File

@ -0,0 +1,8 @@
src/
build/
dev/
examples/
npm-package/
test/
package.json
webpack/

7
extension/.eslintignore Normal file
View File

@ -0,0 +1,7 @@
node_modules
build
dev
webpack/replace
examples
npm-package
_book

31
extension/.eslintrc Normal file
View File

@ -0,0 +1,31 @@
{
"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"]
}

16
extension/.gitignore vendored
View File

@ -1,2 +1,16 @@
node_modules node_modules
dist npm-debug.log
.DS_Store
.idea/
dist/
build/
dev/
tmp/
_book
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*

View File

@ -1,265 +1,4 @@
# remotedev-redux-devtools-extension # Change Log
## 3.2.12 This project adheres to [Semantic Versioning](http://semver.org/).
Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/zalmoxisus/redux-devtools-extension/releases) page.
### 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
### Patch Changes
- d18525b5: Increase min-width of popup
- Updated dependencies [57751ff9]
- @redux-devtools/app@3.0.0
## 3.1.0
### Minor Changes
- d54adb76: Option to sort State Tree keys alphabetically
Option to disable collapsing of object keys
### Patch Changes
- @redux-devtools/app@2.2.2
## 3.0.19
### Patch Changes
- 450cde6e: Fix responsive layout
## 3.0.18
### Patch Changes
- Updated dependencies [81926f32]
- react-json-tree@0.18.0
- @redux-devtools/app@2.2.1
## 3.0.17
### Patch Changes
- 1aa6c4f7: Fix remounting root for devpanel
## 3.0.16
### Patch Changes
- 20ebf725: Remove source map from page wrap bundle
## 3.0.14
### Patch Changes
- 24f60a7a: bump min popup window width to 760px #1126 #1129
## 3.0.13
### Patch Changes
- Updated dependencies [8a7eae4]
- react-json-tree@0.17.0
- @redux-devtools/app@2.2.0
- @redux-devtools/slider-monitor@4.0.0
- @redux-devtools/ui@1.3.0
- @redux-devtools/core@3.13.0
- @redux-devtools/utils@2.0.0
## 3.0.12
### Patch Changes
- Updated dependencies [4891bf6]
- @redux-devtools/core@3.12.0
- @redux-devtools/slider-monitor@3.1.2
- @redux-devtools/utils@1.2.1
- @redux-devtools/app@2.1.4
## 3.0.11
### Patch Changes
- ab3c0e2: Avoid persisting the selected action index between sessions
- Updated dependencies [ab3c0e2]
- Updated dependencies [4c9a890]
- @redux-devtools/app@2.1.3
- react-json-tree@0.16.2
## 3.0.10
### Patch Changes
- 55cc37e: Fix filter to show state-controlled search value
- Updated dependencies [55cc37e]
- @redux-devtools/app@2.1.2

View File

@ -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/docs/extensions/get-started/tutorial/hello-world#load-unpacked) `./build/extension`; - 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 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`. - 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`.
### 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-devtools-extension-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;
@ -105,7 +105,7 @@ const composeEnhancers =
: compose; : compose;
const enhancer = composeEnhancers( const enhancer = composeEnhancers(
applyMiddleware(...middleware), applyMiddleware(...middleware)
// other store enhancers if any // other store enhancers if any
); );
const store = createStore(reducer, enhancer); const store = createStore(reducer, enhancer);
@ -113,26 +113,26 @@ const store = createStore(reducer, enhancer);
> [See the post for more details](https://medium.com/@zalmoxis/improve-your-development-workflow-with-redux-devtools-extension-f0379227ff83). > [See the post for more details](https://medium.com/@zalmoxis/improve-your-development-workflow-with-redux-devtools-extension-f0379227ff83).
### 1.3 Use `@redux-devtools/extension` package from npm ### 1.3 Use `redux-devtools-extension` package from npm
To make things easier, there's an npm package to install: To make things easier, there's an npm package to install:
``` ```
npm install --save @redux-devtools/extension npm install --save redux-devtools-extension
``` ```
and to use like so: and to use like so:
```js ```js
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension'; import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore( const store = createStore(
reducer, reducer,
composeWithDevTools( composeWithDevTools(
applyMiddleware(...middleware), applyMiddleware(...middleware)
// other store enhancers if any // other store enhancers if any
), )
); );
``` ```
@ -140,7 +140,7 @@ To specify [extensions options](https://github.com/zalmoxisus/redux-devtools-
```js ```js
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from '@redux-devtools/extension'; import { composeWithDevTools } from 'redux-devtools-extension';
const composeEnhancers = composeWithDevTools({ const composeEnhancers = composeWithDevTools({
// Specify name here, actionsDenylist, actionsCreators and other options if needed // Specify name here, actionsDenylist, actionsCreators and other options if needed
@ -148,23 +148,23 @@ const composeEnhancers = composeWithDevTools({
const store = createStore( const store = createStore(
reducer, reducer,
/* preloadedState, */ composeEnhancers( /* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware), applyMiddleware(...middleware)
// other store enhancers if any // other store enhancers if any
), )
); );
``` ```
> There are just a [few lines of code](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/index.js) added to your bundle. > Therere just [few lines of code](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/index.js) added to your bundle.
In case you don't include other enhancers and middlewares, just use `devToolsEnhancer`: In case you don't include other enhancers and middlewares, just use `devToolsEnhancer`:
```js ```js
import { createStore } from 'redux'; import { createStore } from 'redux';
import { devToolsEnhancer } from '@redux-devtools/extension'; import { devToolsEnhancer } from 'redux-devtools-extension';
const store = createStore( const store = createStore(
reducer, reducer,
/* preloadedState, */ devToolsEnhancer(), /* preloadedState, */ devToolsEnhancer()
// Specify name here, actionsDenylist, actionsCreators and other options if needed // Specify name here, actionsDenylist, actionsCreators and other options if needed
); );
``` ```
@ -173,15 +173,15 @@ const store = createStore(
It's useful to include the extension in production as well. Usually you [can use it for development](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f). It's useful to include the extension in production as well. Usually you [can use it for development](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
If you want to restrict it there, use `composeWithDevToolsLogOnlyInProduction` or `devToolsEnhancerLogOnlyInProduction`: If you want to restrict it there, use `redux-devtools-extension/logOnlyInProduction`:
```js ```js
import { createStore } from 'redux'; import { createStore } from 'redux';
import { devToolsEnhancerLogOnlyInProduction } from '@redux-devtools/extension'; import { devToolsEnhancer } from 'redux-devtools-extension/logOnlyInProduction';
const store = createStore( const store = createStore(
reducer, reducer,
/* preloadedState, */ devToolsEnhancerLogOnlyInProduction(), /* preloadedState, */ devToolsEnhancer()
// options like actionSanitizer, stateSanitizer // options like actionSanitizer, stateSanitizer
); );
``` ```
@ -190,25 +190,25 @@ or with middlewares and enhancers:
```js ```js
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
import { composeWithDevToolsLogOnlyInProduction } from '@redux-devtools/extension'; import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
const composeEnhancers = composeWithDevToolsLogOnlyInProduction({ const composeEnhancers = composeWithDevTools({
// options like actionSanitizer, stateSanitizer // options like actionSanitizer, stateSanitizer
}); });
const store = createStore( const store = createStore(
reducer, reducer,
/* preloadedState, */ composeEnhancers( /* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware), applyMiddleware(...middleware)
// other store enhancers if any // other store enhancers if any
), )
); );
``` ```
> You'll have to add `'process.env.NODE_ENV': JSON.stringify('production')` in your Webpack config for the production bundle ([to envify](https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md#exclude-devtools-from-production-builds)). If you use `create-react-app`, [it already does it for you.](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/webpack.config.prod.js#L253-L257) > You'll have to add `'process.env.NODE_ENV': JSON.stringify('production')` in your Webpack config for the production bundle ([to envify](https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md#exclude-devtools-from-production-builds)). If you use `create-react-app`, [it already does it for you.](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/webpack.config.prod.js#L253-L257)
If you're already checking `process.env.NODE_ENV` when creating the store, import `composeWithDevToolsLogOnly` or `devToolsEnhancerLogOnly` for production environment. If you're already checking `process.env.NODE_ENV` when creating the store, include `redux-devtools-extension/logOnly` for production environment.
If you dont want to allow the extension in production, just use `composeWithDevToolsDevelopmentOnly` or `devToolsEnhancerDevelopmentOnly`. If you dont want to allow the extension in production, just use `redux-devtools-extension/developmentOnly`.
> See [the article](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f) for more details. > See [the article](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f) for more details.
@ -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)

18
extension/appveyor.yml Normal file
View File

@ -0,0 +1,18 @@
environment:
matrix:
- nodejs_version: '6'
cache:
- '%LOCALAPPDATA%/Yarn'
- node_modules
install:
- ps: Install-Product node $env:nodejs_version
- yarn install
test_script:
- node --version
- yarn --version
- yarn test
build: off

18
extension/book.json Normal file
View File

@ -0,0 +1,18 @@
{
"gitbook": "3.2.2",
"title": "Redux DevTools Extension",
"plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs"],
"pluginsConfig": {
"edit-link": {
"base": "https://github.com/zalmoxisus/redux-devtools-extension/tree/master",
"label": "Edit This Page"
},
"github": {
"url": "https://github.com/zalmoxisus/redux-devtools-extension/"
},
"sharing": {
"facebook": true,
"twitter": true
}
}
}

View File

@ -1,55 +0,0 @@
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');

View File

@ -5,18 +5,18 @@ Use with
- `window.__REDUX_DEVTOOLS_EXTENSION__([options])` - `window.__REDUX_DEVTOOLS_EXTENSION__([options])`
- `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__([options])()` - `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__([options])()`
- `window.__REDUX_DEVTOOLS_EXTENSION__.connect([options])` - `window.__REDUX_DEVTOOLS_EXTENSION__.connect([options])`
- `@redux-devtools/extension` npm package: - `redux-devtools-extension` npm package:
```js ```js
import { composeWithDevTools } from '@redux-devtools/extension'; import { composeWithDevTools } from 'redux-devtools-extension';
const composeEnhancers = composeWithDevTools(options); const composeEnhancers = composeWithDevTools(options);
const store = createStore( const store = createStore(
reducer, reducer,
/* preloadedState, */ composeEnhancers( /* preloadedState, */ composeEnhancers(
applyMiddleware(...middleware), applyMiddleware(...middleware)
// other store enhancers if any // other store enhancers if any
), )
); );
``` ```
@ -51,6 +51,7 @@ _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.
@ -69,7 +70,7 @@ _boolean_ or _object_ which contains:
}, },
}, },
}, },
}), })
); );
``` ```
@ -86,7 +87,7 @@ _boolean_ or _object_ which contains:
replacer: (key, value) => replacer: (key, value) =>
value && mori.isMap(value) ? mori.toJs(value) : value, value && mori.isMap(value) ? mori.toJs(value) : value,
}, },
}), })
); );
``` ```
@ -108,7 +109,7 @@ _boolean_ or _object_ which contains:
} }
}, },
}, },
}), })
); );
``` ```
@ -133,7 +134,7 @@ _boolean_ or _object_ which contains:
} }
}, },
}, },
}), })
); );
``` ```
@ -173,7 +174,7 @@ _boolean_ or _object_ which contains:
immutable: Immutable, immutable: Immutable,
refs: [ABRecord], refs: [ABRecord],
}, },
}), })
); );
``` ```
@ -184,7 +185,7 @@ In the example bellow it will always send `{ component: '[React]' }`, regardless
```js ```js
function component( function component(
state = { component: null, toJSON: () => ({ component: '[React]' }) }, state = { component: null, toJSON: () => ({ component: '[React]' }) },
action, action
) { ) {
switch (action.type) { switch (action.type) {
case 'ADD_COMPONENT': case 'ADD_COMPONENT':
@ -205,7 +206,7 @@ function counter(
return { conter: this.count * 10 }; return { conter: this.count * 10 };
}, },
}, },
action, action
) { ) {
switch (action.type) { switch (action.type) {
case 'INCREMENT': case 'INCREMENT':
@ -235,7 +236,7 @@ const store = createStore(
actionSanitizer, actionSanitizer,
stateSanitizer: (state) => stateSanitizer: (state) =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state, state.data ? { ...state, data: '<<LONG_BLOB>>' } : state,
}), })
); );
``` ```
@ -253,7 +254,7 @@ createStore(
actionsDenylist: 'SOME_ACTION', actionsDenylist: 'SOME_ACTION',
// or actionsDenylist: ['SOME_ACTION', 'SOME_OTHER_ACTION'] // or actionsDenylist: ['SOME_ACTION', 'SOME_OTHER_ACTION']
// or just actionsDenylist: 'SOME_' to omit both // or just actionsDenylist: 'SOME_' to omit both
}), })
); );
``` ```
@ -269,7 +270,7 @@ const store = createStore(
window.__REDUX_DEVTOOLS_EXTENSION__({ window.__REDUX_DEVTOOLS_EXTENSION__({
predicate: (state, action) => predicate: (state, action) =>
state.dev.logLevel === VERBOSE && !action.forwarded, state.dev.logLevel === VERBOSE && !action.forwarded,
}), })
); );
``` ```

View File

@ -42,7 +42,7 @@ devTools.init({ value: 'initial state' });
devTools.send('change state', { value: 'state changed' }); devTools.send('change state', { value: 'state changed' });
``` ```
See [redux enhancer's example](https://github.com/reduxjs/redux-devtools/blob/main/packages/redux-devtools-extension/src/logOnly.ts), [react example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/react-counter-messaging/components/Counter.js) and [blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details. See [redux enhancer's example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/logOnly.js), [react example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/react-counter-messaging/components/Counter.js) and [blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details.
### disconnect() ### disconnect()

View File

@ -1,37 +0,0 @@
# 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.

View File

@ -32,7 +32,7 @@ import { inspectProps } from 'react-inspect-props';
compose( compose(
withState('count', 'setCount', 0), withState('count', 'setCount', 0),
inspectProps('Counter inspector'), inspectProps('Counter inspector')
)(Counter); )(Counter);
``` ```
@ -167,7 +167,7 @@ run(App, {
{ id: newId(), num: 0 }, { id: newId(), num: 0 },
{ id: newId(), num: 0 }, { id: newId(), num: 0 },
], ],
}), })
), ),
}); });
``` ```

View File

@ -2,7 +2,7 @@
### Using in a typescript project ### Using in a typescript project
The recommended way is to use [`@redux-devtools/extension` npm package](/README.md#13-use-redux-devtools-extension-package-from-npm), which contains all typescript definitions. Or you can just use `window as any`: The recommended way is to use [`redux-devtools-extension` npm package](/README.md#13-use-redux-devtools-extension-package-from-npm), which contains all typescript definitions. Or you can just use `window as any`:
```js ```js
const store = createStore( const store = createStore(
@ -15,7 +15,7 @@ const store = createStore(
Note that you many need to set `no-any` to false in your `tslint.json` file. Note that you many need to set `no-any` to false in your `tslint.json` file.
Alternatively you can use type-guard in order to avoid Alternatively you can use typeguard in order to avoid
casting to any. casting to any.
```typescript ```typescript
@ -28,7 +28,7 @@ type WindowWithDevTools = Window & {
}; };
const isReduxDevtoolsExtenstionExist = ( const isReduxDevtoolsExtenstionExist = (
arg: Window | WindowWithDevTools, arg: Window | WindowWithDevTools
): arg is WindowWithDevTools => { ): arg is WindowWithDevTools => {
return '__REDUX_DEVTOOLS_EXTENSION__' in arg; return '__REDUX_DEVTOOLS_EXTENSION__' in arg;
}; };
@ -40,7 +40,7 @@ const store = createStore(
initialState, initialState,
isReduxDevtoolsExtenstionExist(window) isReduxDevtoolsExtenstionExist(window)
? window.__REDUX_DEVTOOLS_EXTENSION__() ? window.__REDUX_DEVTOOLS_EXTENSION__()
: undefined, : undefined
); );
``` ```
@ -54,25 +54,25 @@ The extension is not sharing `store` object, so you should take care of that.
### Applying multiple times with different sets of options ### Applying multiple times with different sets of options
We're [not allowing that from instrumentation part](https://github.com/reduxjs/redux-devtools/blob/main/packages/redux-devtools-extension/src/logOnly.ts), which can be used it like so: We're [not allowing that from instrumentation part](https://github.com/zalmoxisus/redux-devtools-instrument/blob/master/src/instrument.js#L676), because that would re-dispatch every app action in case we'd have many liftedStores, but there's [a helper for logging only](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/logOnly.js), which can be used it like so:
```js ```js
import { createStore, compose } from 'redux'; import { createStore, compose } from 'redux';
import { devToolsEnhancerLogOnly } from '@redux-devtools/extension'; import { devToolsEnhancer } from 'redux-devtools-extension/logOnly';
const store = createStore( const store = createStore(
reducer, reducer,
/* preloadedState, */ compose( /* preloadedState, */ compose(
devToolsEnhancerLogOnly({ devToolsEnhancer({
instaceID: 1, instaceID: 1,
name: 'Denylisted', name: 'Denylisted',
actionsDenylist: '...', actionsDenylist: '...',
}), }),
devToolsEnhancerLogOnly({ devToolsEnhancer({
instaceID: 2, instaceID: 2,
name: 'Allowlisted', name: 'Allowlisted',
actionsAllowlist: '...', actionsAllowlist: '...',
}), })
), )
); );
``` ```

View File

@ -16,8 +16,6 @@ If you develop on your local filesystem, make sure to allow Redux DevTools acces
Most likely you mutate the state. Check it by [adding `redux-immutable-state-invariant` middleware](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/counter/store/configureStore.js#L3). Most likely you mutate the state. Check it by [adding `redux-immutable-state-invariant` middleware](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/counter/store/configureStore.js#L3).
Another cause could be that you are creating multiple stores, which means that the devtools get attached to one but the application uses another. See [https://github.com/reduxjs/redux-toolkit/issues/2753](this issue).
### @@INIT or REPLACE action resets the state of the app or last actions RE-APPLIED ### @@INIT or REPLACE action resets the state of the app or last actions RE-APPLIED
`@@redux/REPLACE` (or `@@INIT`) is used internally when the application is hot reloaded. When you use `store.replaceReducer` the effect will be the same as for hot-reloading, where the extension is recomputing all the history again. To avoid that set [`shouldHotReload`](/docs/API/Arguments.md#shouldhotreload) parameter to `false`. `@@redux/REPLACE` (or `@@INIT`) is used internally when the application is hot reloaded. When you use `store.replaceReducer` the effect will be the same as for hot-reloading, where the extension is recomputing all the history again. To avoid that set [`shouldHotReload`](/docs/API/Arguments.md#shouldhotreload) parameter to `false`.
@ -35,8 +33,8 @@ const store = createStore(
window.__REDUX_DEVTOOLS_EXTENSION__ window.__REDUX_DEVTOOLS_EXTENSION__
? window.__REDUX_DEVTOOLS_EXTENSION__() ? window.__REDUX_DEVTOOLS_EXTENSION__()
: (noop) => noop, : (noop) => noop,
batchedSubscribe(/* ... */), batchedSubscribe(/* ... */)
), )
); );
``` ```
@ -60,7 +58,7 @@ const store = createStore(
actionSanitizer, actionSanitizer,
stateSanitizer: (state) => stateSanitizer: (state) =>
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state, state.data ? { ...state, data: '<<LONG_BLOB>>' } : state,
}), })
); );
``` ```
@ -124,7 +122,7 @@ const store = Redux.createStore(
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__({ window.__REDUX_DEVTOOLS_EXTENSION__({
serialize: true, serialize: true,
}), })
); );
``` ```

View File

@ -1,62 +0,0 @@
{
"version": "3.2.10",
"name": "Redux DevTools",
"description": "Redux DevTools for debugging application's state changes.",
"homepage_url": "https://github.com/reduxjs/redux-devtools",
"manifest_version": 3,
"action": {
"default_icon": "img/logo/gray.png",
"default_title": "Redux DevTools",
"default_popup": "devpanel.html#popup"
},
"commands": {
"devtools-window": {
"description": "DevTools window"
},
"devtools-remote": {
"description": "Remote DevTools"
},
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+E"
}
}
},
"icons": {
"16": "img/logo/16x16.png",
"48": "img/logo/48x48.png",
"128": "img/logo/128x128.png"
},
"options_ui": {
"page": "options.html"
},
"background": {
"service_worker": "background.bundle.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"exclude_globs": ["https://www.google*"],
"js": ["content.bundle.js"],
"run_at": "document_start",
"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",
"externally_connectable": {
"ids": ["*"]
},
"permissions": ["notifications", "contextMenus", "storage"],
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
}
}

View File

@ -1,39 +0,0 @@
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,
},
},
},
];

View File

@ -0,0 +1,36 @@
/**
* 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');
}
}
}

View File

@ -0,0 +1,3 @@
{
"presets": ["es2015", "stage-0", "react"]
}

View File

@ -0,0 +1,37 @@
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);
};
}

View File

@ -0,0 +1,27 @@
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;

View File

@ -0,0 +1,16 @@
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);

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Redux counter example</title>
</head>
<body>
<div id="root"></div>
<script src="/static/bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,14 @@
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')
);

View File

@ -0,0 +1,41 @@
{
"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"
}
}

View File

@ -0,0 +1,12 @@
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;
}
}

View File

@ -0,0 +1,8 @@
import { combineReducers } from 'redux';
import counter from './counter';
const rootReducer = combineReducers({
counter,
});
export default rootReducer;

View File

@ -0,0 +1,32 @@
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
);
}
});

View File

@ -0,0 +1,28 @@
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;
}

View File

@ -0,0 +1,73 @@
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));
});
});

View File

@ -0,0 +1,53 @@
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();
});
});

View File

@ -0,0 +1,53 @@
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/);
});
});
});

View File

@ -0,0 +1,23 @@
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);
});
});
});

View File

@ -0,0 +1,5 @@
import { jsdom } from 'jsdom';
global.document = jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = global.window.navigator;

View File

@ -0,0 +1,23 @@
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/,
},
],
},
};

View File

@ -0,0 +1,3 @@
{
"presets": ["es2015", "stage-0", "react"]
}

View File

@ -0,0 +1,62 @@
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;

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>React counter example</title>
</head>
<body>
<div id="root"></div>
<script src="/static/bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,5 @@
import React from 'react';
import { render } from 'react-dom';
import Counter from './components/Counter';
render(<Counter />, document.getElementById('root'));

View File

@ -0,0 +1,33 @@
{
"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"
}
}

View File

@ -0,0 +1,25 @@
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,
},
};

View File

@ -0,0 +1,4 @@
{
"presets": ["es2015", "stage-0", "react"],
"plugins": ["add-module-exports", "transform-decorators-legacy"]
}

View File

@ -0,0 +1,25 @@
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 };
}

View File

@ -0,0 +1,76 @@
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;

View File

@ -0,0 +1,30 @@
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;

View File

@ -0,0 +1,94 @@
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;

View File

@ -0,0 +1,75 @@
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;

View File

@ -0,0 +1,59 @@
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;

View File

@ -0,0 +1,6 @@
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';

View File

@ -0,0 +1,3 @@
export const SHOW_ALL = 'show_all';
export const SHOW_COMPLETED = 'show_completed';
export const SHOW_ACTIVE = 'show_active';

View File

@ -0,0 +1,38 @@
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);

View File

@ -0,0 +1,21 @@
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;

View File

@ -0,0 +1,68 @@
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;

View File

@ -0,0 +1,10 @@
<!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>

View File

@ -0,0 +1,16 @@
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')
);

View File

@ -0,0 +1,53 @@
{
"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"
}
}

View File

@ -0,0 +1,10 @@
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import todos from './todos';
const rootReducer = combineReducers({
todos,
router: routerStateReducer,
});
export default rootReducer;

View File

@ -0,0 +1,61 @@
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;
}
}

View File

@ -0,0 +1,32 @@
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
);
}
});

View File

@ -0,0 +1,28 @@
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;
}

View File

@ -0,0 +1,46 @@
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,
});
});
});

View File

@ -0,0 +1,108 @@
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();
});
});
});

View File

@ -0,0 +1,50 @@
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);
});
});
});

View File

@ -0,0 +1,150 @@
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]);
});
});
});
});

View File

@ -0,0 +1,118 @@
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('');
});
});
});

View File

@ -0,0 +1,84 @@
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);
});
});
});

Some files were not shown because too many files have changed in this diff Show More