├── .npmignore
├── .travis.yml
├── .prettierrc
├── docs
├── _config.yml
├── introduction
│ ├── installation.md
│ └── usage.md
└── index.md
├── .gitignore
├── tsconfig.json
├── LICENSE.md
├── README.md
├── .eslintrc.js
├── tests
├── __snapshots__
│ └── index.test.tsx.snap
└── index.test.tsx
├── package.json
└── src
└── index.tsx
/.npmignore:
--------------------------------------------------------------------------------
1 | tests
2 | .babelrc
3 | .vscode
4 | coverage
5 | .flowconfig
6 | src
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: '6'
3 | sudo: false
4 | after_success:
5 | - bash <(curl -s https://codecov.io/bash)
6 |
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": false,
3 | "semi": true,
4 | "trailingComma": "all",
5 | "singleQuote": true,
6 | "printWidth": 120,
7 | "tabWidth": 2
8 | }
9 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | remote_theme: 42bv/fortytwo
2 | title: 'react-fetch-progressbar'
3 | description: 'Progressbar during fetch requests'
4 | baseurl: /react-fetch-progressbar/
5 | plugins:
6 | - jekyll-remote-theme
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Build output
4 | /lib
5 |
6 | # dependencies
7 | /node_modules
8 |
9 | # testing
10 | /coverage
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 | .vscode
19 |
20 |
21 | # Jekyll
22 | *.gem
23 | .bundle
24 | .sass-cache
25 | _site
26 | Gemfile.lock
--------------------------------------------------------------------------------
/docs/introduction/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Installation
4 | description: 'Installation instructions for react-fetch-progressbar.'
5 | parent: Introduction
6 | permalink: /installation
7 | nav_order: 1
8 | ---
9 |
10 | ### With npm
11 |
12 | ```
13 | npm install react-fetch-progressbar --save
14 | ```
15 |
16 | ### Or with Yarn
17 |
18 | ```
19 | yarn add react-fetch-progressbar
20 | ```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noUnusedLocals": true,
4 | "noImplicitReturns": true,
5 | "noFallthroughCasesInSwitch": true,
6 | "declaration": true,
7 | "downlevelIteration": true,
8 | "lib": ["dom", "es6", "es2015.promise"],
9 | "module": "commonjs",
10 | "moduleResolution": "Node",
11 | "outDir": "./lib",
12 | "strict": true,
13 | "target": "es6",
14 | "jsx": "react",
15 | "esModuleInterop": true
16 | },
17 | "files": ["src/index.tsx"],
18 | "exclude": ["node_modules"]
19 | }
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2016-2017 42BV (http://www.42.nl)
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
4 |
5 | http://www.apache.org/licenses/LICENSE-2.0
6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About
2 |
3 | [](https://travis-ci.org/42BV/react-fetch-progressbar)
4 | [](https://codecov.io/gh/42BV/react-fetch-progressbar)
5 |
6 | Show a ProgressBar in React whenever a fetch request is in progress.
7 |
8 | You can view a [demo here.]( https://codesandbox.io/embed/g5jmo1Q8Z?view=preview)
9 |
10 | # Installation
11 |
12 | `npm install react-fetch-progressbar --save`
13 |
14 | # Documentation
15 |
16 | See the [documentation.](https://42bv.github.io/react-fetch-progressbar/)
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | ecmaVersion: 2018,
5 | sourceType: 'module'
6 | },
7 | extends: ['plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
8 | plugins: ['@typescript-eslint', 'react'],
9 | rules: {
10 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
11 | // e.g. "@typescript-eslint/explicit-function-return-type": "off",
12 | indent: 'off',
13 | '@typescript-eslint/indent': ['error', 2],
14 | 'react/prop-types': [0] // Disable propTypes warning @see https://stackoverflow.com/questions/41746028/proptypes-in-a-typescript-react-application
15 | },
16 | settings: {
17 | react: {
18 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
19 | },
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Introduction
4 | nav_order: 1
5 | description: 'Documentation for mad-spring-connect.'
6 | has_children: true
7 | permalink: /
8 | ---
9 |
10 | [](https://travis-ci.org/42BV/react-fetch-progressbar)
11 | [](https://codecov.io/gh/42BV/react-fetch-progressbar)
12 |
13 | Show a ProgressBar in React whenever a fetch request is in progress. You can view a [demo here.]( https://codesandbox.io/embed/g5jmo1Q8Z?view=preview)
14 |
15 | ## Features
16 |
17 | 1. Only shows the ProgressBar when requests take more than 150 milliseconds.
18 | This way users are not looking at a progress bar needlessly.
19 | 2. When the requests finishes it will always show a nice 100% complete animation.
20 | This way users will always see the progress bar complete the animation.
21 | 3. The ProgressBar can be styled to your liking.
22 |
23 |
--------------------------------------------------------------------------------
/tests/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Component: ProgressBar ui custom style 1`] = `
4 |
19 | `;
20 |
21 | exports[`Component: ProgressBar ui mode: active 1`] = `
22 |
36 | `;
37 |
38 | exports[`Component: ProgressBar ui mode: complete 1`] = `
39 |
53 | `;
54 |
55 | exports[`Component: ProgressBar ui mode: init 1`] = `
56 |
70 | `;
71 |
--------------------------------------------------------------------------------
/docs/introduction/usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: Usage
4 | description: 'Usage instructions for react-fetch-progressbar.'
5 | parent: Introduction
6 | permalink: /usage
7 | nav_order: 3
8 | ---
9 |
10 | First you must override `window.fetch` with the `progressbarFetch`
11 | so the ProgressBar can knows whenever fetch is called:
12 |
13 |
14 | ```js
15 | import { progressBarFetch, setOriginalFetch } from 'react-fetch-progressbar';
16 |
17 | // Let react-fetch-progressbar know what the original fetch is.
18 | setOriginalFetch(window.fetch);
19 |
20 | /*
21 | Now override the fetch with progressBarFetch, so the ProgressBar
22 | knows how many requests are currently active.
23 | */
24 | window.fetch = progressBarFetch;
25 | ```
26 |
27 | Next you simply display the ProgressBar somewhere:
28 |
29 | ```jsx
30 | import { ProgressBar } from 'react-fetch-progressbar';
31 |
32 | // Then somewhere in a render method:
33 |
34 | ```
35 |
36 | WARNING: only render one ProgressBar at a time, otherwise the two
37 | progressBars will interfere with each other.
38 |
39 | # Styling the ProgressBar
40 |
41 | You have a two options, either provide a style object or create
42 | a CSS rule.
43 |
44 | First these are the default styles, which are applied:
45 |
46 | ```js
47 | {
48 | position: 'absolute',
49 | top: '0',
50 | zIndex: '9000',
51 | backgroundColor: '#f0ad4e',
52 | height: '4px',
53 | }
54 | ```
55 |
56 | It is important that you never override the `transition` and the
57 | `width` property, otherwise the animation will not work.
58 |
59 | ## Via style
60 |
61 | Say you want a different `height` and `backgroundColor` you simply
62 | override the styles using:
63 |
64 | ```jsx
65 | const style = {
66 | backgroundColor: 'red',
67 | height: '10px'
68 | }
69 |
70 |
71 | ```
72 |
73 | ## Via CSS
74 |
75 | The class which is added on the progress bar is called `.react-fetch-progress-bar`
76 | you can extend that class and override properties like so:
77 |
78 | ```css
79 | .react-fetch-progress-bar {
80 | backgroundColor: red !important;
81 | height: 10px;
82 | }
83 | ```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-fetch-progressbar",
3 | "version": "1.0.0",
4 | "description": "Show a ProgressBar in React whenever a fetch request is in progress.",
5 | "files": [
6 | "lib"
7 | ],
8 | "main": "lib/index.js",
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/42BV/react-fetch-progressbar.git"
12 | },
13 | "keywords": [
14 | "React",
15 | "Fetch",
16 | "Progressbar"
17 | ],
18 | "author": "Maarten Hus",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/42BV/react-fetch-progressbar/issues"
22 | },
23 | "homepage": "https://github.com/42BV/react-fetch-progressbar#readme",
24 | "peerDependencies": {
25 | "react": "^15.6.1"
26 | },
27 | "devDependencies": {
28 | "@types/enzyme-adapter-react-16": "1.0.5",
29 | "@types/enzyme-to-json": "1.5.3",
30 | "@types/jest": "24.0.11",
31 | "@types/react": "16.8.8",
32 | "@typescript-eslint/eslint-plugin": "1.5.0",
33 | "@typescript-eslint/parser": "1.5.0",
34 | "enzyme": "3.3.0",
35 | "enzyme-adapter-react-16": "1.11.2",
36 | "enzyme-to-json": "3.3.5",
37 | "eslint": "5.15.3",
38 | "eslint-config-prettier": "4.1.0",
39 | "eslint-plugin-prettier": "3.0.1",
40 | "eslint-plugin-react": "7.12.4",
41 | "jest": "24.5.0",
42 | "prettier": "1.16.4",
43 | "react": "16.3.2",
44 | "react-dom": "16.3.3",
45 | "ts-jest": "24.0.0",
46 | "typescript": "3.3.4000"
47 | },
48 | "scripts": {
49 | "start": "jest test --watch",
50 | "test": "npm run lint && jest test --coverage",
51 | "ts": "tsc --version && tsc",
52 | "coverage": "npm test -- --coverage",
53 | "lint": "eslint \"src/**\"",
54 | "prepublish": "rm -rf lib && npm test && npm run ts"
55 | },
56 | "jest": {
57 | "globals": {
58 | "ts-jest": {
59 | "tsConfig": "/tsconfig.json"
60 | }
61 | },
62 | "moduleFileExtensions": [
63 | "ts",
64 | "tsx",
65 | "js",
66 | "node"
67 | ],
68 | "transform": {
69 | "\\.(ts|tsx)$": "ts-jest"
70 | },
71 | "testRegex": "/tests/.*\\.(ts|tsx)$"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | // Keeps track of how many requests are currently active.
4 | export let activeRequests = 0;
5 |
6 | /*
7 | Store a reference to the ProgressBar so 'progressBarFetch' can
8 | move the ProgressBar to the 'init' state.
9 | */
10 | export let progressBar: ProgressBar;
11 |
12 | /*
13 | The modes form a state machine with the following flow
14 |
15 | init -----> active -> complete --
16 | ^ \ |
17 | | \----\ |
18 | | \ |
19 | | \ |
20 | | ▾ |
21 | --------- hibernate <-----------
22 |
23 | hibernate: no animation is running the bar is invisible
24 | init: Preparing to potentially show the animation.
25 | active: the animation is running slowly to 80%
26 | complete: the animation runs quickly to 100%
27 | */
28 | type Mode = 'hibernate' | 'init' | 'active' | 'complete' | 'inactive';
29 |
30 | interface Props {
31 | style?: Record;
32 | }
33 |
34 | interface State {
35 | mode: Mode;
36 | }
37 |
38 | export class ProgressBar extends Component {
39 | public state: Readonly = {
40 | mode: 'hibernate',
41 | };
42 |
43 | // Set the reference to progressBar
44 | public componentDidMount(): void {
45 | progressBar = this;
46 | }
47 |
48 | // Only render if the mode changes
49 | public shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
50 | return nextState.mode !== this.state.mode;
51 | }
52 |
53 | /**
54 | * The ProgressBar continuously checks the status of how many
55 | * requests are currently active, and will accordingly move
56 | * to another state.
57 | *
58 | * @memberof ProgressBar
59 | */
60 | public tick(): void {
61 | const mode = this.state.mode;
62 |
63 | if (mode === 'complete') {
64 | //console.log('complete: moving to hibernate after 1 second to allow the close animation to complete');
65 | setTimeout(() => {
66 | this.setState({ mode: 'hibernate' });
67 | }, 1000);
68 | } else if (mode === 'active') {
69 | if (activeRequests === 0) {
70 | //console.log('active: there are no more pending request move to complete if there are still no pending requests after 200 milliseconds');
71 | setTimeout(() => {
72 | if (activeRequests === 0) {
73 | //console.log('active: even after 200 milliseconds there are no more pending request, moving to complete');
74 | this.moveToMode('complete');
75 | } else {
76 | //console.log('active: after 200 milliseconds another request was pending instead of going to complete will stay active.');
77 | this.tick();
78 | }
79 | }, 200);
80 | } else {
81 | //console.log('active: there are still pending requests staying active');
82 | this.tickWithDelay();
83 | }
84 | } else {
85 | // mode === 'init'
86 | if (activeRequests > 0) {
87 | //console.log('init: there are pending request move to active if there are still pending requests after 100 milliseconds');
88 | setTimeout(() => {
89 | if (activeRequests > 0) {
90 | //console.log('init: even after 100 milliseconds there are pending request, moving to active to trigger animation');
91 | this.moveToMode('active');
92 | } else {
93 | //console.log('init: after 100 milliseconds there were no pending request, the requests was so fast that showing an animation is unnecessary, move to hibernate');
94 | this.setState({ mode: 'hibernate' });
95 | }
96 | }, 100);
97 | } else {
98 | //console.log('init: no pending requests move to hibernate');
99 | this.setState({ mode: 'hibernate' });
100 | }
101 | }
102 | }
103 |
104 | public moveToInit(): void {
105 | if (this.state.mode === 'hibernate') {
106 | this.moveToMode('init');
107 | }
108 | }
109 |
110 | public moveToMode(mode: Mode): void {
111 | this.setState({ mode }, () => {
112 | this.tick();
113 | });
114 | }
115 |
116 | public tickWithDelay(): void {
117 | setTimeout(() => {
118 | this.tick();
119 | }, 50);
120 | }
121 |
122 | public render(): React.ReactNode {
123 | const mode = this.state.mode;
124 |
125 | if (mode === 'hibernate') {
126 | return null;
127 | }
128 |
129 | const width = mode === 'complete' ? 100 : mode === 'init' ? 0 : 80;
130 | const animationSpeed = mode === 'complete' ? 0.8 : 30;
131 | const transition = mode === 'init' ? '' : `width ${animationSpeed}s ease-in`;
132 |
133 | const style: React.CSSProperties = {
134 | position: 'absolute',
135 | top: '0',
136 | zIndex: 9000,
137 | backgroundColor: '#f0ad4e',
138 | height: '4px',
139 | transition,
140 | width: `${width}%`,
141 | ...this.props.style,
142 | };
143 |
144 | return ;
145 | }
146 | }
147 |
148 | type FetchSignature = (url: string, options?: object) => Promise;
149 |
150 | // We store the fetch here as provided by the user.
151 | let originalFetch: FetchSignature;
152 |
153 | export function setOriginalFetch(nextOriginalFetch: FetchSignature): void {
154 | originalFetch = nextOriginalFetch;
155 | }
156 |
157 | /**
158 | * Wrapper around fetch: https://developer.mozilla.org/en/docs/Web/API/Fetch_API
159 | *
160 | * It is used to monitor the number of requests which are currently
161 | * active. Each time a requests is made it increases the number of
162 | * requests, each time a request is finished, the number is decreased.
163 | *
164 | * @export
165 | * @param {string} url The url you want to send a request to.
166 | * @param {RequestOptions} [options] The options you want to pass for that request
167 | * @returns {Promise} A Promise which returns a Response
168 | */
169 | export async function progressBarFetch(url: string, options?: object): Promise {
170 | activeRequests += 1;
171 |
172 | if (progressBar) {
173 | progressBar.moveToInit();
174 | }
175 |
176 | try {
177 | const response = await originalFetch(url, options);
178 | activeRequests -= 1;
179 | return response;
180 | } catch (error) {
181 | activeRequests -= 1;
182 | return Promise.reject(error);
183 | }
184 | }
185 |
186 | /**
187 | * Sets the number of activeRequests manually.
188 | *
189 | * This method exists for testing purposes, so you should not
190 | * use it.
191 | *
192 | * @export
193 | * @param {number} nextActiveRequest
194 | */
195 | export function setActiveRequests(nextActiveRequest: number): void {
196 | activeRequests = nextActiveRequest;
197 | }
198 |
--------------------------------------------------------------------------------
/tests/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme, { shallow, ShallowWrapper } from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import toJson from 'enzyme-to-json';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 |
8 | import {
9 | ProgressBar,
10 | progressBarFetch,
11 | setActiveRequests,
12 | activeRequests,
13 | progressBar as progressBarRef,
14 | setOriginalFetch,
15 | } from '../src/index';
16 |
17 | describe('Component: ProgressBar', () => {
18 | describe('ui', () => {
19 | let progressBar: ShallowWrapper;
20 |
21 | function setup({ mode, style }: { mode: string; style?: object }): void {
22 | progressBar = shallow();
23 | progressBar.setState({ mode });
24 | }
25 |
26 | test('mode: hibernate', () => {
27 | setup({ mode: 'hibernate', style: undefined });
28 |
29 | expect(toJson(progressBar)).toBe('');
30 | });
31 |
32 | test('mode: init', () => {
33 | setup({ mode: 'init', style: undefined });
34 |
35 | expect(toJson(progressBar)).toMatchSnapshot();
36 | });
37 |
38 | test('mode: active', () => {
39 | setup({ mode: 'active', style: undefined });
40 |
41 | expect(toJson(progressBar)).toMatchSnapshot();
42 | });
43 |
44 | test('mode: complete', () => {
45 | setup({ mode: 'complete', style: undefined });
46 |
47 | expect(toJson(progressBar)).toMatchSnapshot();
48 | });
49 |
50 | test('custom style', () => {
51 | setup({
52 | mode: 'active',
53 | style: { backgroundColor: 'red', height: '10px', left: '10px' },
54 | });
55 |
56 | expect(toJson(progressBar)).toMatchSnapshot();
57 | });
58 | });
59 |
60 | describe('lifecycle methods', () => {
61 | it('should when componentDidMount set the progressBar reference', () => {
62 | const progressBar = shallow();
63 | progressBar.instance().componentDidMount();
64 | expect(progressBarRef).toBe(progressBar.instance());
65 | });
66 |
67 | it('should when shouldComponentUpdate is called only return true when the mode changes', () => {
68 | const progressBar = shallow();
69 |
70 | progressBar.setState({ mode: 'complete' });
71 | const instance = progressBar.instance();
72 |
73 | expect(instance.shouldComponentUpdate({ style: { color: 'blue' } }, { mode: 'complete' })).toBe(false);
74 | expect(instance.shouldComponentUpdate({ style: { color: 'orange' } }, { mode: 'active' })).toBe(true);
75 | });
76 | });
77 |
78 | describe('tick', () => {
79 | beforeEach(() => {
80 | jest.useFakeTimers();
81 |
82 | setActiveRequests(0);
83 | });
84 |
85 | afterEach(() => {
86 | jest.useRealTimers();
87 | });
88 |
89 | describe('mode: inactive', () => {
90 | it('should move to "active" when there are pending request, and after 100 milliseconds there are still pending requests', () => {
91 | const progressBar = shallow();
92 |
93 | setActiveRequests(1);
94 | progressBar.setState({ mode: 'inactive' });
95 | progressBar.instance().tick();
96 |
97 | jest.runTimersToTime(99);
98 | expect(progressBar.state().mode).toBe('inactive');
99 |
100 | jest.runTimersToTime(100);
101 | expect(progressBar.state().mode).toBe('active');
102 | });
103 |
104 | it('should move back to "hibernate" when there are pending request, but after 150 milliseconds no pending requests anymore', () => {
105 | const progressBar = shallow();
106 |
107 | setActiveRequests(1);
108 | progressBar.setState({ mode: 'inactive' });
109 | progressBar.instance().tick();
110 |
111 | jest.runTimersToTime(99);
112 | expect(progressBar.state().mode).toBe('inactive');
113 |
114 | setActiveRequests(0);
115 |
116 | jest.runTimersToTime(100);
117 | expect(progressBar.state().mode).toBe('hibernate');
118 | });
119 |
120 | it('should move to "hibernate" when there are no pending requests', () => {
121 | const progressBar = shallow();
122 |
123 | setActiveRequests(0);
124 | progressBar.setState({ mode: 'inactive' });
125 | progressBar.instance().tick();
126 |
127 | jest.runTimersToTime(50);
128 |
129 | expect(progressBar.state().mode).toBe('hibernate');
130 | });
131 | });
132 |
133 | describe('mode: active', () => {
134 | it('should move to "complete" when there are no pending request, and after 200 milliseconds there are still no pending requests', () => {
135 | const progressBar = shallow();
136 |
137 | setActiveRequests(0);
138 | progressBar.setState({ mode: 'active' });
139 | progressBar.instance().tick();
140 |
141 | jest.runTimersToTime(199);
142 | expect(progressBar.state().mode).toBe('active');
143 |
144 | jest.runTimersToTime(200);
145 | expect(progressBar.state().mode).toBe('complete');
146 | });
147 |
148 | it('should stay "active" when there are no pending request at first, but after 200 milliseconds there are new pending requests', () => {
149 | const progressBar = shallow();
150 |
151 | setActiveRequests(0);
152 | progressBar.setState({ mode: 'active' });
153 | progressBar.instance().tick();
154 |
155 | jest.runTimersToTime(199);
156 | expect(progressBar.state().mode).toBe('active');
157 |
158 | setActiveRequests(1);
159 |
160 | jest.runTimersToTime(200);
161 | expect(progressBar.state().mode).toBe('active');
162 | });
163 |
164 | it('should stay "active" when there are pending requests', () => {
165 | const progressBar = shallow();
166 |
167 | setActiveRequests(1);
168 | progressBar.setState({ mode: 'active' });
169 | progressBar.instance().tick();
170 |
171 | jest.runTimersToTime(50);
172 |
173 | expect(progressBar.state().mode).toBe('active');
174 | });
175 | });
176 |
177 | describe('mode: complete', () => {
178 | it('should move to "hibernate" after 1000 milliseconds to allow the animation to complete', () => {
179 | const progressBar = shallow();
180 | progressBar.setState({ mode: 'complete' });
181 | progressBar.instance().tick();
182 |
183 | jest.runTimersToTime(999);
184 |
185 | expect(progressBar.state().mode).toBe('complete');
186 |
187 | jest.runTimersToTime(1000);
188 |
189 | expect(progressBar.state().mode).toBe('hibernate');
190 | });
191 | });
192 | });
193 |
194 | describe('moveToInit', () => {
195 | it('should move to "init" when mode is "hibernate"', () => {
196 | const progressBar = shallow();
197 |
198 | spyOn(progressBar.instance(), 'moveToMode');
199 |
200 | progressBar.setState({ mode: 'hibernate' });
201 | progressBar.instance().moveToInit();
202 |
203 | expect(progressBar.instance().moveToMode).toHaveBeenCalledTimes(1);
204 | expect(progressBar.instance().moveToMode).toHaveBeenCalledWith('init');
205 | });
206 |
207 | it('should not move to "init" when mode is another mode', () => {
208 | const progressBar = shallow();
209 |
210 | spyOn(progressBar.instance(), 'moveToMode');
211 |
212 | progressBar.setState({ mode: 'active' });
213 | progressBar.instance().moveToInit();
214 |
215 | expect(progressBar.instance().moveToMode).toHaveBeenCalledTimes(0);
216 | });
217 | });
218 | });
219 |
220 | describe('progressBarFetch', () => {
221 | let fakeFetch: jest.Mock;
222 |
223 | beforeEach(() => {
224 | fakeFetch = jest.fn();
225 |
226 | const progressBar = shallow();
227 | progressBar.instance().componentDidMount();
228 |
229 | spyOn(progressBarRef, 'moveToInit');
230 |
231 | // @ts-ignore
232 | setOriginalFetch(fakeFetch);
233 | setActiveRequests(0);
234 | });
235 |
236 | it('should increase "activeRequests" when making a request and decrease on success, and init the progressBar', () => {
237 | fakeFetch.mockReturnValue(Promise.resolve({ status: 200 }));
238 | expect(activeRequests).toBe(0);
239 |
240 | const result = progressBarFetch('/hello-world');
241 |
242 | expect(activeRequests).toBe(1);
243 | expect(progressBarRef.moveToInit).toHaveBeenCalledTimes(1);
244 | expect(fakeFetch).toHaveBeenCalledTimes(1);
245 | expect(fakeFetch).toHaveBeenCalledWith('/hello-world', undefined);
246 |
247 | result.then(({ status }) => {
248 | expect(status).toBe(200);
249 | expect(activeRequests).toBe(0);
250 | });
251 | });
252 |
253 | it('should increase "activeRequests" when making a request and decrease on failure, and init the progressBar', () => {
254 | fakeFetch.mockReturnValue(Promise.reject({ status: 500 }));
255 | expect(activeRequests).toBe(0);
256 |
257 | const result = progressBarFetch('/goodbye-world', { method: 'POST' });
258 |
259 | expect(activeRequests).toBe(1);
260 | expect(progressBarRef.moveToInit).toHaveBeenCalledTimes(1);
261 | expect(fakeFetch).toHaveBeenCalledTimes(1);
262 | expect(fakeFetch).toHaveBeenCalledWith('/goodbye-world', {
263 | method: 'POST',
264 | });
265 |
266 | result.catch(({ status }) => {
267 | expect(status).toBe(500);
268 |
269 | setTimeout(() => {
270 | expect(activeRequests).toBe(0);
271 | }, 10);
272 | });
273 | });
274 |
275 | it('should not init the progressBar when the ref is not defined', () => {
276 | const progressBar = shallow();
277 | const componentDidMount = progressBar.instance().componentDidMount;
278 | componentDidMount.bind(undefined)();
279 |
280 | fakeFetch.mockReturnValue(Promise.resolve());
281 | progressBarFetch('/goodbye-world', { method: 'POST' });
282 |
283 | expect(progressBarRef).toBe(undefined);
284 | });
285 | });
286 |
--------------------------------------------------------------------------------