├── .babelrc
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── jest.config.js
├── jest.mongodb.environment.js
├── jest.mongodb.setup.js
├── jest.mongodb.teardown.js
├── jest.setup.js
├── package.json
├── src
├── App.jsx
├── __tests__
│ ├── CheckboxWithLabelComponent.test.jsx
│ ├── FileSummarizer.test.js
│ ├── __snapshots__
│ │ ├── jest_snap_property.test.js.snap
│ │ └── react_link.test.jsx.snap
│ ├── jest_advance_timer_game.test.js
│ ├── jest_async_await.test.js
│ ├── jest_async_callback.test.js
│ ├── jest_async_promise.test.js
│ ├── jest_common.test.js
│ ├── jest_infinite_timer_game.test.js
│ ├── jest_mock_names.test.js
│ ├── jest_mock_return_values.test.js
│ ├── jest_mongodb.test.js
│ ├── jest_run_all_timer.test.js
│ ├── jest_setup_describe.js
│ ├── jest_setup_describe_diff.js
│ ├── jest_setup_describe_order.js
│ ├── jest_setup_each_moretime.test.js
│ ├── jest_setup_each_onetime.test.js
│ ├── jest_setup_test_only.js
│ ├── jest_snap_property.test.js
│ ├── jest_sound_player.test.js
│ ├── jest_sound_player_2.test.js
│ ├── jest_sound_player_3.test.js
│ ├── jest_sound_player_4.test.js
│ ├── jest_timer_game.test.js
│ ├── jest_use_mock_function.test.js
│ ├── jest_use_mock_function_property.test.js
│ ├── react_link.test.jsx
│ ├── sum.test.js
│ ├── useMatchMedia.test.js
│ ├── user.test.js
│ ├── user_async.test.js
│ ├── user_async_await.test.js
│ ├── user_async_await_error.test.js
│ └── user_async_await_rejects.test.js
├── actions
│ ├── counter.js
│ └── index.js
├── components
│ ├── AboutComponent.jsx
│ ├── AppComponent.jsx
│ ├── CheckboxWithLabelComponent.jsx
│ ├── CounterComponent.jsx
│ ├── HomeComponent.jsx
│ ├── Link.react.jsx
│ ├── LoadingComponent.jsx
│ ├── RoleComponent.jsx
│ ├── TopicComponent.jsx
│ └── TopicsComponent.jsx
├── css
│ ├── CounterComponent.css
│ └── main.css
├── index.jsx
├── lib
│ ├── FileSummarizer.js
│ ├── __mocks__
│ │ ├── fs.js
│ │ ├── request.js
│ │ └── sound-player.js
│ ├── infiniteTimerGame.js
│ ├── matchMedia.mock.js
│ ├── request.js
│ ├── sound-player-consumer.js
│ ├── sound-player.js
│ ├── sum.js
│ ├── task.js
│ ├── timerGame.js
│ ├── useMatchMedia.js
│ └── user.js
├── reducers
│ ├── counter.js
│ └── index.js
└── routes.jsx
├── webpack.common.js
├── webpack.config.js
├── webpack.dev.js
└── webpack.prod.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-async-to-generator",
4 | "transform-strict-mode",
5 | "transform-object-assign",
6 | "transform-decorators-legacy",
7 | "react-hot-loader/babel",
8 | "syntax-dynamic-import"
9 | ],
10 | "presets": [
11 | "react",
12 | "stage-0",
13 | "env"
14 | ]
15 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "es6": true,
6 | "jquery": true,
7 | "jest": true
8 | },
9 | "parser": "babel-eslint",
10 | "plugins": [
11 | "react",
12 | "html"
13 | ],
14 | "extends": [
15 | "airbnb"
16 | ],
17 | "rules": {
18 | "no-underscore-dangle": 0,
19 | "import/no-extraneous-dependencies": ["error", {
20 | "devDependencies": true
21 | }]
22 |
23 | },
24 | "globals": {
25 | "Actions": true
26 | },
27 |
28 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 | package-lock.json
63 |
64 | dist
65 | globalConfig.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 durban zhang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webpack4-react16-reactrouter-demo
2 | webpack4-react16-reactrouter-demo
3 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React + ReactRouter Demo
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | setupFiles: ['./jest.setup.js'],
3 | snapshotSerializers: ['enzyme-to-json/serializer'],
4 | globalSetup: './jest.mongodb.setup.js',
5 | globalTeardown: './jest.mongodb.teardown.js',
6 | testEnvironment: './jest.mongodb.environment.js',
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/jest.mongodb.environment.js:
--------------------------------------------------------------------------------
1 | const NodeEnvironment = require('jest-environment-node');
2 | const path = require('path');
3 | const fs = require('fs');
4 |
5 | const globalConfigPath = path.join(__dirname, 'globalConfig.json');
6 |
7 | class MongoEnvironment extends NodeEnvironment {
8 | constructor(config) {
9 | super(config);
10 | }
11 |
12 | async setup() {
13 | console.log('设置MongoDB测试环境');
14 |
15 | const globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, 'utf-8'));
16 |
17 | this.global.__MONGO_URI__ = globalConfig.mongoUri;
18 | this.global.__MONGO_DB_NAME__ = globalConfig.mongoDBName;
19 |
20 | await super.setup();
21 | }
22 |
23 | async teardown() {
24 | console.log('卸载MongoDB测试环境');
25 |
26 | await super.teardown();
27 | }
28 |
29 | runScript(script) {
30 | return super.runScript(script);
31 | }
32 | }
33 |
34 | module.exports = MongoEnvironment;
35 |
--------------------------------------------------------------------------------
/jest.mongodb.setup.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const MongodbMemoryServer = require('mongodb-memory-server');
4 |
5 | const globalConfigPath = path.join(__dirname, 'globalConfig.json');
6 | const mongoServer = new MongodbMemoryServer.MongoMemoryServer();
7 |
8 | module.exports = async function setupMongodb() {
9 | console.log('配置Jest Setup调用');
10 | const mongoConfig = {
11 | mongoDBName: 'jest',
12 | mongoUri: await mongoServer.getConnectionString(),
13 | };
14 |
15 | // 将配置写入本地配置文件以供所有测试都能调用的到
16 | fs.writeFileSync(globalConfigPath, JSON.stringify(mongoConfig));
17 |
18 | // 设置对mongodb的引用,以便在拆卸期间关闭服务器。
19 | global.__MONGOD__ = mongoServer;
20 | };
21 |
--------------------------------------------------------------------------------
/jest.mongodb.teardown.js:
--------------------------------------------------------------------------------
1 | module.exports = async function tearDownMongodb() {
2 | console.log('配置Jest TearDown调用');
3 | await global.__MONGOD__.stop();
4 | };
5 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | configure({
5 | adapter: new Adapter(),
6 | });
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack4_react16_reactrouter4",
3 | "version": "1.0.0",
4 | "description": "Webpack4 + React16 + ReactRouter4",
5 | "private": true,
6 | "scripts": {
7 | "test": "jest --notify --watchman=false",
8 | "build": "npx webpack --config webpack.prod.js",
9 | "build:package": "NODE_ENV=production npx webpack --config webpack.prod.js",
10 | "start": "npx webpack-dev-server --open --hot --config webpack.dev.js",
11 | "server:run": "npx http-server dist"
12 | },
13 | "keywords": [],
14 | "author": "durban.zhang ",
15 | "license": "ISC",
16 | "dependencies": {
17 | "axios": "^0.18.0",
18 | "connected-react-router": "^4.3.0",
19 | "enzyme-to-json": "^3.3.4",
20 | "history": "^4.7.2",
21 | "prop-types": "^15.6.1",
22 | "react": "^16.4.0",
23 | "react-dom": "^16.4.0",
24 | "react-hot-loader": "^4.3.0",
25 | "react-loadable": "^5.4.0",
26 | "react-redux": "^5.0.7",
27 | "react-router-dom": "^4.3.1",
28 | "redux": "^4.0.0",
29 | "redux-logger": "^3.0.6"
30 | },
31 | "devDependencies": {
32 | "babel-core": "^6.26.3",
33 | "babel-eslint": "^8.2.3",
34 | "babel-jest": "^23.2.0",
35 | "babel-loader": "^7.1.4",
36 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
37 | "babel-plugin-transform-decorators-legacy": "^1.3.5",
38 | "babel-plugin-transform-object-assign": "^6.22.0",
39 | "babel-plugin-transform-strict-mode": "^6.24.1",
40 | "babel-preset-env": "^1.7.0",
41 | "babel-preset-react": "^6.24.1",
42 | "babel-preset-stage-0": "^6.24.1",
43 | "clean-webpack-plugin": "^0.1.19",
44 | "css-loader": "^0.28.11",
45 | "enzyme": "^3.3.0",
46 | "enzyme-adapter-react-16": "^1.1.1",
47 | "eslint": "^4.19.1",
48 | "eslint-config-airbnb": "^16.1.0",
49 | "eslint-plugin-html": "^4.0.3",
50 | "eslint-plugin-import": "^2.12.0",
51 | "eslint-plugin-jsx-a11y": "^6.0.3",
52 | "eslint-plugin-react": "^7.9.1",
53 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
54 | "html-webpack-plugin": "^3.2.0",
55 | "inline-manifest-webpack-plugin": "^4.0.0",
56 | "jest": "^23.1.0",
57 | "jest-changed-files": "^23.2.0",
58 | "jest-diff": "^23.2.0",
59 | "jest-docblock": "^23.2.0",
60 | "jest-environment-node": "^23.4.0",
61 | "jest-get-type": "^22.4.3",
62 | "jest-validate": "^23.2.0",
63 | "jest-worker": "^23.2.0",
64 | "mongodb": "^3.1.1",
65 | "mongodb-memory-server": "^1.9.0",
66 | "pretty-format": "^23.2.0",
67 | "react-test-renderer": "^16.4.1",
68 | "regenerator-runtime": "^0.12.0",
69 | "style-loader": "^0.21.0",
70 | "uglifyjs-webpack-plugin": "^1.2.5",
71 | "url-loader": "^1.0.1",
72 | "webpack": "^4.11.1",
73 | "webpack-bundle-analyzer": "^3.6.1",
74 | "webpack-cli": "^3.0.3",
75 | "webpack-dev-server": "^3.1.4",
76 | "webpack-manifest-plugin": "^2.0.3",
77 | "webpack-merge": "^4.1.2",
78 | "workbox-webpack-plugin": "^3.3.0"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { ConnectedRouter } from 'connected-react-router';
4 | import routes from './routes';
5 |
6 | const App = ({ history }) => (
7 |
8 | {routes}
9 |
10 | );
11 |
12 | App.propTypes = {
13 | history: PropTypes.objectOf(PropTypes.any).isRequired,
14 | };
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/src/__tests__/CheckboxWithLabelComponent.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import CheckboxWithLabelComponent from '../components/CheckboxWithLabelComponent';
4 |
5 | test('CheckboxWithLabelComponent changes the text after click', () => {
6 | // Render a checkbox with label in the document
7 | const checkbox = shallow();
8 |
9 | expect(checkbox.text()).toEqual('Off');
10 |
11 | checkbox.find('input').simulate('change');
12 |
13 | expect(checkbox.text()).toEqual('On');
14 | });
15 |
--------------------------------------------------------------------------------
/src/__tests__/FileSummarizer.test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const FileSummarizer = require('../lib/FileSummarizer');
3 |
4 |
5 | jest.mock('fs');
6 |
7 | describe('listFilesInDirectorySync', () => {
8 | const MOCK_FILE_INFO = {
9 | '/path/to/file1.js': 'console.log("file1 contents");',
10 | '/path/to/file2.txt': 'file2 contents',
11 | };
12 |
13 | beforeEach(() => {
14 | // Set up some mocked out file info before each test
15 | fs.__setMockFiles(MOCK_FILE_INFO);
16 | });
17 |
18 | test('includes all files in the directory in the summary', () => {
19 | const fileSummary = FileSummarizer.summarizeFilesInDirectorySync('/path/to');
20 |
21 | expect(fileSummary.length).toBe(2);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/__tests__/__snapshots__/jest_snap_property.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`检查匹配器并测试通过 1`] = `
4 | Object {
5 | "createAt": Any,
6 | "id": Any,
7 | "name": "Durban",
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/src/__tests__/__snapshots__/react_link.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`正确的渲染 1`] = `
4 |
10 | Walkerfree
11 |
12 | `;
13 |
--------------------------------------------------------------------------------
/src/__tests__/jest_advance_timer_game.test.js:
--------------------------------------------------------------------------------
1 | const timerGame = require('../lib/timerGame');
2 |
3 | jest.useFakeTimers();
4 |
5 | it('1秒钟后通过advanceTimersByTime调用回调函数', () => {
6 | const callback = jest.fn();
7 |
8 | timerGame(callback);
9 |
10 | // callback还没有被执行
11 | expect(callback).not.toBeCalled();
12 |
13 | // 提前1秒钟执行
14 | jest.advanceTimersByTime(1000);
15 |
16 | // 所有的callback被调用
17 | expect(callback).toBeCalled();
18 | expect(callback).toHaveBeenCalledTimes(1);
19 | });
20 |
--------------------------------------------------------------------------------
/src/__tests__/jest_async_await.test.js:
--------------------------------------------------------------------------------
1 | const fetchData = (err) => {
2 | const promise = new Promise((resolve, reject) => {
3 | if (err) {
4 | return reject('error');
5 | }
6 |
7 | return setTimeout(() => resolve('gowhich'), 3000);
8 | });
9 | return promise;
10 | };
11 |
12 |
13 | test('data的数据是"gowhich"', async () => {
14 | const data = await fetchData();
15 | expect(data).toBe('gowhich');
16 | });
17 |
18 | test('获取数据失败', async () => {
19 | try {
20 | await fetchData(true);
21 | } catch (err) {
22 | expect(err).toBe('error');
23 | }
24 | });
25 |
26 | test('data的数据是"gowhich"', async () => {
27 | await expect(fetchData()).resolves.toBe('gowhich');
28 | });
29 |
30 | test('获取数据失败', async () => {
31 | await expect(fetchData(true)).rejects.toMatch('error');
32 | });
33 |
--------------------------------------------------------------------------------
/src/__tests__/jest_async_callback.test.js:
--------------------------------------------------------------------------------
1 | function fetchData(callback) {
2 | setTimeout(() => {
3 | callback('peanut butter');
4 | }, 1000);
5 | }
6 |
7 | test('two section the data is peanut butter', (done) => {
8 | function callback(data) {
9 | // console.log('two section data = ', data);
10 | expect(data).toBe('peanut butter');
11 | done();
12 | }
13 |
14 | fetchData(callback);
15 | });
16 |
--------------------------------------------------------------------------------
/src/__tests__/jest_async_promise.test.js:
--------------------------------------------------------------------------------
1 | const fetchData = (err) => {
2 | const promise = new Promise((resolve, reject) => {
3 | if (err) {
4 | return reject('error');
5 | }
6 |
7 | return setTimeout(() => resolve('peanut butter'), 3000);
8 | });
9 | return promise;
10 | };
11 |
12 | test('the data is peanut butter', () => {
13 | const promise = fetchData().then((data) => {
14 | // console.log('third section data = ', data);
15 | expect(data).toBe('peanut butter');
16 | });
17 |
18 | return promise;
19 | });
20 |
21 | test('the fetch fails with an error', () => {
22 | expect.assertions(1);
23 | const promise = fetchData(true).catch((err) => {
24 | expect(err).toMatch('error');
25 | });
26 |
27 | return promise;
28 | });
29 |
30 | test('the data is peanut butter', () => {
31 | const promise = expect(fetchData()).resolves.toBe('peanut butter');
32 | return promise;
33 | });
34 |
35 | test('the fetch fails with an error', () => {
36 | const promise = expect(fetchData(true)).rejects.toMatch('error');
37 | return promise;
38 | });
39 |
--------------------------------------------------------------------------------
/src/__tests__/jest_common.test.js:
--------------------------------------------------------------------------------
1 | test('two plus two is four', () => {
2 | expect(2 + 2).toBe(4);
3 | });
4 |
5 | test('object assignment', () => {
6 | const data = { one: 1 };
7 | data.two = 2;
8 | expect(data).toEqual({ one: 1, two: 2 });
9 | });
10 |
11 | test('adding positive numbers is not zero', () => {
12 | for (let a = 1; a < 10; a += 1) {
13 | for (let b = 1; b < 10; b += 1) {
14 | expect(a + b).not.toBe(0);
15 | }
16 | }
17 | });
18 |
19 | test('null', () => {
20 | const n = null;
21 | expect(n).toBeNull();
22 | expect(n).toBeDefined();
23 | expect(n).not.toBeUndefined();
24 | expect(n).not.toBeTruthy();
25 | expect(n).toBeFalsy();
26 | });
27 |
28 | test('zero', () => {
29 | const z = 0;
30 | expect(z).not.toBeNull();
31 | expect(z).toBeDefined();
32 | expect(z).not.toBeUndefined();
33 | expect(z).not.toBeTruthy();
34 | expect(z).toBeFalsy();
35 | });
36 |
37 | test('two plus tow', () => {
38 | const value = 2 + 2;
39 | expect(value).toBeGreaterThan(3);
40 | expect(value).toBeGreaterThanOrEqual(3.5);
41 | expect(value).toBeLessThan(5);
42 | expect(value).toBeLessThanOrEqual(4.5);
43 | });
44 |
45 | test('adding floating point numbers', () => {
46 | const value = 0.1 + 0.2;
47 | expect(value).toBeCloseTo(0.3);
48 | });
49 |
50 | test('there is no I in team', () => {
51 | expect('team').not.toMatch(/I/);
52 | });
53 |
54 | test('but there is a "stop" in Christoph', () => {
55 | expect('Christoph').toMatch(/stop/);
56 | });
57 |
58 | test('the shopping list has beer on it', () => {
59 | const shopping = [
60 | 'diapers',
61 | 'kleenex',
62 | 'trash bags',
63 | 'paper towels',
64 | 'beer',
65 | ];
66 |
67 | expect(shopping).toContain('beer');
68 | });
69 |
70 | test('compiling android goes as expected', () => {
71 | function compileAndroidCode() {
72 | throw new Error('you are useing the wrong JDK');
73 | }
74 |
75 | expect(compileAndroidCode).toThrow();
76 | expect(compileAndroidCode).toThrow(Error);
77 |
78 | // 匹配错误信息
79 | expect(compileAndroidCode).toThrow('you are useing the wrong JDK');
80 | expect(compileAndroidCode).toThrow(/JDK/);
81 | });
82 |
--------------------------------------------------------------------------------
/src/__tests__/jest_infinite_timer_game.test.js:
--------------------------------------------------------------------------------
1 | const infiniteTimerGame = require('../lib/infiniteTimerGame');
2 |
3 | jest.useFakeTimers();
4 |
5 | describe('infiniteTimerGame', () => {
6 | test('schedules a 10-second timer after 1 second', () => {
7 | const callback = jest.fn();
8 |
9 | infiniteTimerGame(callback);
10 |
11 | // 在这里,会在意秒钟后执行callback的回调
12 | expect(setTimeout).toHaveBeenCalledTimes(1);
13 | expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
14 |
15 | // 只有当前待定的计时器(但不是在该过程中创建的任何新计时器)
16 | jest.runOnlyPendingTimers();
17 |
18 | // 此时,1秒钟的计时器应该已经被回调了
19 | expect(callback).toBeCalled();
20 |
21 | // 它应该创建一个新的计时器,以便在10秒内启动游戏
22 | expect(setTimeout).toHaveBeenCalledTimes(2);
23 | expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/__tests__/jest_mock_names.test.js:
--------------------------------------------------------------------------------
1 | const myMockFunc = jest
2 | .fn()
3 | .mockReturnValue('default')
4 | .mockImplementation(v => 42 + v)
5 | .mockName('add42');
6 |
7 | test('add 42', () => {
8 | expect(myMockFunc(1)).toEqual(43);
9 | });
10 |
--------------------------------------------------------------------------------
/src/__tests__/jest_mock_return_values.test.js:
--------------------------------------------------------------------------------
1 | const myMock = jest.fn();
2 | console.log(myMock);
3 |
4 | myMock
5 | .mockReturnValueOnce(10)
6 | .mockReturnValueOnce('x')
7 | .mockReturnValue(true);
8 |
9 | console.log(myMock(), myMock(), myMock(), myMock(), myMock());
10 |
11 |
12 | const filterTestFn = jest.fn();
13 |
14 | // 第一次mock返回true,第二次mock返回false
15 | filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
16 |
17 | const result = [11, 12].filter(filterTestFn);
18 |
19 | console.log(result);
20 | // > [11]
21 | console.log(filterTestFn.mock.calls);
22 | // > [ [11], [12] ]
23 |
24 | test('do test', () => {
25 | expect(true).toBe(true);
26 | });
27 |
28 |
--------------------------------------------------------------------------------
/src/__tests__/jest_mongodb.test.js:
--------------------------------------------------------------------------------
1 | const {
2 | MongoClient,
3 | } = require('mongodb');
4 |
5 | let connection;
6 | let db;
7 |
8 | beforeAll(async () => {
9 | connection = await MongoClient.connect(global.__MONGO_URI__, {
10 | useNewUrlParser: true,
11 | });
12 | db = await connection.db(global.__MONGO_DB_NAME__);
13 | });
14 |
15 | afterAll(async () => {
16 | await connection.close();
17 | });
18 |
19 | it('从集合中汇总文档', async () => {
20 | const files = db.collection('files');
21 |
22 | await files.insertMany([{
23 | type: 'Document',
24 | },
25 | {
26 | type: 'Video',
27 | },
28 | {
29 | type: 'Image',
30 | },
31 | {
32 | type: 'Document',
33 | },
34 | {
35 | type: 'Image',
36 | },
37 | {
38 | type: 'Document',
39 | },
40 | ]);
41 |
42 | const topFiles = await files
43 | .aggregate([{
44 | $group: {
45 | _id: '$type',
46 | count: {
47 | $sum: 1,
48 | },
49 | },
50 | },
51 | {
52 | $sort: {
53 | count: -1,
54 | },
55 | },
56 | ])
57 | .toArray();
58 |
59 | expect(topFiles).toEqual([{
60 | _id: 'Document',
61 | count: 3,
62 | },
63 | {
64 | _id: 'Image',
65 | count: 2,
66 | },
67 | {
68 | _id: 'Video',
69 | count: 1,
70 | },
71 | ]);
72 | });
73 |
--------------------------------------------------------------------------------
/src/__tests__/jest_run_all_timer.test.js:
--------------------------------------------------------------------------------
1 | const timerGame = require('../lib/timerGame');
2 |
3 | jest.useFakeTimers();
4 |
5 | test('1分钟后调用回调callback', () => {
6 | const callback = jest.fn();
7 |
8 | timerGame(callback);
9 |
10 | // 在这个时间点上,callback回调函数还没有被调用
11 | expect(callback).not.toBeCalled();
12 |
13 | // 所有timers被执行
14 | jest.runAllTimers();
15 |
16 | // 现在我们的callback回调函数被调用
17 | expect(callback).toBeCalled();
18 | expect(callback).toHaveBeenCalledTimes(1);
19 | });
20 |
--------------------------------------------------------------------------------
/src/__tests__/jest_setup_describe.js:
--------------------------------------------------------------------------------
1 | const citys = [];
2 | const foods = [];
3 | let time1 = 1;
4 | let time2 = 1;
5 | const isCity = (city) => {
6 | if (citys.indexOf(city) > -1) {
7 | return true;
8 | }
9 | return false;
10 | };
11 |
12 | const isCityAndFood = (cityAndFood) => {
13 | let hasCity = false;
14 | let hasFood = false;
15 |
16 | if (citys.indexOf(cityAndFood.city) > -1) {
17 | hasCity = true;
18 | }
19 |
20 | if (foods.indexOf(cityAndFood.food) > -1) {
21 | hasFood = true;
22 | }
23 |
24 | if (hasCity && hasFood) {
25 | return true;
26 | }
27 |
28 | return false;
29 | };
30 | const initCityDatabase = () => new Promise((resolve, reject) => {
31 | let promise;
32 |
33 | try {
34 | setTimeout(() => {
35 | // console.log('initCityDatabase time = ', time1);
36 | if (time1 === 1) {
37 | citys.push('Shanghai');
38 | } else if (time1 === 2) {
39 | citys.push('Chifeng');
40 | }
41 | time1 += 1;
42 | promise = resolve(citys);
43 | }, 1000);
44 | } catch (err) {
45 | return reject(err);
46 | }
47 |
48 | return promise;
49 | });
50 |
51 | const initFoodDatabase = () => new Promise((resolve, reject) => {
52 | let promise;
53 |
54 | try {
55 | setTimeout(() => {
56 | // console.log('initFoodDatabase time = ', time2);
57 | if (time2 === 1) {
58 | foods.push('Banana');
59 | } else if (time2 === 2) {
60 | foods.push('Apple');
61 | }
62 | time2 += 1;
63 | promise = resolve(foods);
64 | }, 1000);
65 | } catch (err) {
66 | return reject(err);
67 | }
68 |
69 | return promise;
70 | });
71 |
72 | beforeEach(() => initCityDatabase());
73 |
74 | test('city database has Shanghai', () => {
75 | expect(isCity('Shanghai')).toBeTruthy();
76 | });
77 |
78 | test('city database has Chifeng', () => {
79 | expect(isCity('Chifeng')).toBeTruthy();
80 | });
81 |
82 | describe('matching cities to foods', () => {
83 | beforeEach(() => initFoodDatabase());
84 |
85 | test('database has Shanghai and Banana', () => {
86 | expect(isCityAndFood({
87 | city: 'Shanghai',
88 | food: 'Banana',
89 | })).toBe(true);
90 | });
91 |
92 | test('database has Chifeng and Apple', () => {
93 | expect(isCityAndFood({
94 | city: 'Chifeng',
95 | food: 'Apple',
96 | })).toBe(true);
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/src/__tests__/jest_setup_describe_diff.js:
--------------------------------------------------------------------------------
1 | beforeAll(() => console.log('1 - beforeAll'));
2 | afterAll(() => console.log('1 - afterAll'));
3 | beforeEach(() => console.log('1 - beforeEach'));
4 | afterEach(() => console.log('1 - afterEach'));
5 | test('', () => console.log('1 - test'));
6 | describe('Scoped / Nested block', () => {
7 | beforeAll(() => console.log('2 - beforeAll'));
8 | afterAll(() => console.log('2 - afterAll'));
9 | beforeEach(() => console.log('2 - beforeEach'));
10 | afterEach(() => console.log('2 - afterEach'));
11 | test('', () => console.log('2 - test'));
12 | });
13 |
--------------------------------------------------------------------------------
/src/__tests__/jest_setup_describe_order.js:
--------------------------------------------------------------------------------
1 | describe('outer', () => {
2 | console.log('describe outer-a');
3 |
4 | describe('describe inner 1', () => {
5 | console.log('describe inner 1');
6 | test('test 1', () => {
7 | console.log('test for describe inner 1');
8 | expect(true).toEqual(true);
9 | });
10 | });
11 |
12 | console.log('describe outer-b');
13 |
14 | test('test 1', () => {
15 | console.log('test for describe outer');
16 | expect(true).toEqual(true);
17 | });
18 |
19 | describe('describe inner 2', () => {
20 | console.log('describe inner 2');
21 | test('test for describe inner 2', () => {
22 | console.log('test for describe inner 2');
23 | expect(false).toEqual(false);
24 | });
25 | });
26 |
27 | console.log('describe outer-c');
28 | });
29 |
--------------------------------------------------------------------------------
/src/__tests__/jest_setup_each_moretime.test.js:
--------------------------------------------------------------------------------
1 | let citys = [];
2 | let time1 = 1;
3 | let time2 = 1;
4 | const isCity = (city) => {
5 | if (citys.indexOf(city) > -1) {
6 | return true;
7 | }
8 | return false;
9 | };
10 |
11 | const initCityDatabase = () => new Promise((resolve, reject) => {
12 | let promise;
13 |
14 | try {
15 | setTimeout(() => {
16 | // console.log('moretime -> init time = ', time1);
17 | if (time1 === 1) {
18 | citys.push('natasha1');
19 | time1 += 1;
20 | } else if (time1 === 2) {
21 | citys.push('natasha2');
22 | time1 += 1;
23 | }
24 | promise = resolve(citys);
25 | }, 1000);
26 | } catch (err) {
27 | return reject(err);
28 | }
29 |
30 | return promise;
31 | });
32 |
33 | const clearCityDatabase = () => new Promise((resolve, reject) => {
34 | let promise;
35 | try {
36 | setTimeout(() => {
37 | // console.log('moretime -> clear time = ', time2);
38 | time2 += 1;
39 | citys = [];
40 | promise = resolve(citys);
41 | }, 1000);
42 | } catch (err) {
43 | return reject(err);
44 | }
45 |
46 | return promise;
47 | });
48 |
49 | beforeEach(() => initCityDatabase());
50 |
51 | afterEach(() => clearCityDatabase());
52 |
53 | test('The city database has natasha1', () => {
54 | expect(isCity('natasha1')).toBeTruthy();
55 | });
56 |
57 | test('The city database has natasha2', () => {
58 | expect(isCity('natasha2')).toBeTruthy();
59 | });
60 |
--------------------------------------------------------------------------------
/src/__tests__/jest_setup_each_onetime.test.js:
--------------------------------------------------------------------------------
1 | let citys = [];
2 | let time1 = 1;
3 | let time2 = 1;
4 | const isCity = (city) => {
5 | if (citys.indexOf(city) > -1) {
6 | return true;
7 | }
8 | return false;
9 | };
10 |
11 | const initCityDatabase = () => new Promise((resolve, reject) => {
12 | let promise;
13 |
14 | try {
15 | setTimeout(() => {
16 | // console.log('onetime -> init time = ', time1);
17 | time1 += 1;
18 | citys.push('natasha1');
19 | citys.push('natasha2');
20 | promise = resolve(citys);
21 | }, 1000);
22 | } catch (err) {
23 | return reject(err);
24 | }
25 |
26 | return promise;
27 | });
28 |
29 | const clearCityDatabase = () => new Promise((resolve, reject) => {
30 | let promise;
31 | try {
32 | // console.log('onetime -> clear time = ', time2);
33 | time2 += 1;
34 | setTimeout(() => {
35 | citys = [];
36 | promise = resolve(citys);
37 | }, 1000);
38 | } catch (err) {
39 | return reject(err);
40 | }
41 |
42 | return promise;
43 | });
44 |
45 | beforeAll(() => initCityDatabase());
46 |
47 | afterAll(() => clearCityDatabase());
48 |
49 | test('The city database has natasha1', () => {
50 | expect(isCity('natasha1')).toBeTruthy();
51 | });
52 |
53 | test('The city database has natasha2', () => {
54 | expect(isCity('natasha2')).toBeTruthy();
55 | });
56 |
--------------------------------------------------------------------------------
/src/__tests__/jest_setup_test_only.js:
--------------------------------------------------------------------------------
1 | test.only('this will be the only test that runs', () => {
2 | expect(true).toBe(true);
3 | });
4 |
5 | test('this test will not run', () => {
6 | expect('A').toBe('A');
7 | });
8 |
--------------------------------------------------------------------------------
/src/__tests__/jest_snap_property.test.js:
--------------------------------------------------------------------------------
1 | it('检查匹配器并测试通过', () => {
2 | const user = {
3 | createAt: new Date(),
4 | id: Math.floor(Math.random() * 20),
5 | name: 'Durban',
6 | };
7 |
8 | expect(user).toMatchSnapshot({
9 | createAt: expect.any(Date),
10 | id: expect.any(Number),
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/__tests__/jest_sound_player.test.js:
--------------------------------------------------------------------------------
1 | import SoundPlayer from '../lib/sound-player';
2 | import SoundPlayerConsumer from '../lib/sound-player-consumer';
3 |
4 | jest.mock('../lib/sound-player'); // SoundPlayer 现在是一个模拟构造函数
5 |
6 | beforeEach(() => {
7 | // 清除所有实例并调用构造函数和所有方法:
8 | SoundPlayer.mockClear();
9 | });
10 |
11 | it('我们可以检查SoundPlayerConsumer是否调用了类构造函数', () => {
12 | const soundPlayerConsumer = new SoundPlayerConsumer();
13 | expect(SoundPlayer).toHaveBeenCalledTimes(1);
14 | });
15 |
16 | it('我们可以检查SoundPlayerConsumer是否在类实例上调用了一个方法', () => {
17 | // 检查 mockClear() 会否起作用:
18 | expect(SoundPlayer).not.toHaveBeenCalled();
19 |
20 | const soundPlayerConsumer = new SoundPlayerConsumer();
21 | // 类构造函数再次被调用
22 | expect(SoundPlayer).toHaveBeenCalledTimes(1);
23 |
24 | const coolSoundFileName = 'song.mp3';
25 | soundPlayerConsumer.play();
26 |
27 | // mock.instances可用于自动模拟
28 | const mockSoundPlayerInstance = SoundPlayer.mock.instances[0];
29 | const mockChoicePlaySoundFile = mockSoundPlayerInstance.choicePlaySoundFile;
30 | expect(mockChoicePlaySoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
31 | // 相当于上面的检查
32 | expect(mockChoicePlaySoundFile).toHaveBeenCalledWith(coolSoundFileName);
33 | expect(mockChoicePlaySoundFile).toHaveBeenCalledTimes(1);
34 | });
35 |
--------------------------------------------------------------------------------
/src/__tests__/jest_sound_player_2.test.js:
--------------------------------------------------------------------------------
1 | import SoundPlayer, { mockChoicePlaySoundFile } from '../lib/sound-player';
2 | import SoundPlayerConsumer from '../lib/sound-player-consumer';
3 |
4 | jest.mock('../lib/sound-player'); // SoundPlayer 现在是一个模拟构造函数
5 |
6 | beforeEach(() => {
7 | // 清除所有实例并调用构造函数和所有方法:
8 | SoundPlayer.mockClear();
9 | mockChoicePlaySoundFile.mockClear();
10 | });
11 |
12 | it('我们可以检查SoundPlayerConsumer是否调用了类构造函数', () => {
13 | const soundPlayerConsumer = new SoundPlayerConsumer();
14 | expect(SoundPlayer).toHaveBeenCalledTimes(1);
15 | });
16 |
17 | it('我们可以检查SoundPlayerConsumer是否在类实例上调用了一个方法', () => {
18 | const soundPlayerConsumer = new SoundPlayerConsumer();
19 | const coolSoundFileName = 'song.mp3';
20 | soundPlayerConsumer.play();
21 | expect(mockChoicePlaySoundFile).toHaveBeenCalledWith(coolSoundFileName);
22 | });
23 |
--------------------------------------------------------------------------------
/src/__tests__/jest_sound_player_3.test.js:
--------------------------------------------------------------------------------
1 | import SoundPlayer from '../lib/sound-player';
2 | import SoundPlayerConsumer from '../lib/sound-player-consumer';
3 |
4 | jest.mock('../lib/sound-player'); // SoundPlayer 现在是一个模拟构造函数
5 |
6 |
7 | const mockPlaySoundFile = jest.fn();
8 | const mockChoicePlaySoundFile = jest.fn();
9 |
10 | jest.mock('../lib/sound-player', () => jest.fn().mockImplementation(() => ({
11 | choicePlaySoundFile: mockChoicePlaySoundFile,
12 | playSoundFile: mockPlaySoundFile,
13 | })));
14 |
15 | beforeEach(() => {
16 | // 清除所有实例并调用构造函数和所有方法:
17 | SoundPlayer.mockClear();
18 | mockChoicePlaySoundFile.mockClear();
19 | });
20 |
21 | it('我们可以检查SoundPlayerConsumer是否调用了类构造函数', () => {
22 | const soundPlayerConsumer = new SoundPlayerConsumer();
23 | expect(SoundPlayer).toHaveBeenCalledTimes(1);
24 | });
25 |
26 | it('我们可以检查SoundPlayerConsumer是否在类实例上调用了一个方法', () => {
27 | const soundPlayerConsumer = new SoundPlayerConsumer();
28 | const coolSoundFileName = 'song.mp3';
29 | soundPlayerConsumer.play();
30 | expect(mockChoicePlaySoundFile).toHaveBeenCalledWith(coolSoundFileName);
31 | });
32 |
--------------------------------------------------------------------------------
/src/__tests__/jest_sound_player_4.test.js:
--------------------------------------------------------------------------------
1 | import SoundPlayer from '../lib/sound-player';
2 | import SoundPlayerConsumer from '../lib/sound-player-consumer';
3 |
4 | jest.mock('../lib/sound-player'); // SoundPlayer 现在是一个模拟构造函数
5 |
6 | describe('SoundPlayer被调用的时候抛出异常', () => {
7 | beforeAll(() => {
8 | SoundPlayer.mockImplementation(() => ({
9 | playSoundFile: () => {
10 | throw new Error('Test error');
11 | },
12 | choicePlaySoundFile: () => {
13 | throw new Error('Test error');
14 | },
15 | }));
16 | });
17 |
18 | it('play被调用的收抛出异常', () => {
19 | const soundPlayerConsumer = new SoundPlayerConsumer();
20 | expect(() => soundPlayerConsumer.play()).toThrow();
21 | });
22 | });
23 |
24 |
--------------------------------------------------------------------------------
/src/__tests__/jest_timer_game.test.js:
--------------------------------------------------------------------------------
1 | const timerGame = require('../lib/timerGame');
2 |
3 | jest.useFakeTimers();
4 |
5 | test('等待1分钟后结束游戏', () => {
6 | timerGame();
7 |
8 | expect(setTimeout).toHaveBeenCalledTimes(1);
9 | expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
10 | });
11 |
--------------------------------------------------------------------------------
/src/__tests__/jest_use_mock_function.test.js:
--------------------------------------------------------------------------------
1 | function forEach(items, callback) {
2 | for (let i = 0; i < items.length; i += 1) {
3 | callback(items[i]);
4 | }
5 | }
6 |
7 | const mockFunction = jest.fn();
8 | forEach([0, 1], mockFunction);
9 |
10 |
11 | test('mockFunction被调用2次', () => {
12 | expect(mockFunction.mock.calls.length).toBe(2);
13 | });
14 |
15 | test('第一次调用第一个参数是0', () => {
16 | expect(mockFunction.mock.calls[0][0]).toBe(0);
17 | });
18 |
19 | test('第一次调用第一个参数是1', () => {
20 | expect(mockFunction.mock.calls[1][0]).toBe(1);
21 | });
22 |
23 |
--------------------------------------------------------------------------------
/src/__tests__/jest_use_mock_function_property.test.js:
--------------------------------------------------------------------------------
1 | const myMock = jest.fn();
2 |
3 | const a = new myMock();
4 | const b = {};
5 | const bound = myMock.bind(b);
6 | bound();
7 |
8 | console.log(myMock.mock.instances);
9 |
10 | test('do test', () => {
11 | expect(true).toBe(true);
12 | });
13 |
--------------------------------------------------------------------------------
/src/__tests__/react_link.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import ALink from '../components/Link.react';
4 |
5 | it('正确的渲染', () => {
6 | const tree = renderer
7 | .create(Walkerfree)
8 | .toJSON(); expect(tree).toMatchSnapshot();
9 | });
10 |
--------------------------------------------------------------------------------
/src/__tests__/sum.test.js:
--------------------------------------------------------------------------------
1 | const sum = require('../lib/sum');
2 |
3 | test('adds 1 + 2 to equal 3', () => {
4 | expect(sum(1, 2)).toBe(3);
5 | });
6 |
--------------------------------------------------------------------------------
/src/__tests__/useMatchMedia.test.js:
--------------------------------------------------------------------------------
1 | // import '../lib/matchMedia.mock';
2 | import { useMatchMedia } from '../lib/useMatchMedia';
3 |
4 | describe('useMatchMedia()', () => {
5 | it('useMatchMedia() 被调用', () => {
6 | const res = useMatchMedia();
7 | expect(res).toBeUndefined();
8 | // expect(res).toBeDefined();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/__tests__/user.test.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import Users from '../lib/user';
3 |
4 | jest.mock('axios');
5 |
6 | test('should fetch users', () => {
7 | const resp = {
8 | data: [
9 | {
10 | name: 'Durban',
11 | },
12 | ],
13 | };
14 |
15 | axios.get.mockResolvedValue(resp);
16 | // 或者也可以使用下面的代码
17 | // axios.get.mockImplementation(() => Promise.resolve(resp));
18 |
19 |
20 | return Users.all().then(users => expect(users).toEqual(resp.data));
21 | });
22 |
--------------------------------------------------------------------------------
/src/__tests__/user_async.test.js:
--------------------------------------------------------------------------------
1 | import Users from '../lib/user';
2 |
3 | jest.mock('../lib/request');
4 |
5 | // The assertion for a promise must be returned.
6 | it('works with promises', () =>
7 | // expect.assertions(1);
8 | Users
9 | .getUserName(4)
10 | .then(data => expect(data).toEqual('Mark')));
11 |
12 | it('works with resolves', () => {
13 | expect.assertions(1);
14 | return expect(Users.getUserName(5)).resolves.toEqual('Paul');
15 | });
16 |
--------------------------------------------------------------------------------
/src/__tests__/user_async_await.test.js:
--------------------------------------------------------------------------------
1 | import Users from '../lib/user';
2 |
3 | jest.mock('../lib/request');
4 |
5 | it('works with async/await', async () => {
6 | const data = await Users.getUserName(4);
7 | expect(data).toEqual('Mark');
8 | });
9 |
10 | it('works with async/await and resolves', async () => {
11 | expect.assertions(1);
12 | await expect(Users.getUserName(5)).resolves.toEqual('Paul');
13 | });
14 |
--------------------------------------------------------------------------------
/src/__tests__/user_async_await_error.test.js:
--------------------------------------------------------------------------------
1 | import Users from '../lib/user';
2 |
3 | jest.mock('../lib/request');
4 |
5 | test('tests error with promises', async () => {
6 | expect.assertions(1);
7 | return Users.getUserName(2).catch(e =>
8 | expect(e).toEqual({
9 | error: 'User with 2 not found.',
10 | }));
11 | });
12 |
13 | it('tests error with async/await', async () => {
14 | expect.assertions(1);
15 | try {
16 | await Users.getUserName(1);
17 | } catch (e) {
18 | expect(e).toEqual({
19 | error: 'User with 1 not found.',
20 | });
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/src/__tests__/user_async_await_rejects.test.js:
--------------------------------------------------------------------------------
1 | import Users from '../lib/user';
2 |
3 | jest.mock('../lib/request');
4 | it('tests error with rejects', () => {
5 | expect.assertions(1);
6 | return expect(Users.getUserName(3)).rejects.toEqual({
7 | error: 'User with 3 not found.',
8 | });
9 | });
10 |
11 | it('tests error with async/await and rejects', async () => {
12 | expect.assertions(1);
13 | await expect(Users.getUserName(3)).rejects.toEqual({
14 | error: 'User with 3 not found.',
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/actions/counter.js:
--------------------------------------------------------------------------------
1 | const increment = () => ({
2 | type: 'INCREMENT',
3 | });
4 |
5 | const decrement = () => ({
6 | type: 'DECREMENT',
7 | });
8 |
9 | export default {
10 | increment,
11 | decrement,
12 | };
13 |
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import counter from './counter';
2 |
3 | const actions = {
4 | counter,
5 | };
6 |
7 | export default actions;
8 | export { counter };
9 |
--------------------------------------------------------------------------------
/src/components/AboutComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PropTypes } from 'prop-types';
3 | import { Route, Link as ALink } from 'react-router-dom';
4 | import RoleComponent from './RoleComponent';
5 |
6 | class AboutComponent extends React.Component {
7 | constructor(props, context) {
8 | super(props, context);
9 |
10 | this.state = {};
11 | }
12 |
13 | render() {
14 | const {
15 | match,
16 | } = this.props;
17 |
18 | return (
19 |
20 |
About
21 |
22 |
23 | -
24 | 你
25 |
26 | -
27 | 我
28 |
29 | -
30 | 他
31 |
32 |
33 |
34 |
35 |
选择一个角色.
}
39 | />
40 |
41 | );
42 | }
43 | }
44 |
45 | AboutComponent.propTypes = {
46 | match: PropTypes.objectOf(PropTypes.any).isRequired,
47 | };
48 |
49 | export default AboutComponent;
50 |
--------------------------------------------------------------------------------
/src/components/AppComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class AppComponent extends React.Component {
5 | constructor(props, context) {
6 | super(props, context);
7 |
8 | this.state = {};
9 | }
10 |
11 | render() {
12 | return (
13 | {this.props.children}
14 | );
15 | }
16 | }
17 |
18 | AppComponent.propTypes = {
19 | children: PropTypes.node.isRequired,
20 | };
21 |
22 | export default AppComponent;
23 |
--------------------------------------------------------------------------------
/src/components/CheckboxWithLabelComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class CheckboxWithLabelComponent extends React.Component {
5 | constructor(props, context) {
6 | super(props, context);
7 |
8 | this.state = {
9 | isChecked: false,
10 | };
11 |
12 | this.onChange = this.onChange.bind(this);
13 | }
14 |
15 | onChange() {
16 | this.setState({
17 | isChecked: !this.state.isChecked,
18 | });
19 | }
20 |
21 | render() {
22 | return (
23 |
31 | );
32 | }
33 | }
34 |
35 | CheckboxWithLabelComponent.propTypes = {
36 | labelOn: PropTypes.string.isRequired,
37 | labelOff: PropTypes.string.isRequired,
38 | };
39 |
40 | export default CheckboxWithLabelComponent;
41 |
--------------------------------------------------------------------------------
/src/components/CounterComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { counter } from 'Actions';
5 |
6 | const {
7 | increment, decrement,
8 | } = counter;
9 |
10 | class CounterComponent extends Component {
11 | constructor(props, context) {
12 | super(props, context);
13 |
14 | this.state = {};
15 | }
16 |
17 | render() {
18 | const {
19 | count,
20 | } = this.props;
21 |
22 | return (
23 |
24 |
数值【Look hot reloader】: {count}
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
32 | CounterComponent.propTypes = {
33 | count: PropTypes.number,
34 | doIncrement: PropTypes.func.isRequired,
35 | doDecrement: PropTypes.func.isRequired,
36 | };
37 |
38 | CounterComponent.defaultProps = {
39 | count: 0,
40 | };
41 |
42 | const mapStateToProps = state => ({
43 | count: state.count,
44 | });
45 |
46 | const mapDispatchToProps = dispatch => ({
47 | doIncrement: () => dispatch(increment()),
48 | doDecrement: () => dispatch(decrement()),
49 | });
50 |
51 |
52 | export default connect(mapStateToProps, mapDispatchToProps)(CounterComponent);
53 |
--------------------------------------------------------------------------------
/src/components/HomeComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | class HomeComponent extends React.Component {
5 | constructor(props, context) {
6 | super(props, context);
7 |
8 | this.state = {};
9 | }
10 |
11 | render() {
12 | return (
13 |
14 |
首页
15 |
16 | );
17 | }
18 | }
19 |
20 | export default HomeComponent;
21 |
--------------------------------------------------------------------------------
/src/components/Link.react.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const STATUS = {
5 | HOVERED: 'hovered',
6 | NORMAL: 'normal',
7 | };
8 |
9 | class Link extends React.Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.onMouseEnter = this.onMouseEnter.bind(this);
14 | this.onMouseleave = this.onMouseleave.bind(this);
15 | this.state = {
16 | class: STATUS.NORMAL,
17 | };
18 | }
19 |
20 | onMouseEnter() {
21 | this.setState({
22 | class: STATUS.HOVERED,
23 | });
24 | }
25 |
26 | onMouseleave() {
27 | this.setState({
28 | class: STATUS.NORMAL,
29 | });
30 | }
31 |
32 | render() {
33 | return (
34 |
40 | {this.props.children}
41 |
42 | );
43 | }
44 | }
45 |
46 | Link.propTypes = {
47 | page: PropTypes.string.isRequired,
48 | children: PropTypes.oneOfType([
49 | PropTypes.element,
50 | PropTypes.string,
51 | ]).isRequired,
52 | };
53 |
54 | export default Link;
55 |
--------------------------------------------------------------------------------
/src/components/LoadingComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class LoadingComponent extends Component {
4 | constructor(props, context) {
5 | super(props, context);
6 |
7 | this.state = {};
8 | }
9 |
10 | render() {
11 | return (
12 | Loading......
13 | );
14 | }
15 | }
16 |
17 | export default LoadingComponent;
18 |
--------------------------------------------------------------------------------
/src/components/RoleComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class RoleComponent extends Component {
5 | constructor(props, context) {
6 | super(props, context);
7 |
8 | this.state = {};
9 | }
10 |
11 | render() {
12 | const {
13 | match,
14 | } = this.props;
15 |
16 | const {
17 | role,
18 | } = match.params;
19 |
20 | let name;
21 | if (role === 'you') {
22 | name = '你';
23 | } else if (role === 'me') {
24 | name = '我';
25 | } else if (role === 'him') {
26 | name = '他';
27 | }
28 | return (
29 | 关于 {name} 的故事
30 | );
31 | }
32 | }
33 |
34 | RoleComponent.propTypes = {
35 | match: PropTypes.objectOf(PropTypes.any).isRequired,
36 | };
37 |
38 | export default RoleComponent;
39 |
40 |
--------------------------------------------------------------------------------
/src/components/TopicComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PropTypes } from 'prop-types';
3 |
4 | class TopicComponent extends React.Component {
5 | constructor(props, context) {
6 | super(props, context);
7 |
8 | this.state = {};
9 | }
10 |
11 | render() {
12 | const {
13 | match,
14 | } = this.props;
15 |
16 | return (
17 | 论题【{match.params.topicId}】
18 | );
19 | }
20 | }
21 |
22 | TopicComponent.propTypes = {
23 | match: PropTypes.objectOf(PropTypes.any).isRequired,
24 | };
25 |
26 | export default TopicComponent;
27 |
--------------------------------------------------------------------------------
/src/components/TopicsComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link as ALink, Route } from 'react-router-dom';
3 | import { PropTypes } from 'prop-types';
4 | import TopicComponent from './TopicComponent';
5 |
6 | class TopicsComponent extends React.Component {
7 | constructor(props, context) {
8 | super(props, context);
9 |
10 | this.state = {};
11 | }
12 |
13 | render() {
14 | const {
15 | match,
16 | } = this.props;
17 |
18 | return (
19 |
20 |
论题
21 |
22 | -
23 | 论题1
24 |
25 | -
26 | 论题2
27 |
28 | -
29 | 论题3
30 |
31 | -
32 | 论题4
33 |
34 |
35 |
36 |
37 |
38 |
请选择一个感兴趣的论题.
}
42 | />
43 |
44 | );
45 | }
46 | }
47 |
48 | TopicsComponent.propTypes = {
49 | match: PropTypes.objectOf(PropTypes.any).isRequired,
50 | };
51 |
52 | export default TopicsComponent;
53 |
--------------------------------------------------------------------------------
/src/css/CounterComponent.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | width: 60px;
3 | height: 30px;
4 | background: #673ab7;
5 | font-size: 16px;
6 | border: none;
7 | border-radius: 2px;
8 | outline: none;
9 | color: #fff;
10 | }
11 |
12 | .btn:focus {
13 | background: #3d51b5;
14 | }
15 |
16 | .btn.first-child {
17 | margin-right: 5px;
18 | }
--------------------------------------------------------------------------------
/src/css/main.css:
--------------------------------------------------------------------------------
1 | @import './CounterComponent.css';
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { AppContainer } from 'react-hot-loader';
3 | import { createBrowserHistory } from 'history';
4 | import { applyMiddleware, compose, createStore } from 'redux';
5 | import { Provider } from 'react-redux';
6 | import { createLogger } from 'redux-logger';
7 | import { connectRouter, routerMiddleware } from 'connected-react-router';
8 | import ReactDOM from 'react-dom';
9 | import App from './App';
10 | import rootReducer from './reducers';
11 | import './css/main.css';
12 |
13 | if ('serviceWorker' in navigator) {
14 | window.addEventListener('load', () => {
15 | navigator.serviceWorker.register('/service-worker.js').then((registration) => {
16 | console.log('SW registered: ', registration);
17 | }).catch((registrationError) => {
18 | console.log('SW registration failed: ', registrationError);
19 | });
20 | });
21 | }
22 |
23 | const history = createBrowserHistory();
24 | const initialState = {};
25 | const middleware = [];
26 | if (process.env.NODE_ENV !== 'production') {
27 | middleware.push(createLogger());
28 | }
29 |
30 | middleware.push(routerMiddleware(history));
31 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
32 | const store = createStore(
33 | connectRouter(history)(rootReducer),
34 | initialState,
35 | composeEnhancer(applyMiddleware(...middleware)),
36 | );
37 |
38 | const render = () => {
39 | ReactDOM.render(
40 | (
41 |
42 |
43 |
44 |
45 |
46 | ),
47 | document.getElementById('root'),
48 | );
49 | };
50 |
51 | render();
52 |
53 | if (module.hot) {
54 | module.hot.addStatusHandler((status) => {
55 | console.log('status = ', status);
56 | });
57 |
58 | module.hot.accept('./App', () => {
59 | // 这里是当前版本很重要的环节,不然的话react-hot-reload不起作用
60 | require('./App').default;
61 | render();
62 | });
63 |
64 | module.hot.accept('./reducers', () => {
65 | store.replaceReducer(connectRouter(history)(rootReducer));
66 | render();
67 | });
68 | }
69 |
--------------------------------------------------------------------------------
/src/lib/FileSummarizer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | function summarizeFilesInDirectorySync(directory) {
4 | return fs.readdirSync(directory).map(fileName => ({
5 | directory,
6 | fileName,
7 | }));
8 | }
9 |
10 | exports.summarizeFilesInDirectorySync = summarizeFilesInDirectorySync;
11 |
--------------------------------------------------------------------------------
/src/lib/__mocks__/fs.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const fs = jest.genMockFromModule('fs');
4 |
5 | let mockFiles = Object.create(null);
6 |
7 | function __setMockFiles(newMockFiles) {
8 | mockFiles = Object.create(null);
9 |
10 | const keys = Object.keys(newMockFiles);
11 |
12 | for (let index = 0; index < keys.length; index += 1) {
13 | const file = keys[index];
14 | const dir = path.dirname(file);
15 | if (!mockFiles[dir]) {
16 | mockFiles[dir] = [];
17 | }
18 | mockFiles[dir].push(path.basename(file));
19 | }
20 | }
21 |
22 | function readdirSync(directoryPath) {
23 | return mockFiles[directoryPath] || [];
24 | }
25 |
26 | fs.__setMockFiles = __setMockFiles;
27 | fs.readdirSync = readdirSync;
28 |
29 | module.exports = fs;
30 |
--------------------------------------------------------------------------------
/src/lib/__mocks__/request.js:
--------------------------------------------------------------------------------
1 | const users = {
2 | 4: {
3 | name: 'Mark',
4 | },
5 | 5: {
6 | name: 'Paul',
7 | },
8 | };
9 |
10 | export default function request(url) {
11 | return new Promise((resolve, reject) => {
12 | const userID = parseInt(url.substr('/users/'.length), 10);
13 | process.nextTick(() => {
14 | if (users[userID]) {
15 | return resolve(users[userID]);
16 | }
17 | return reject({
18 | error: `User with ${userID} not found.`,
19 | });
20 | });
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/src/lib/__mocks__/sound-player.js:
--------------------------------------------------------------------------------
1 | export const mockChoicePlaySoundFile = jest.fn();
2 | const mockPlaySoundFile = jest.fn();
3 |
4 | const mock = jest.fn().mockImplementation(() => {
5 | const data = {
6 | choicePlaySoundFile: mockChoicePlaySoundFile,
7 | playSoundFile: mockPlaySoundFile,
8 | };
9 |
10 | return data;
11 | });
12 |
13 | export default mock;
14 |
--------------------------------------------------------------------------------
/src/lib/infiniteTimerGame.js:
--------------------------------------------------------------------------------
1 | function infiniteTimerGame(callback) {
2 | console.log('Ready....go!');
3 |
4 | setTimeout(() => {
5 | console.log('Times up! 10 seconds before the next game starts...');
6 |
7 | if (callback) {
8 | callback();
9 | }
10 |
11 | // 10秒钟后执行下一个
12 | setTimeout(() => {
13 | infiniteTimerGame(callback);
14 | }, 10000);
15 | }, 1000);
16 | }
17 |
18 | module.exports = infiniteTimerGame;
19 |
--------------------------------------------------------------------------------
/src/lib/matchMedia.mock.js:
--------------------------------------------------------------------------------
1 | window.matchMedia = jest.fn().mockImplementation((query) => {
2 | const obj = {
3 | matches: false,
4 | media: query,
5 | onchange: null,
6 | addListener: jest.fn(),
7 | removeListener: jest.fn(),
8 | };
9 |
10 | return obj;
11 | });
12 |
--------------------------------------------------------------------------------
/src/lib/request.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 |
3 | export default function request(url) {
4 | return new Promise((resolve) => {
5 | http.get({
6 | path: url,
7 | }, (response) => {
8 | let data = '';
9 | response.on('data', (o) => {
10 | data += o;
11 | return data;
12 | });
13 | response.on('end', () => resolve(data));
14 | });
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/src/lib/sound-player-consumer.js:
--------------------------------------------------------------------------------
1 | import SoundPlayer from './sound-player';
2 |
3 | export default class SoundPlayerConsumer {
4 | constructor() {
5 | this.soundPlayer = new SoundPlayer();
6 | }
7 |
8 | play() {
9 | const coolSoundFileName = 'song.mp3';
10 | this.soundPlayer.choicePlaySoundFile(coolSoundFileName);
11 | this.soundPlayer.playSoundFile();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/sound-player.js:
--------------------------------------------------------------------------------
1 | export default class SoundPlayer {
2 | constructor() {
3 | this.name = 'Player1';
4 | this.fileName = '';
5 | }
6 |
7 | choicePlaySoundFile(fileName) {
8 | this.fileName = fileName;
9 | }
10 |
11 | playSoundFile() {
12 | console.log('播放的文件是:', this.fileName);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/lib/sum.js:
--------------------------------------------------------------------------------
1 | function sum(a, b) {
2 | return a + b;
3 | }
4 | module.exports = sum;
5 |
--------------------------------------------------------------------------------
/src/lib/task.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Task: args => args,
3 | };
4 |
5 |
--------------------------------------------------------------------------------
/src/lib/timerGame.js:
--------------------------------------------------------------------------------
1 | function timerGame(callback) {
2 | console.log('Ready....go!');
3 | setTimeout(() => {
4 | console.log('Times up -- stop!');
5 | return callback && callback();
6 | }, 1000);
7 | }
8 |
9 | module.exports = timerGame;
10 |
--------------------------------------------------------------------------------
/src/lib/useMatchMedia.js:
--------------------------------------------------------------------------------
1 | const useMatchMedia = () => {
2 | const res = window.matchMedia;
3 | return res;
4 | };
5 |
6 | module.exports = {
7 | useMatchMedia,
8 | };
9 |
--------------------------------------------------------------------------------
/src/lib/user.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import request from './request';
3 |
4 | class Users {
5 | static all() {
6 | return axios.get('/user.json').then(resp => resp.data);
7 | }
8 |
9 | static getUserName(userID) {
10 | return request(`/users/${userID}`).then(user => user.name);
11 | }
12 | }
13 |
14 | export default Users;
15 |
--------------------------------------------------------------------------------
/src/reducers/counter.js:
--------------------------------------------------------------------------------
1 | const counterReducer = (state = 0, action) => {
2 | switch (action.type) {
3 | case 'INCREMENT':
4 | return state + 1;
5 | case 'DECREMENT':
6 | return state - 1;
7 | default:
8 | return state;
9 | }
10 | };
11 |
12 | export default counterReducer;
13 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import counterReducer from './counter';
3 |
4 | export default combineReducers({
5 | count: counterReducer,
6 | });
7 |
--------------------------------------------------------------------------------
/src/routes.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Loadable from 'react-loadable';
3 | import {
4 | BrowserRouter as Router,
5 | Route,
6 | Link as ALink,
7 | } from 'react-router-dom';
8 |
9 | import AppComponent from './components/AppComponent';
10 | import HomeComponent from './components/HomeComponent';
11 | import LoadingComponent from './components/LoadingComponent';
12 |
13 | const routes = (
14 |
15 |
16 |
17 |
18 | - 首页
19 | - 关于
20 | - 论题
21 | - 计数器
22 |
23 |
24 |
25 |
26 | import('./components/AboutComponent'),
30 | loading: LoadingComponent,
31 | })}
32 | />
33 | import('./components/TopicsComponent'),
37 | loading: LoadingComponent,
38 | })}
39 | />
40 | import('./components/CounterComponent'),
44 | loading: LoadingComponent,
45 | })}
46 | />
47 |
48 |
49 |
50 | );
51 |
52 | export default routes;
53 |
--------------------------------------------------------------------------------
/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const CleanWebpackPlugin = require('clean-webpack-plugin');
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
5 |
6 | module.exports = {
7 | plugins: [
8 | new CleanWebpackPlugin(['dist']),
9 | new HtmlWebpackPlugin({
10 | title: 'React + ReactRouter Demo',
11 | filename: './index.html', // 调用的文件
12 | template: './index.html', // 模板文件
13 | }),
14 | ],
15 | output: {
16 | filename: '[name].bundle.js',
17 | chunkFilename: '[chunkhash].bundle.js',
18 | path: path.resolve(__dirname, 'dist'),
19 | publicPath: '/',
20 | },
21 | module: {
22 | rules: [{
23 | test: /\.(js|jsx)$/,
24 | loader: 'babel-loader',
25 | exclude: [
26 | path.resolve(__dirname, 'node_modules'),
27 | ],
28 | options: {
29 | plugins: ['transform-async-to-generator', 'transform-strict-mode', 'transform-object-assign', 'transform-decorators-legacy', 'react-hot-loader/babel'],
30 | presets: ['env', 'react', 'stage-0'],
31 | },
32 | },
33 | {
34 | test: /\.css$/,
35 | use: ExtractTextPlugin.extract({
36 | fallback: 'style-loader',
37 | use: 'css-loader',
38 | }),
39 | },
40 | {
41 | test: /\.(png|svg|jpg|gif)$/,
42 | use: [
43 | 'file-loader',
44 | ],
45 | },
46 | {
47 | test: /\.(woff|woff2|eot|ttf|otf)$/,
48 | use: [
49 | 'file-loader',
50 | ],
51 | },
52 | {
53 | test: /\.(csv|tsv)$/,
54 | use: [
55 | 'csv-loader',
56 | ],
57 | },
58 | {
59 | test: /\.xml$/,
60 | use: [
61 | 'xml-loader',
62 | ],
63 | },
64 | ],
65 | },
66 | resolve: {
67 | extensions: ['.js', '.jsx'], // 这里是必须要加的,不然默认的值加载['.js','.json']为后缀的文件
68 | alias: {
69 | Actions: path.resolve(__dirname, 'src/actions'),
70 | },
71 | },
72 | };
73 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const CleanWebpackPlugin = require('clean-webpack-plugin');
5 |
6 | let config = {
7 | entry: {
8 | app: [
9 | 'webpack/hot/only-dev-server',
10 | 'react-hot-loader/patch',
11 | './src/index.jsx',
12 | ],
13 | },
14 | plugins: [
15 | new CleanWebpackPlugin(['dist']),
16 | new HtmlWebpackPlugin({
17 | title: 'React + ReactRouter Demo',
18 | filename: './index.html', // 调用的文件
19 | template: './index.html', // 模板文件
20 | }),
21 | ],
22 | output: {
23 | filename: '[name].bundle.js',
24 | chunkFilename: '[chunkhash].bundle.js',
25 | path: path.resolve(__dirname, 'dist'),
26 | publicPath: '/',
27 | },
28 | module: {
29 | rules: [{
30 | test: /\.(js|jsx)$/,
31 | loader: 'babel-loader',
32 | exclude: [
33 | path.resolve(__dirname, 'node_modules'),
34 | ],
35 | options: {
36 | plugins: ['transform-async-to-generator', 'transform-strict-mode', 'transform-object-assign', 'transform-decorators-legacy', 'react-hot-loader/babel'],
37 | presets: ['env', 'react', 'stage-0'],
38 | },
39 | },
40 | {
41 | test: /\.css$/,
42 | use: [
43 | 'style-loader',
44 | 'css-loader',
45 | ],
46 | },
47 | {
48 | test: /\.(png|svg|jpg|gif)$/,
49 | use: [
50 | 'file-loader',
51 | ],
52 | },
53 | {
54 | test: /\.(woff|woff2|eot|ttf|otf)$/,
55 | use: [
56 | 'file-loader',
57 | ],
58 | },
59 | {
60 | test: /\.(csv|tsv)$/,
61 | use: [
62 | 'csv-loader',
63 | ],
64 | },
65 | {
66 | test: /\.xml$/,
67 | use: [
68 | 'xml-loader',
69 | ],
70 | },
71 | ],
72 | },
73 | resolve: {
74 | extensions: ['.js', '.jsx'], // 这里是必须要加的,不然默认的值加载['.js','.json']为后缀的文件
75 | alias: {
76 | Actions: path.resolve(__dirname, 'src/actions'),
77 | },
78 | },
79 | };
80 |
81 | if (process.env.NODE_ENV === 'production') {
82 | config = Object.assign({}, config, {
83 | mode: 'production',
84 | });
85 | } else {
86 | const {
87 | plugins,
88 | } = config;
89 | plugins.push(new webpack.DefinePlugin({
90 | 'global.GENTLY': false,
91 | __DEV__: true,
92 | }));
93 | config = Object.assign({}, config, {
94 | mode: 'development',
95 | devtool: 'eval',
96 | devServer: {
97 | hot: true,
98 | contentBase: path.join(__dirname, 'dist'),
99 | compress: true,
100 | port: 8083,
101 | historyApiFallback: {
102 | rewrites: [{
103 | from: /^\/$/,
104 | to: './index.html',
105 | }],
106 | },
107 | },
108 | plugins,
109 | });
110 | }
111 |
112 | module.exports = config;
113 |
--------------------------------------------------------------------------------
/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const CleanWebpackPlugin = require('clean-webpack-plugin');
5 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
6 | const ManifestPlugin = require('webpack-manifest-plugin');
7 | const path = require('path');
8 | const common = require('./webpack.common');
9 |
10 | module.exports = merge(common, {
11 | mode: 'development',
12 | devtool: 'eval',
13 | entry: {
14 | app: [
15 | 'webpack/hot/only-dev-server',
16 | 'react-hot-loader/patch',
17 | './src/index.jsx',
18 | ],
19 | vendor: [
20 | 'react',
21 | 'react-dom',
22 | 'redux',
23 | ],
24 | },
25 | output: {
26 | filename: '[name].[hash].bundle.js',
27 | chunkFilename: '[name].[hash].bundle.js',
28 | path: path.resolve(__dirname, 'dist'),
29 | publicPath: '/',
30 | },
31 | devServer: {
32 | hot: true,
33 | contentBase: path.join(__dirname, 'dist'),
34 | compress: true,
35 | port: 8083,
36 | historyApiFallback: {
37 | rewrites: [{
38 | from: /^\/$/,
39 | to: './index.html',
40 | }],
41 | },
42 | },
43 | plugins: [
44 | new CleanWebpackPlugin(['dist']),
45 | new HtmlWebpackPlugin({
46 | title: 'React + ReactRouter Demo',
47 | filename: './index.html', // 调用的文件
48 | template: './index.html', // 模板文件
49 | }),
50 | new webpack.DefinePlugin({
51 | 'process.env.NODE_ENV': JSON.stringify('development'),
52 | }),
53 | new ExtractTextPlugin({
54 | filename: '[name].[hash].bundle.css',
55 | }),
56 | new ManifestPlugin(),
57 | new webpack.NamedModulesPlugin(),
58 | ],
59 | optimization: {
60 | splitChunks: {
61 | chunks: 'initial', // 必须三选一: "initial" | "all"(默认就是all) | "async"
62 | minSize: 0, // 最小尺寸,默认0
63 | minChunks: 1, // 最小 chunk ,默认1
64 | maxAsyncRequests: 1, // 最大异步请求数, 默认1
65 | maxInitialRequests: 1, // 最大初始化请求书,默认1
66 | name: () => {}, // 名称,此选项可接收 function
67 | cacheGroups: { // 这里开始设置缓存的 chunks
68 | priority: '0', // 缓存组优先级 false | object |
69 | vendor: { // key 为entry中定义的 入口名称
70 | chunks: 'initial', // 必须三选一: "initial" | "all" | "async"(默认就是异步)
71 | test: /react|lodash|react-dom|redux/, // 正则规则验证,如果符合就提取 chunk
72 | name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
73 | minSize: 0,
74 | minChunks: 1,
75 | enforce: true,
76 | maxAsyncRequests: 1, // 最大异步请求数, 默认1
77 | maxInitialRequests: 1, // 最大初始化请求书,默认1
78 | reuseExistingChunk: true, // 可设置是否重用该chunk(查看源码没有发现默认值)
79 | },
80 | },
81 | },
82 | },
83 | });
84 |
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const merge = require('webpack-merge');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 | const CleanWebpackPlugin = require('clean-webpack-plugin');
6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
7 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
8 | const ManifestPlugin = require('webpack-manifest-plugin');
9 | const InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin');
10 | const WebpackBundleAnalyzer = require('webpack-bundle-analyzer');
11 | const common = require('./webpack.common');
12 | const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
13 |
14 | const {
15 | BundleAnalyzerPlugin,
16 | } = WebpackBundleAnalyzer;
17 |
18 | module.exports = merge(common, {
19 | mode: 'production',
20 | devtool: 'source-map',
21 | entry: {
22 | app: [
23 | './src/index.jsx',
24 | ],
25 | vendor: [
26 | 'react',
27 | 'react-dom',
28 | 'redux',
29 | ],
30 | },
31 | output: {
32 | filename: '[name].[chunkhash].bundle.js',
33 | chunkFilename: '[name].[chunkhash].bundle.js',
34 | path: path.resolve(__dirname, 'dist'),
35 | publicPath: '/',
36 | },
37 | plugins: [
38 | new CleanWebpackPlugin(['dist']),
39 | new HtmlWebpackPlugin({
40 | title: 'React + ReactRouter',
41 | filename: './index.html', // 调用的文件
42 | template: './index.html', // 模板文件
43 | }),
44 | new InlineManifestWebpackPlugin(),
45 | new UglifyJsPlugin({
46 | sourceMap: true,
47 | }),
48 | new webpack.DefinePlugin({
49 | 'process.env.NODE_ENV': JSON.stringify('production'),
50 | }),
51 | new ExtractTextPlugin({
52 | filename: 'main.[chunkhash].css',
53 | }),
54 | new ManifestPlugin(),
55 | new webpack.NamedModulesPlugin(),
56 | // new BundleAnalyzerPlugin(),
57 | new WorkboxWebpackPlugin.GenerateSW({
58 | clientsClaim: true,
59 | skipWaiting: true,
60 | exclude: [/\.map$/],
61 | }),
62 | ],
63 | optimization: {
64 | splitChunks: {
65 | chunks: 'initial', // 必须三选一: "initial" | "all"(默认就是all) | "async"
66 | minSize: 0, // 最小尺寸,默认0
67 | minChunks: 1, // 最小 chunk ,默认1
68 | maxAsyncRequests: 1, // 最大异步请求数, 默认1
69 | maxInitialRequests: 1, // 最大初始化请求书,默认1
70 | name: () => {}, // 名称,此选项可接收 function
71 | cacheGroups: { // 这里开始设置缓存的 chunks
72 | priority: '0', // 缓存组优先级 false | object |
73 | vendor: { // key 为entry中定义的 入口名称
74 | chunks: 'initial', // 必须三选一: "initial" | "all" | "async"(默认就是异步)
75 | test: /react|lodash|react-dom|redux/, // 正则规则验证,如果符合就提取 chunk
76 | name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
77 | minSize: 0,
78 | minChunks: 1,
79 | enforce: true,
80 | maxAsyncRequests: 1, // 最大异步请求数, 默认1
81 | maxInitialRequests: 1, // 最大初始化请求书,默认1
82 | reuseExistingChunk: true, // 可设置是否重用该chunk(查看源码没有发现默认值)
83 | },
84 | },
85 | },
86 | runtimeChunk: {
87 | name: 'manifest',
88 | },
89 | },
90 | });
91 |
--------------------------------------------------------------------------------