├── .gitignore
├── LICENSE.md
├── README.md
├── unit-testing-jest-enzyme
├── .gitignore
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── style
│ │ └── bootstrap.min.css
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── actions
│ ├── index.js
│ ├── index.test.js
│ └── types.js
│ ├── components
│ ├── comment_box.js
│ ├── comment_box.test.js
│ ├── comment_list.js
│ └── comment_list.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reducers
│ ├── comments.js
│ ├── comments.test.js
│ └── index.js
│ └── registerServiceWorker.js
└── unit-testing-mocha-chai
├── .babelrc
├── .gitignore
├── favicon.ico
├── index.html
├── logo.svg
├── manifest.json
├── package-lock.json
├── package.json
├── src
├── actions
│ ├── index.js
│ └── types.js
├── components
│ ├── app.js
│ ├── comment_box.js
│ └── comment_list.js
├── index.js
├── reducers
│ ├── comments.js
│ └── index.js
└── registerServiceWorker.js
├── style
├── bootstrap.min.css
└── style.css
├── test
├── actions
│ └── index_test.js
├── components
│ ├── app_test.js
│ ├── comment_box_test.js
│ └── comment_list_test.js
├── reducers
│ └── comments_test.js
└── test_helper.js
├── webpack.config.dev.js
└── webpack.config.prod.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .temp
3 | .idea
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Holger Kraatz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sorry !! Gosh ... 4 years went by.
2 | I hope to find time soon to update this tutorial.
3 |
4 | # React Unit Testing
5 |
6 | This tutorial shall help setting up Unit Tests for your React/Redux Application.
7 |
8 | We will do Unit Testing on React Components and the Redux State within these 2 Stacks:
9 |
10 | 1. Mocha/Chai (our own Stack)
11 |
12 | 2. Jest/Enzyme (Create React App Starter Kit: Jest is included)
13 |
14 |
15 | *Notes:*
16 | - Unit Tests on Redux:
17 |
18 | - Most Redux code are functions anyway, so you don't need to mock anything.
19 | - Test your Action Creators, Reducers, and the resulting application state by passing test values/no values/wrong values into/through them.
20 | - Docs: https://redux.js.org/recipes/writing-tests
21 |
22 | - Unit Tests on React Components:
23 | - Should focus on bullet-proof answers of these questions:
24 | - is component's content (hierarchy) rendered correctly (findable in DOM) and contains ALL necessary children?
25 | - is component's content really NOT rendered if I wanted to hide it?
26 | - have passed Props the right impact?
27 | - are passed Props forwarded down to the component's child if the child is the recipient of the Props?
28 | - is the component's state (NOT application state! This is Redux!) updated correctly and holds the right state after an event/passed Props?
29 | - are CSS classes/selectors attached to the component?
30 | - is Dynamic Styling working as expected?
31 | - do lifecycle events work correctly, like componentDidMount, componentWillUnmount?
32 | - and surely some more
33 |
34 | - Should NOT focus on the business logic of your React Components as business logic should be pulled out of your React Components.
35 | - Jest Snapshots can save time by just comparing the rendered Outputs of Test A (actual test) and Test B (test which ran immediately before):
36 | - Jest will "complain/inform" if the snapshot differs from the previous test, so you will be reminded to doublecheck the differences and, if you did a change on purpose, you can run jest with the -u flag (to update the previous shapshot with the actual one).
37 | - Installation: `npm install --save-dev react-test-renderer`
38 | - Links:
39 | - https://reactjs.org/docs/test-renderer.html
40 | - https://github.com/facebook/react/tree/master/packages/react-test-renderer
41 |
42 |
43 | - Testing Interactions between Redux and React Components:
44 |
45 | - This is already an Integration Test, but:
46 | - We will cover it shortly below: [Integration Tests between React Component and the Redux State](#chapter3) :-)
47 |
48 | ### IMPORTANT CAUTION:
49 | The npm packages might have discovered vulnerabilities by now.
50 | So do not use the combination of our npm versions in your production environment, but within a test environment instead that is sandboxed from your production network.
51 | Unfortunately the author has no time right now to always keep the npm packages at their latest versions and ensure that their combination still plays successfully together. The tuturial is supposed to show how it works and should help building working prototypes to make life easier for you.
52 |
53 | ## Table of Contents
54 |
55 | 1. [Getting started](#chapter1)
56 | 2. [Two Test Stacks](#chapter2)
57 | 1. [Unit Testing with Stack `Webpack`, `Babel`, `Mocha`, `Chai`](#chapter2a)
58 | 1. [Unit Testing React Components](#chapter2a1)
59 | 2. [Unit Testing the Redux State](#chapter2a2)
60 | 2. [Unit Testing with `Create React App` Starter Kit (Integrated: `Webpack`, `Babel`, `Jest`) plus `Enzyme`](#chapter2b)
61 | 1. [Unit Testing React Components](#chapter2b1)
62 | 2. [Unit Testing the Redux State](#chapter2b2)
63 | 3. [Integration Tests between React Component and the Redux State](#chapter3)
64 | 4. [Links](#chapter4)
65 |
66 |
67 | ## 1. Getting started
68 |
69 | ### Testing in general
70 |
71 | Besides Unit Tests, there are Integration Tests, End-to-end Tests (E2E), and User Acceptance Tests.
72 | These are the main tests in software development. If you want to check out other tests, go here: https://en.wikipedia.org/wiki/Software_testing
73 |
74 | This is the usual order of testing BEFORE delivering a new software or software version to the client:
75 |
76 | 1. Unit Tests:
77 | - A single piece of code (usually a function or a class/object) is tested, separate from other pieces.
78 | - Unit Tests are very helpful if you need to change your code: If your set of Unit Tests verify that your code works, you can safely change your code and have confidence that the other parts of your program will still work as expected.
79 | - Unit Tests wording can be used for documentation.
80 | - Save time because you don't have to always repeat manual testing after working on your code.
81 | - Examples (in general, not React specific):
82 | - Testing a function without passing any argument
83 | - Testing a function by passing a wrong argument type
84 | - Passing the correct argument type to a function: Testing if the outcome is valid
85 | - Testing if function returns a valid JSON structure
86 |
87 | TDD: TDD (Test-Driven Development / or Design) was developed out of Unit Testing:
88 | - Some quotes:
89 | - "Keep it simple, stupid!" (KISS)
90 | - "You aren't gonna need it!" (YAGNI)
91 | - You can better focus on a demanded requirement by writing very specific test cases first. This way you are forced to work and concentrate on the demanded requirement only, NOT on functionality that is NOT proven to meet the requirement or was never demanded by the client (see MVP: https://en.wikipedia.org/wiki/Minimum_viable_product).
92 | - By focusing on writing only the code necessary to pass the tests, designs can often be cleaner and clearer than it is achieved by other methods.
93 | - TDD Cycle:
94 | 1. Add a test
95 | 2. Run all tests and new test will fail as code not written yet: This way we check if the new test really works
96 | 3. Write the code
97 | 4. Run tests: If all test cases pass, go to next step
98 | 5. Refactor code: Clean up
99 | - Critics:
100 | - The difficult thing about TDD for many developers is that you have to write tests BEFORE writing any code.
101 | - If you're the client and the developer in one person, and you're not quite sure yet how your software should convince and appear to users, better focus on options, brainstorming, and branding than on writing test cases.
102 |
103 | BDD: BDD (Behaviour-Driven Development / or Design) was developed out of TDD:
104 | - Sets focus on User Stories and their scenarios/events.
105 | - Sets more focus on the wording for better understanding and documentation.
106 | - Therefore makes it easier to show test results to the business side to make the client happy - and yourself.
107 |
108 | 2. Integration Tests:
109 | - Not a single function like above, but logical groups (e.g. a login component, or a Service/API to handle authentication) are tested to see, if they (still) work together successfully.
110 | - To simulate user actions in Node.js, NOT needing a real browser, we will use jsdom module, which is a great DOM implementation in Node.js and gives us a "fake" DOM entirely in JavaScript to run our automated tests against.
111 | - Examples (Apps in general, not React specific):
112 | - Testing if a login component running on a browser connects to a Service/API running on a server successfully.
113 | - Testing if a Service/API returns data from a database successfully.
114 | - Testing if a message component running in a progressive web app returns a success message from the database after writing to it.
115 |
116 | 3. End-to-end Tests (E2E):
117 | - End-to-end Test make sure that ALL logical groups of an application and all Third-Party Software work together as expected.
118 | - The application should be tested with production data under real-time (stress) conditions against the test setup in order to do check performance as well.
119 | - The name End-to-end means that the communication and data transfer of the application works as expected from the one end (the client interface) throughout the other end (the database or another client interface) and vice versa.
120 | - Also called System Test
121 | - Examples (Apps in general, not React specific):
122 | - Testing ALL Use Cases/User Stories:
123 | - User signs up, creating an account and getting Welcome message on screen / receiving an email to confirm.
124 | - User signs in, getting Welcome Back message and New Offers on screen.
125 | - User logs out, getting Good-Bye message.
126 | - User adds item to shopping cart to prepare checkout and to continue shopping.
127 | - User checks out.
128 | - User cancels account.
129 | - ...
130 | - Testing Performance
131 | - Testing Security
132 |
133 |
134 | #### When all tests above are successful:
135 |
136 | 4. User Acceptance Tests:
137 | - The Client/Purchaser/Sponsor is involved.
138 | - Many manual / decision making steps.
139 | - Questions:
140 | - Are all requirements of a specification or contract met?
141 | - Is performance good enough?
142 | - Did QA Department test edge cases manually?
143 | - Did QA Department test security manually?
144 | - Is Software/New Version ready to be released/integrated into IT landscape?
145 | - Regression Tests successfully after integrating New Version into IT landscape? Do all previous features still work as expected?
146 |
147 |
148 | Ok, now let's dive into Unit Testing using BDD.
149 |
150 | ## 2. Two Test Stacks
151 |
152 | We want to show Unit Testing along these two BDD frameworks, but with the same outcome:
153 |
154 | 1. Mocha/Chai
155 |
156 | 2. Jest/Enzyme
157 |
158 | It will of course depend on your stack and your preference which framework to use.
159 |
160 | *Note:*
161 | - For a list of other Unit Test JavaScript frameworks, see https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScript
162 |
163 | ## i. Unit Testing with Stack `Webpack`, `Babel`, `Mocha`, `Chai`
164 |
165 | ### A Note to Mocha/Chai:
166 |
167 | Mocha is the container in which Chai code is running.
168 |
169 | ### Preparation
170 |
171 | First install all dependencies:
172 |
173 | ```
174 | > cd unit-testing-mocha-chai
175 | > npm install
176 | ```
177 |
178 | When done, run the tests:
179 |
180 | ```
181 | > npm run test
182 | ```
183 |
184 | The outcome will be:
185 |
186 | ```
187 | > npm run test
188 |
189 | > testing-with-webpack-and-chai-mocha@1.0.0 test /Users/piano/Desktop/Projects/react-testing/unit-testing-mocha-chai
190 | > mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test
191 |
192 | actions
193 | saveComment
194 | ✓ has the correct type
195 | ✓ has the correct payload
196 |
197 | App
198 | ✓ shows comment box
199 | ✓ shows comment list
200 |
201 | CommentBox
202 | ✓ has the correct class
203 | ✓ has a text area
204 | ✓ has a button
205 | entering some text
206 | ✓ shows that text in the textarea
207 | ✓ when submitted, clears the input
208 |
209 | CommentList
210 | ✓ shows an LI for each comment
211 | ✓ shows each comment that is provided
212 |
213 | Comments Reducer
214 | ✓ handles action with unknown type
215 | ✓ SAVE_COMMENT
216 |
217 |
218 | 13 passing (943ms)
219 |
220 | >
221 | ```
222 |
223 | To add a watcher to always re-run all tests after a change in code:
224 |
225 | ```
226 | > npm run test:watch
227 | ```
228 |
229 | ### a. Unit Testing React Components
230 |
231 | As example we will dive into the simple Unit Test of the App Component and play with it.
232 |
233 | Let's explore `test/components/app_test.js`:
234 |
235 | ```
236 | import { renderComponent, expect } from '../test_helper';
237 | import App from '../../src/components/app';
238 |
239 | // Use 'describe' to group together similar tests
240 | describe('App', () => {
241 |
242 | let component;
243 |
244 | // First create an instance of App
245 | beforeEach(() => {
246 | component = renderComponent(App);
247 | });
248 |
249 | // Use 'it' to test a single attribute of a target
250 | it('shows comment box', () => {
251 |
252 | // Use 'expect' to make an 'assertion' about a target
253 | expect(component.find('.comment-box')).to.exist;
254 | });
255 |
256 | it('shows comment list', () =>{
257 | expect(component.find('.comment-list')).to.exist;
258 | });
259 | });
260 | ```
261 |
262 | 1. First we import the test_helper.js file to enable Mocha/Chai and to render the Component.
263 | 2. We need to also import `app.js` as well, but we leave it WITHOUT CommentList and CommentBox Components ...
264 |
265 | ```
266 |
267 |
268 | ```
269 |
270 | ... because we want our test to FAIL initially !
271 |
272 | ```
273 | import React, {Component} from 'react';
274 |
275 | class App extends Component {
276 | render() {
277 | return (
278 |
279 |
280 |
281 |
Welcome to React
282 |
283 |
284 |
285 |
286 | );
287 | }
288 | }
289 |
290 | export default App;
291 | ```
292 | 3. Description of Chai commands:
293 |
294 | - The `describe('App', () => {});` command groups together different tests within the same context, for better readability.
295 | - The `beforeEach(() => {})` command is called before EACH test = `it('attribute to test', () => {});` command is executed.
296 | - The `it('shows comment box', () => {});` command groups together the final Unit Test(s).
297 | - The `expect(component.find('.comment-box')).to.exist;` command executes the final Unit Test, testing our assertion that, in this case, a CommentBox exists within our App Component.
298 | - Check out http://chaijs.com/api/bdd for many many things you can test !
299 |
300 |
301 | 4. Next, we run the test, and the outcome will be:
302 |
303 | ```
304 | ...
305 |
306 | 11 passing (841ms)
307 | 2 failing
308 |
309 | 1) App
310 | shows comment box:
311 | AssertionError: expected undefined to exist
312 | at Context. (test/components/app_test.js:18:9)
313 |
314 | 2) App
315 | shows comment list:
316 | AssertionError: expected undefined to exist
317 | at Context. (test/components/app_test.js:22:9)
318 |
319 | npm ERR! code ELIFECYCLE
320 | npm ERR! errno 2
321 | npm ERR! testing-with-webpack-and-chai-mocha@1.0.0 test: `mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test`
322 | npm ERR! Exit status 2
323 | npm ERR!
324 | npm ERR! Failed at the testing-with-webpack-and-chai-mocha@1.0.0 test script.
325 | npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
326 |
327 | npm ERR! A complete log of this run can be found in:
328 | npm ERR! /Users/piano/.npm/_logs/2018-06-30T14_47_30_101Z-debug.log
329 | >
330 | ```
331 |
332 | 5. Put back CommentList and CommentBox Components in `app.js` and all tests will again pass.
333 |
334 | For more Unit Tests on React Components, see
335 | - the other test files in `test/components` folder and
336 | - this great tutorial: https://medium.freecodecamp.org/the-right-way-to-test-react-components-548a4736ab22
337 |
338 | ### b. Unit Testing the Redux State
339 |
340 | As second example we'll check if our Reducer `src/reducers/comments.js` works correctly.
341 | We do it through test file `test/reducers/comments_test.js`:
342 |
343 | Here are 2 things we want to test:
344 |
345 | - Will reducer return an empty array as new state (an empty array to not to break anything) if we pass it a state of undefined?
346 | - Will action-payload of 'new comment' really return 'new comment' as new state?
347 |
348 | ```
349 | describe('Comments Reducer', () => {
350 |
351 | // in case there is a weird input, we react with the default (initial) state
352 | it('handles action with unknown type', () => {
353 |
354 | expect(commentReducer(undefined, {})).to.eql([]);
355 | });
356 |
357 | it('SAVE_COMMENT', () => {
358 |
359 | const action = { type: SAVE_COMMENT, payload: 'new comment'};
360 | expect(commentReducer([], action)).to.eql(['new comment']);
361 | });
362 |
363 | });
364 | ```
365 |
366 | For more Unit Tests on Redux, see
367 | - the action creator test file `test/actions/index_test.js`
368 | - Example: https://hackernoon.com/low-effort-high-value-integration-tests-in-redux-apps-d3a590bd9fd5
369 | - Docs: https://redux.js.org/recipes/writing-tests
370 |
371 | ## ii. Unit Testing with `Create React App` Starter Kit plus `Enzyme`
372 |
373 | First install all dependencies:
374 |
375 | ```
376 | > cd unit-testing-jest-enzyme
377 | > npm install
378 | ```
379 |
380 | *Note:*
381 | `npm install` will install the `Create React App` Starter Kit plus:
382 |
383 | - enzyme
384 | - enzyme-adapter-react-16
385 | - redux-mock-store
386 |
387 | `enzyme` and `enzyme-adapter-react-16` are modules of AirBnb and recommended by Facebook. They are needed to easier mount your React Components, also enabling to connect your Redux Store.
388 |
389 | `redux-mock-store` will be installed to give you the option to use a mock Redux Store, independent of your own Redux Store. This may make sense in some scenarios.
390 |
391 | When done, run the tests:
392 |
393 | ```
394 | > npm run test
395 | ```
396 |
397 | Jest will ask you:
398 |
399 | ```
400 | Press `a` to run all tests, or run Jest with `--watchAll`.
401 |
402 | Watch Usage
403 | › Press a to run all tests.
404 | › Press p to filter by a filename regex pattern.
405 | › Press t to filter by a test name regex pattern.
406 | › Press q to quit watch mode.
407 | › Press Enter to trigger a test run.
408 | ```
409 |
410 | So press `a` to run all tests, and the outcome will be:
411 |
412 | ```
413 | PASS src/components/comment_box.test.js
414 | CommentBox
415 | ✓ has the correct class (36ms)
416 | ✓ has a text area (6ms)
417 | ✓ has a button (2ms)
418 | entering some text
419 | ✓ shows that text in the textarea (9ms)
420 | ✓ when submitted, clears the input (6ms)
421 |
422 | PASS src/App.test.js
423 | App
424 | ✓ shows comment box (15ms)
425 | ✓ shows comment list (5ms)
426 |
427 | PASS src/components/comment_list.test.js
428 | CommentList
429 | ✓ shows an LI for each comment (6ms)
430 | ✓ shows each comment that is provided (3ms)
431 |
432 | PASS src/reducers/comments.test.js
433 | Comments Reducer
434 | ✓ handles action with unknown type (2ms)
435 | ✓ SAVE_COMMENT (1ms)
436 |
437 | PASS src/actions/index.test.js
438 | actions
439 | saveComment
440 | ✓ has the correct type (3ms)
441 | ✓ has the correct payload (1ms)
442 |
443 | Test Suites: 5 passed, 5 total
444 | Tests: 13 passed, 13 total
445 | Snapshots: 0 total
446 | Time: 1.616s, estimated 2s
447 | Ran all test suites.
448 |
449 | Watch Usage: Press w to show more.
450 | ```
451 |
452 | You are in watch mode now, which means that anytime you do a JavaScript code change in any file under root,
453 | the test will automatically re-run.
454 |
455 | If you want to see the overall coverage of files tested vs. NOT tested, add flag `--coverage` to package.json:
456 |
457 | ```
458 | "test": "react-scripts test --env=jsdom --verbose --coverage"
459 | ```
460 |
461 | This will create a coverage folder under root.
462 |
463 | The additional output in the Terminal will look like this:
464 |
465 | ```
466 | ---------------------------|----------|----------|----------|----------|-------------------|
467 | File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
468 | ---------------------------|----------|----------|----------|----------|-------------------|
469 | All files | 26.76 | 7.41 | 38.46 | 40.91 | |
470 | src | 1.89 | 0 | 5.88 | 3.7 | |
471 | App.js | 100 | 100 | 100 | 100 | |
472 | index.js | 0 | 0 | 0 | 0 |... 6,7,8,10,12,18 |
473 | registerServiceWorker.js | 0 | 0 | 0 | 0 |... 36,137,138,139 |
474 | src/actions | 100 | 100 | 100 | 100 | |
475 | index.js | 100 | 100 | 100 | 100 | |
476 | types.js | 100 | 100 | 100 | 100 | |
477 | src/components | 100 | 100 | 100 | 100 | |
478 | comment_box.js | 100 | 100 | 100 | 100 | |
479 | comment_list.js | 100 | 100 | 100 | 100 | |
480 | src/reducers | 100 | 100 | 100 | 100 | |
481 | comments.js | 100 | 100 | 100 | 100 | |
482 | index.js | 100 | 100 | 100 | 100 | |
483 | ---------------------------|----------|----------|----------|----------|-------------------|
484 | >
485 | ```
486 |
487 | ### a. Unit Testing React Components
488 |
489 | As example we will show again the Unit Test of the App Component: `src/App.test.js`
490 |
491 | ```
492 | import React from 'react';
493 | import App from './App';
494 | import { mount } from 'enzyme';
495 |
496 | // We need to wrap CommentBox with tag in first beforeEach(() => {}) below;
497 | // otherwise we receive this error message:
498 | // Invariant Violation: Could not find “store” in either the context or props of “Connect(CommentBox)”
499 | // https://stackoverflow.com/questions/36211739/invariant-violation-could-not-find-store-in-either-the-context-or-props-of-c
500 | // Also see comment_list.test.js
501 | import {configure} from 'enzyme';
502 | import Adapter from 'enzyme-adapter-react-16';
503 | import {Provider} from "react-redux";
504 | import {createStore, applyMiddleware} from 'redux';
505 | import reducers from './reducers';
506 |
507 | configure({adapter: new Adapter()});
508 | const createStoreWithMiddleware = applyMiddleware()(createStore);
509 |
510 | // Use 'describe' to group together similar tests
511 | describe('App', () => {
512 |
513 | let component;
514 |
515 | beforeEach(() => {
516 | component = mount();
517 | });
518 |
519 | // Use 'test' or it' (both possible) to test a single attribute of a target
520 | test('shows comment box', () => {
521 |
522 | expect(component.find('.comment-box').length).toBe(1);
523 | });
524 |
525 | test('shows comment list', () => {
526 | expect(component.find('.comment-list').length).toBe(1);
527 | });
528 | });
529 | ```
530 |
531 | Again, for more Unit Tests on React Components, see
532 | - the other test files in `src/components` folder with suffix `.test.js` and
533 | - this great tutorial: https://medium.freecodecamp.org/the-right-way-to-test-react-components-548a4736ab22
534 |
535 | ### b. Unit Testing the Redux State
536 |
537 | As second example again we'll check if our Reducer `src/reducers/comments.js` works correctly.
538 | We do it through test file `src/reducers/comments.test.js`:
539 |
540 |
541 | ```
542 | import commentReducer from './comments';
543 | import { SAVE_COMMENT } from '../actions/types';
544 |
545 | describe('Comments Reducer', () => {
546 |
547 | // in case there is a weird input, we react with the default state
548 | test('handles action with unknown type', () => {
549 |
550 | expect(commentReducer(undefined, {})).toEqual([]);
551 | });
552 |
553 | test('SAVE_COMMENT', () => {
554 |
555 | const action = { type: SAVE_COMMENT, payload: 'new comment'};
556 | expect(commentReducer([], action)).toEqual(['new comment']);
557 |
558 | });
559 |
560 | });
561 | ```
562 |
563 | Again, for more Unit Tests on Redux, see
564 | - the action creator test file `src/actions/index.test.js`
565 | - Example: https://hackernoon.com/low-effort-high-value-integration-tests-in-redux-apps-d3a590bd9fd5
566 | - Docs: https://redux.js.org/recipes/writing-tests
567 |
568 | ## 3. Integration Tests between React Component and the Redux State
569 |
570 | In the `CommentBox Component` and `CommentList Component` you might have discovered some Integration Tests already.
571 |
572 | Let's have a look at `CommentBox Component`:
573 |
574 | ### Mocha/Chai:
575 |
576 | ```
577 | describe('entering some text', () => {
578 |
579 | beforeEach(() => {
580 |
581 | component.find('textarea').simulate('change', 'new comment');
582 | });
583 |
584 | it('shows that text in the textarea', () => {
585 |
586 | expect(component.find('textarea')).to.have.value('new comment');
587 | });
588 |
589 | it('when submitted, clears the input', () => {
590 |
591 | component.simulate('submit');
592 | expect(component.find('textarea')).to.have.value('');
593 | });
594 | });
595 | ```
596 |
597 | ### Jest/Enzyme:
598 |
599 | ```
600 | describe('entering some text', () => {
601 |
602 | beforeEach(() => {
603 |
604 | component.find('textarea').simulate('change', {target: {value: 'new comment'}});
605 | });
606 |
607 | test('shows that text in the textarea', () => {
608 |
609 | expect(component.find('textarea').prop('value')).toEqual('new comment');
610 | });
611 |
612 | test('when submitted, clears the input', () => {
613 |
614 | component.simulate('submit');
615 | expect(component.find('textarea').prop('value')).toEqual('');
616 | });
617 | });
618 | ```
619 |
620 | What happens here:
621 |
622 | 1. `beforeEach(() => {})` injects the string `new comment` into the React Component's textarea BEFORE each following tests `test('...', () => {})` are executed.
623 | This causes an Update of the Redux State and therefore causes a Re-Render of the React Component.
624 | 2. Then we make an assertion whether the React Component got Re-Rendered as we expect within the `expect` statement.
625 | 3. The test will either pass or fail. In our case it will pass.
626 |
627 | So with Mocha/Chai and Jest/Enzyme we already have an Integration Test engine as we can accomplish even a full round-trip:
628 |
629 | React Component >> Redux State >> React Component
630 |
631 | #### Isn't that great ?!
632 |
633 | The reasons:
634 | - Everything in React is a Component, also the Redux Store Provider wrapped around our React Components.
635 | - Mocha/Chai and Jest/Enzyme render the entire React App into memory.
636 | - Within our Node.js Setup, `jsdom` module simulates a "fake" DOM for us, to simulate (user) interactions.
637 |
638 | Some manuals:
639 | - https://medium.freecodecamp.org/real-integration-tests-with-react-redux-and-react-router-417125212638
640 | - http://engineering.pivotal.io/post/react-integration-tests-with-enzyme/
641 | - https://medium.com/homeaway-tech-blog/integration-testing-in-react-21f92a55a894
642 |
643 |
644 | ### What's next ?
645 |
646 | Next step are End-to-end Tests. If you want to stay within Jest ecosystem, you can think of adding Puppeteer as your browser engine for testing user interactions:
647 | - Puppeteer (Headless Chrome Node API): https://github.com/GoogleChrome/puppeteer
648 | - Jest + Puppeteer: https://blog.logrocket.com/end-to-end-testing-react-apps-with-puppeteer-and-jest-ce2f414b4fd7
649 |
650 | Of course there are other tools out there. Please check them out as well:
651 | - Selenium
652 | - TestCafé and
653 | - Cypress
654 |
655 | You'll find the links in the link list below.
656 |
657 | Happy Testing !
658 |
659 | ## 4. Links
660 |
661 | ### Have a look !
662 |
663 | Our React-Redux App:
664 | - Stephen Grider, Repo: https://github.com/StephenGrider/AdvancedReduxCode
665 | - Stephen Grider, Udemy Course "Advanced React and Redux": https://www.udemy.com/react-redux-tutorial
666 |
667 | Mocha/Chai:
668 | - https://github.com/chaijs/chai-jquery
669 | - http://chaijs.com/api/bdd
670 |
671 | Jest/Enzyme:
672 | - Jest:
673 | - https://github.com/facebook/jest
674 | - http://jestjs.io/docs/en/expect.html#content
675 | - Jest Snapshots (React Test Renderer):
676 | - https://reactjs.org/docs/test-renderer.html
677 | - https://github.com/facebook/react/tree/master/packages/react-test-renderer
678 | - Enzyme: https://airbnb.io/enzyme/docs/api/
679 | - Manuals:
680 | - https://www.sitepoint.com/test-react-components-jest/
681 | - https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#running-tests
682 | - https://hackernoon.com/low-effort-high-value-integration-tests-in-redux-apps-d3a590bd9fd5
683 |
684 | Unit Tests Redux:
685 | - Docs: https://redux.js.org/recipes/writing-tests
686 | - Example: https://hackernoon.com/low-effort-high-value-integration-tests-in-redux-apps-d3a590bd9fd5
687 |
688 | Unit Tests React Components:
689 | - Example: https://medium.freecodecamp.org/the-right-way-to-test-react-components-548a4736ab22
690 |
691 | Unit Tests General:
692 | - https://en.wikipedia.org/wiki/Unit_testing
693 | - https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#JavaScript
694 | - https://hackernoon.com/testing-your-frontend-code-part-ii-unit-testing-1d05f8d50859
695 | - https://codeutopia.net/blog/2015/03/01/unit-testing-tdd-and-bdd/
696 | - https://en.wikipedia.org/wiki/Behavior-driven_development
697 | - https://en.wikipedia.org/wiki/Test-driven_development
698 |
699 | Integration Tests:
700 | - https://hackernoon.com/testing-your-frontend-code-part-iv-integration-testing-f1f4609dc4d9
701 | - https://github.com/jsdom/jsdom
702 | - Manuals:
703 | - https://medium.freecodecamp.org/real-integration-tests-with-react-redux-and-react-router-417125212638
704 | - http://engineering.pivotal.io/post/react-integration-tests-with-enzyme/
705 | - https://medium.com/homeaway-tech-blog/integration-testing-in-react-21f92a55a894
706 |
707 | End-2-end Tests:
708 | - https://hackernoon.com/testing-your-frontend-code-part-iii-e2e-testing-e9261b56475
709 | - https://medium.freecodecamp.org/why-end-to-end-testing-is-important-for-your-team-cb7eb0ec1504
710 |
711 | End-2-end Testing Tools:
712 | - Selenium:
713 | - https://www.fullstackreact.com/30-days-of-react/day-26/
714 | - https://www.seleniumhq.org/
715 | - Jest + Puppeteer:
716 | - Puppeteer (Headless Chrome Node API): https://github.com/GoogleChrome/puppeteer
717 | - Jest + Puppeteer: https://blog.logrocket.com/end-to-end-testing-react-apps-with-puppeteer-and-jest-ce2f414b4fd7
718 | - TestCafé: https://github.com/DevExpress/testcafe
719 | - Cypress: https://www.cypress.io/
720 |
721 | Acceptance Tests:
722 | - https://en.wikipedia.org/wiki/Acceptance_testing
723 |
724 | Testing General:
725 | - https://en.wikipedia.org/wiki/Software_testing
726 |
727 |
728 | ### Credits to the authors of above links ! Thank you very much !
729 |
730 | ### And credits to the reader: Thanks for your visit !
731 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testing-with-cra-and-jest-enzyme",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "lodash": "^4.17.10",
7 | "react": "^16.4.1",
8 | "react-dom": "^16.4.1",
9 | "react-redux": "^5.0.7",
10 | "react-router": "^4.3.1",
11 | "react-scripts": "1.1.4",
12 | "redux": "^4.0.0"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test --env=jsdom --verbose",
18 | "eject": "react-scripts eject"
19 | },
20 | "devDependencies": {
21 | "enzyme": "^3.3.0",
22 | "enzyme-adapter-react-16": "^1.1.1",
23 | "redux-mock-store": "^1.5.3"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herrkraatz/react-unit-testing/38a2789ccc3fb0e91b368874af3d64eb580cff07/unit-testing-jest-enzyme/public/favicon.ico
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from { transform: rotate(0deg); }
27 | to { transform: rotate(360deg); }
28 | }
29 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import CommentBox from './components/comment_box';
3 | import CommentList from './components/comment_list';
4 | import logo from './logo.svg';
5 | import './App.css';
6 |
7 | class App extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
Welcome to React
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 | }
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from './App';
3 | import { mount } from 'enzyme';
4 |
5 | // We need to wrap CommentBox with tag in first beforeEach(() => {}) below;
6 | // otherwise we receive this error message:
7 | // Invariant Violation: Could not find “store” in either the context or props of “Connect(CommentBox)”
8 | // https://stackoverflow.com/questions/36211739/invariant-violation-could-not-find-store-in-either-the-context-or-props-of-c
9 | // Also see comment_list.test.js
10 | import {configure} from 'enzyme';
11 | import Adapter from 'enzyme-adapter-react-16';
12 | import {Provider} from "react-redux";
13 | import {createStore, applyMiddleware} from 'redux';
14 | import reducers from './reducers';
15 |
16 | configure({adapter: new Adapter()});
17 | const createStoreWithMiddleware = applyMiddleware()(createStore);
18 |
19 | // Use 'describe' to group together similar tests
20 | describe('App', () => {
21 |
22 | let component;
23 |
24 | beforeEach(() => {
25 | component = mount();
26 | });
27 |
28 | // Use 'test' or 'it' (both possible) to test a single attribute of a target
29 | test('shows comment box', () => {
30 |
31 | expect(component.find('.comment-box').length).toBe(1);
32 | });
33 |
34 | test('shows comment list', () => {
35 | expect(component.find('.comment-list').length).toBe(1);
36 | });
37 | });
38 |
39 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import { SAVE_COMMENT } from './types';
2 |
3 | // action creator:
4 | export function saveComment(comment){
5 | return {
6 | type: SAVE_COMMENT,
7 | payload: comment
8 | }
9 |
10 | }
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/actions/index.test.js:
--------------------------------------------------------------------------------
1 | import { SAVE_COMMENT } from './types';
2 | import { saveComment } from './';
3 |
4 | describe('actions', () => {
5 | describe('saveComment', () => {
6 | test('has the correct type', () => {
7 |
8 | // action creator returns an action
9 | const action = saveComment();
10 | expect(action.type).toEqual(SAVE_COMMENT);
11 | });
12 |
13 | test('has the correct payload', () => {
14 |
15 | const action = saveComment('new comment');
16 | expect(action.payload).toEqual('new comment');
17 | });
18 |
19 | });
20 |
21 | });
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const SAVE_COMMENT = 'save_comment';
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/components/comment_box.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | // to turn a component into a container to have access to the state:
4 | import { connect } from 'react-redux';
5 | // imports ALL action creators and stores them in variable actions
6 | import * as actions from '../actions';
7 |
8 | // not exporting our component any more, but exporting a container instead:
9 | // export default class CommentBox extends Component {
10 | class CommentBox extends Component {
11 | constructor(props) {
12 | super(props);
13 |
14 | this.state = { comment: '' };
15 | }
16 |
17 | handleChange(event){
18 | this.setState({ comment: event.target.value });
19 | }
20 |
21 | handleSubmit(event){
22 | // keep the form from submitting to itself
23 | event.preventDefault();
24 |
25 | // new after having wired up the Container with the action creators
26 | this.props.saveComment(this.state.comment);
27 | this.setState({ comment: ''});
28 | }
29 |
30 | render() {
31 | return (
32 |
39 | )
40 | }
41 | }
42 |
43 | // to turn a component into a container, but we need no state at all ! So no mapStateToProps here !
44 | // instead of mapDispatchToProps we insert actions here (shortcut)
45 | // this will bind all action creators to our CommentBox under this.props (e.g. this.props.saveComment)
46 | export default connect(null, actions)(CommentBox);
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/components/comment_box.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CommentBox from './comment_box';
3 | import { mount } from 'enzyme';
4 |
5 | // We need to wrap CommentBox with tag in first beforeEach(() => {}) below;
6 | // otherwise we receive this error message:
7 | // Invariant Violation: Could not find “store” in either the context or props of “Connect(CommentBox)”
8 | // https://stackoverflow.com/questions/36211739/invariant-violation-could-not-find-store-in-either-the-context-or-props-of-c
9 |
10 | // The Provider's Redux store can be either our own Redux store (Way 1), or a mock Redux store (Way 2)
11 | // You can choose which you like better (both are working)
12 |
13 | // Redux I, needed for both ways:
14 | import { configure } from 'enzyme';
15 | import Adapter from 'enzyme-adapter-react-16';
16 | import { Provider } from "react-redux";
17 | configure({ adapter: new Adapter() });
18 |
19 | // Redux II, way 1: Use our own Redux store
20 | import { createStore, applyMiddleware } from 'redux';
21 | import reducers from '../reducers';
22 | const createStoreWithMiddleware = applyMiddleware()(createStore);
23 |
24 | // Redux II, way 2: Use a mock Redux store
25 | import configureMockStore from "redux-mock-store";
26 | const mockStore = configureMockStore();
27 | const store = mockStore({});
28 |
29 | describe('CommentBox', () => {
30 |
31 | let component;
32 |
33 | beforeEach(() => {
34 |
35 | // Way 1: Use our own Redux store
36 | // component = mount();
37 |
38 | // Way 2: Use a mock Redux store
39 | component = mount();
40 | });
41 |
42 | test('has the correct class', () => {
43 | expect(component.find('.comment-box').length).toBe(1);
44 |
45 | });
46 |
47 | test('has a text area', () => {
48 |
49 | // Either:
50 | // expect(component.find('textarea').length).toBe(1);
51 | // Or:
52 | expect(component.find('textarea').exists()).toEqual(true);
53 | });
54 |
55 | test('has a button', () => {
56 |
57 | // Either:
58 | // expect(component.find('button').length).toBe(1);
59 | // Or:
60 | expect(component.find('button').exists()).toEqual(true);
61 | });
62 |
63 | describe('entering some text', () => {
64 |
65 | beforeEach(() => {
66 |
67 | component.find('textarea').simulate('change', {target: {value: 'new comment'}});
68 | });
69 |
70 | test('shows that text in the textarea', () => {
71 |
72 | expect(component.find('textarea').prop('value')).toEqual('new comment');
73 | });
74 |
75 | test('when submitted, clears the input', () => {
76 |
77 | component.simulate('submit');
78 | expect(component.find('textarea').prop('value')).toEqual('');
79 | });
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/components/comment_list.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // to turn a component into a container to have access to the state:
4 | import { connect } from 'react-redux';
5 |
6 | const CommentList = (props) => {
7 | const list = props.comments.map(comment =>
{comment}
);
8 | return (
9 |
{list}
10 | );
11 | };
12 |
13 | function mapStateToProps(state){
14 | return { comments: state.comments};
15 | }
16 |
17 | // to turn a component into a container:
18 | export default connect(mapStateToProps)(CommentList);
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/components/comment_list.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CommentList from './comment_list';
3 | import { mount } from 'enzyme';
4 |
5 | // We need to wrap CommentBox with tag in first beforeEach(() => {}) below;
6 | // otherwise we receive this error message:
7 | // Invariant Violation: Could not find “store” in either the context or props of “Connect(CommentBox)”
8 | // https://stackoverflow.com/questions/36211739/invariant-violation-could-not-find-store-in-either-the-context-or-props-of-c
9 |
10 | // The Provider's Redux store must be our own Redux store, not a mock Redux store as in comment_box.test.js because
11 | // we insert an initial state
12 | import { configure } from 'enzyme';
13 | import Adapter from 'enzyme-adapter-react-16';
14 | import { Provider } from "react-redux";
15 | import { createStore, applyMiddleware } from 'redux';
16 | import reducers from '../reducers';
17 |
18 | configure({ adapter: new Adapter() });
19 | const createStoreWithMiddleware = applyMiddleware()(createStore);
20 |
21 | describe('CommentList', () => {
22 |
23 | let component;
24 |
25 | beforeEach(() => {
26 |
27 | const initialState = { comments: ['New Comment', 'New Other Comment'] };
28 | component = mount();
29 | });
30 |
31 | test('shows an LI for each comment', () => {
32 |
33 | expect(component.find('li').length).toBe(2);
34 | });
35 |
36 | test('shows each comment that is provided', () => {
37 |
38 | expect(component.find('li').at(0).text()).toEqual('New Comment');
39 | expect(component.find('li').at(1).text()).toEqual('New Other Comment');
40 | });
41 |
42 | });
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
7 | .Test-container{
8 | width: 50%;
9 | margin: 10px auto 0;
10 | /*margin-top: 10px;*/
11 | }
12 |
13 | ul{
14 | width: 150px;
15 | margin: 10px auto 0;
16 | }
17 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { createStore, applyMiddleware } from 'redux';
5 | import './index.css';
6 | import App from './App';
7 | import reducers from './reducers';
8 | import registerServiceWorker from './registerServiceWorker';
9 |
10 | const createStoreWithMiddleware = applyMiddleware()(createStore);
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 | , document.getElementById('root'));
17 |
18 | registerServiceWorker();
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/reducers/comments.js:
--------------------------------------------------------------------------------
1 | import { SAVE_COMMENT } from '../actions/types';
2 |
3 | export default function(state = [], action){
4 |
5 | switch(action.type){
6 | case SAVE_COMMENT:
7 | // return state.concat([action.payload]);
8 | // better:
9 | return [ ...state, action.payload];
10 | }
11 | return state;
12 | }
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/reducers/comments.test.js:
--------------------------------------------------------------------------------
1 | import commentReducer from './comments';
2 | import { SAVE_COMMENT } from '../actions/types';
3 |
4 | describe('Comments Reducer', () => {
5 |
6 | // in case there is a weird input, we react with the default state
7 | test('handles action with unknown type', () => {
8 |
9 | expect(commentReducer(undefined, {})).toEqual([]);
10 | });
11 |
12 | test('SAVE_COMMENT', () => {
13 |
14 | const action = { type: SAVE_COMMENT, payload: 'new comment'};
15 | expect(commentReducer([], action)).toEqual(['new comment']);
16 |
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import commentsReducer from './comments';
3 |
4 | const rootReducer = combineReducers({
5 | comments: commentsReducer
6 | });
7 |
8 | export default rootReducer;
9 |
--------------------------------------------------------------------------------
/unit-testing-jest-enzyme/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015", "stage-1"]
3 | }
4 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .temp
3 | .idea
4 | node_modules
5 | dist
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herrkraatz/react-unit-testing/38a2789ccc3fb0e91b368874af3d64eb580cff07/unit-testing-mocha-chai/favicon.ico
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 | React App
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testing-with-webpack-and-chai-mocha",
3 | "version": "1.0.0",
4 | "description": "Great thanks to Stephen Grider's Redux Simple Starter Repo, and his Udemy Course 'Advanced React and Redux', which were the basis of this code",
5 | "main": "index.js",
6 | "repository": "git@github.com:herrkraatz/react-testing.git",
7 | "scripts": {
8 | "dev": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --config webpack.config.dev.js",
9 | "prod": "webpack --config webpack.config.prod.js && cp index.html dist && cp manifest.json dist && cp logo.svg dist && cp favicon.ico dist && cp -R style dist",
10 | "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test",
11 | "test:watch": "npm run test -- --watch"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "babel-core": "^6.26.3",
17 | "babel-loader": "^7.1.4",
18 | "babel-preset-env": "^1.7.0",
19 | "babel-preset-react": "^6.24.1",
20 | "chai": "^4.1.2",
21 | "chai-jquery": "^2.0.0",
22 | "jquery": "^3.3.1",
23 | "jsdom": "^11.11.0",
24 | "mocha": "^5.2.0",
25 | "react-addons-test-utils": "^15.6.2",
26 | "webpack": "^4.10.1",
27 | "webpack-cli": "^2.1.4",
28 | "webpack-dev-server": "^3.1.4"
29 | },
30 | "dependencies": {
31 | "babel-preset-stage-1": "^6.1.18",
32 | "lodash": "^4.17.10",
33 | "react": "^15.4.2",
34 | "react-dom": "^15.4.2",
35 | "react-redux": "^5.0.7",
36 | "react-router": "^4.3.1",
37 | "redux": "^4.0.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import { SAVE_COMMENT } from './types';
2 |
3 | // action creator:
4 | export function saveComment(comment){
5 | return {
6 | type: SAVE_COMMENT,
7 | payload: comment
8 | }
9 |
10 | }
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const SAVE_COMMENT = 'save_comment';
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/components/app.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import CommentBox from './comment_box';
3 | import CommentList from './comment_list';
4 |
5 | class App extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
Welcome to React
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/components/comment_box.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | // to turn a component into a container to have access to the state:
4 | import { connect } from 'react-redux';
5 | // imports ALL action creators and stores them in variable actions
6 | import * as actions from '../actions';
7 |
8 | // not exporting our component any more, but exporting a container instead:
9 | // export default class CommentBox extends Component {
10 | class CommentBox extends Component {
11 | constructor(props) {
12 | super(props);
13 |
14 | this.state = { comment: '' };
15 | }
16 |
17 | handleChange(event){
18 | this.setState({ comment: event.target.value });
19 | }
20 |
21 | handleSubmit(event){
22 | // keep the form from submitting to itself
23 | event.preventDefault();
24 |
25 | // new after having wired up the Container with the action creators
26 | this.props.saveComment(this.state.comment);
27 | this.setState({ comment: ''});
28 | }
29 |
30 | render() {
31 | return (
32 |
39 | )
40 | }
41 | }
42 |
43 | // to turn a component into a container, but we need no state at all ! So no mapStateToProps here !
44 | // instead of mapDispatchToProps we insert actions here (shortcut)
45 | // this will bind all action creators to our CommentBox under this.props (e.g. this.props.saveComment)
46 | export default connect(null, actions)(CommentBox);
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/components/comment_list.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // to turn a component into a container to have access to the state:
4 | import { connect } from 'react-redux';
5 |
6 | const CommentList = (props) => {
7 | const list = props.comments.map(comment =>
{comment}
);
8 | return (
9 |
{list}
10 | );
11 | };
12 |
13 | function mapStateToProps(state){
14 | return { comments: state.comments};
15 | }
16 |
17 | // to turn a component into a container:
18 | export default connect(mapStateToProps)(CommentList);
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { createStore, applyMiddleware } from 'redux';
5 |
6 | import App from './components/app';
7 | import reducers from './reducers';
8 | import registerServiceWorker from './registerServiceWorker';
9 |
10 | const createStoreWithMiddleware = applyMiddleware()(createStore);
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 | , document.querySelector('.container'));
17 |
18 | registerServiceWorker();
19 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/reducers/comments.js:
--------------------------------------------------------------------------------
1 | import { SAVE_COMMENT } from '../actions/types';
2 |
3 | export default function(state = [], action){
4 |
5 | switch(action.type){
6 | case SAVE_COMMENT:
7 | // return state.concat([action.payload]);
8 | // better:
9 | return [ ...state, action.payload];
10 | }
11 | return state;
12 | }
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import commentsReducer from './comments';
3 |
4 | const rootReducer = combineReducers({
5 | // state: (state = {}) => state
6 | // for comment list to work: make an array out of it
7 | // dummy reducer:
8 | // comments: (state = []) => state
9 | comments: commentsReducer
10 | });
11 |
12 | export default rootReducer;
13 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/style/bootstrap.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.0.0-alpha.2 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active{outline:0}a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*,::after,::before{text-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}@viewport{width:device-width}html{font-size:16px;-webkit-tap-highlight-color:transparent}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:1rem;line-height:1.5;color:#373a3c;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #818a91}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}pre{margin-top:0;margin-bottom:1rem}figure{margin:0 0 1rem}img{vertical-align:middle}[role=button]{cursor:pointer}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#818a91;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,select,textarea{margin:0;line-height:inherit;border-radius:0}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit}input[type=search]{-webkit-box-sizing:inherit;box-sizing:inherit;-webkit-appearance:none}output{display:inline-block}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1{font-size:2.5rem}h2{font-size:2rem}h3{font-size:1.75rem}h4{font-size:1.5rem}h5{font-size:1.25rem}h6{font-size:1rem}.h1{font-size:2.5rem}.h2{font-size:2rem}.h3{font-size:1.75rem}.h4{font-size:1.5rem}.h5{font-size:1.25rem}.h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300}.display-2{font-size:5.5rem;font-weight:300}.display-3{font-size:4.5rem;font-weight:300}.display-4{font-size:3.5rem;font-weight:300}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:5px}.dl-horizontal{margin-right:-1.875rem;margin-left:-1.875rem}.dl-horizontal::after{display:table;clear:both;content:""}.initialism{font-size:90%;text-transform:uppercase}.blockquote{padding:.5rem 1rem;margin-bottom:1rem;font-size:1.25rem;border-left:.25rem solid #eceeef}.blockquote-footer{display:block;font-size:80%;line-height:1.5;color:#818a91}.blockquote-footer::before{content:"\2014 \00A0"}.blockquote-reverse{padding-right:1rem;padding-left:0;text-align:right;border-right:.25rem solid #eceeef;border-left:0}.blockquote-reverse .blockquote-footer::before{content:""}.blockquote-reverse .blockquote-footer::after{content:"\00A0 \2014"}.carousel-inner>.carousel-item>a>img,.carousel-inner>.carousel-item>img,.img-fluid{display:block;max-width:100%;height:auto}.img-rounded{border-radius:.3rem}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:.25rem;line-height:1.5;background-color:#fff;border:1px solid #ddd;border-radius:.25rem;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#818a91}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:.2rem .4rem;font-size:90%;color:#bd4147;background-color:#f7f7f9;border-radius:.25rem}kbd{padding:.2rem .4rem;font-size:90%;color:#fff;background-color:#333;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;margin-top:0;margin-bottom:1rem;font-size:90%;line-height:1.5;color:#373a3c}pre code{padding:0;font-size:inherit;color:inherit;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:.9375rem;padding-left:.9375rem;margin-right:auto;margin-left:auto}.container::after{display:table;clear:both;content:""}@media (min-width:544px){.container{max-width:576px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:940px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{padding-right:.9375rem;padding-left:.9375rem;margin-right:auto;margin-left:auto}.container-fluid::after{display:table;clear:both;content:""}.row{margin-right:-.9375rem;margin-left:-.9375rem}.row::after{display:table;clear:both;content:""}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:.9375rem;padding-left:.9375rem}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-1{width:8.333333%}.col-xs-2{width:16.666667%}.col-xs-3{width:25%}.col-xs-4{width:33.333333%}.col-xs-5{width:41.666667%}.col-xs-6{width:50%}.col-xs-7{width:58.333333%}.col-xs-8{width:66.666667%}.col-xs-9{width:75%}.col-xs-10{width:83.333333%}.col-xs-11{width:91.666667%}.col-xs-12{width:100%}.col-xs-pull-0{right:auto}.col-xs-pull-1{right:8.333333%}.col-xs-pull-2{right:16.666667%}.col-xs-pull-3{right:25%}.col-xs-pull-4{right:33.333333%}.col-xs-pull-5{right:41.666667%}.col-xs-pull-6{right:50%}.col-xs-pull-7{right:58.333333%}.col-xs-pull-8{right:66.666667%}.col-xs-pull-9{right:75%}.col-xs-pull-10{right:83.333333%}.col-xs-pull-11{right:91.666667%}.col-xs-pull-12{right:100%}.col-xs-push-0{left:auto}.col-xs-push-1{left:8.333333%}.col-xs-push-2{left:16.666667%}.col-xs-push-3{left:25%}.col-xs-push-4{left:33.333333%}.col-xs-push-5{left:41.666667%}.col-xs-push-6{left:50%}.col-xs-push-7{left:58.333333%}.col-xs-push-8{left:66.666667%}.col-xs-push-9{left:75%}.col-xs-push-10{left:83.333333%}.col-xs-push-11{left:91.666667%}.col-xs-push-12{left:100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.333333%}.col-xs-offset-2{margin-left:16.666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.333333%}.col-xs-offset-5{margin-left:41.666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.333333%}.col-xs-offset-8{margin-left:66.666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.333333%}.col-xs-offset-11{margin-left:91.666667%}.col-xs-offset-12{margin-left:100%}@media (min-width:544px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-1{width:8.333333%}.col-sm-2{width:16.666667%}.col-sm-3{width:25%}.col-sm-4{width:33.333333%}.col-sm-5{width:41.666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333%}.col-sm-8{width:66.666667%}.col-sm-9{width:75%}.col-sm-10{width:83.333333%}.col-sm-11{width:91.666667%}.col-sm-12{width:100%}.col-sm-pull-0{right:auto}.col-sm-pull-1{right:8.333333%}.col-sm-pull-2{right:16.666667%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.333333%}.col-sm-pull-5{right:41.666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.333333%}.col-sm-pull-8{right:66.666667%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.333333%}.col-sm-pull-11{right:91.666667%}.col-sm-pull-12{right:100%}.col-sm-push-0{left:auto}.col-sm-push-1{left:8.333333%}.col-sm-push-2{left:16.666667%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.333333%}.col-sm-push-5{left:41.666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.333333%}.col-sm-push-8{left:66.666667%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.333333%}.col-sm-push-11{left:91.666667%}.col-sm-push-12{left:100%}.col-sm-offset-0{margin-left:0}.col-sm-offset-1{margin-left:8.333333%}.col-sm-offset-2{margin-left:16.666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.333333%}.col-sm-offset-5{margin-left:41.666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.333333%}.col-sm-offset-8{margin-left:66.666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.333333%}.col-sm-offset-11{margin-left:91.666667%}.col-sm-offset-12{margin-left:100%}}@media (min-width:768px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-1{width:8.333333%}.col-md-2{width:16.666667%}.col-md-3{width:25%}.col-md-4{width:33.333333%}.col-md-5{width:41.666667%}.col-md-6{width:50%}.col-md-7{width:58.333333%}.col-md-8{width:66.666667%}.col-md-9{width:75%}.col-md-10{width:83.333333%}.col-md-11{width:91.666667%}.col-md-12{width:100%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.333333%}.col-md-pull-2{right:16.666667%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.333333%}.col-md-pull-5{right:41.666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.333333%}.col-md-pull-8{right:66.666667%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.333333%}.col-md-pull-11{right:91.666667%}.col-md-pull-12{right:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.333333%}.col-md-push-2{left:16.666667%}.col-md-push-3{left:25%}.col-md-push-4{left:33.333333%}.col-md-push-5{left:41.666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.333333%}.col-md-push-8{left:66.666667%}.col-md-push-9{left:75%}.col-md-push-10{left:83.333333%}.col-md-push-11{left:91.666667%}.col-md-push-12{left:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.333333%}.col-md-offset-2{margin-left:16.666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.333333%}.col-md-offset-5{margin-left:41.666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.333333%}.col-md-offset-8{margin-left:66.666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.333333%}.col-md-offset-11{margin-left:91.666667%}.col-md-offset-12{margin-left:100%}}@media (min-width:992px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-1{width:8.333333%}.col-lg-2{width:16.666667%}.col-lg-3{width:25%}.col-lg-4{width:33.333333%}.col-lg-5{width:41.666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333%}.col-lg-8{width:66.666667%}.col-lg-9{width:75%}.col-lg-10{width:83.333333%}.col-lg-11{width:91.666667%}.col-lg-12{width:100%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.333333%}.col-lg-pull-2{right:16.666667%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.333333%}.col-lg-pull-5{right:41.666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.333333%}.col-lg-pull-8{right:66.666667%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.333333%}.col-lg-pull-11{right:91.666667%}.col-lg-pull-12{right:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.333333%}.col-lg-push-2{left:16.666667%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.333333%}.col-lg-push-5{left:41.666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.333333%}.col-lg-push-8{left:66.666667%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.333333%}.col-lg-push-11{left:91.666667%}.col-lg-push-12{left:100%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.333333%}.col-lg-offset-2{margin-left:16.666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.333333%}.col-lg-offset-5{margin-left:41.666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.333333%}.col-lg-offset-8{margin-left:66.666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.333333%}.col-lg-offset-11{margin-left:91.666667%}.col-lg-offset-12{margin-left:100%}}@media (min-width:1200px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{float:left}.col-xl-1{width:8.333333%}.col-xl-2{width:16.666667%}.col-xl-3{width:25%}.col-xl-4{width:33.333333%}.col-xl-5{width:41.666667%}.col-xl-6{width:50%}.col-xl-7{width:58.333333%}.col-xl-8{width:66.666667%}.col-xl-9{width:75%}.col-xl-10{width:83.333333%}.col-xl-11{width:91.666667%}.col-xl-12{width:100%}.col-xl-pull-0{right:auto}.col-xl-pull-1{right:8.333333%}.col-xl-pull-2{right:16.666667%}.col-xl-pull-3{right:25%}.col-xl-pull-4{right:33.333333%}.col-xl-pull-5{right:41.666667%}.col-xl-pull-6{right:50%}.col-xl-pull-7{right:58.333333%}.col-xl-pull-8{right:66.666667%}.col-xl-pull-9{right:75%}.col-xl-pull-10{right:83.333333%}.col-xl-pull-11{right:91.666667%}.col-xl-pull-12{right:100%}.col-xl-push-0{left:auto}.col-xl-push-1{left:8.333333%}.col-xl-push-2{left:16.666667%}.col-xl-push-3{left:25%}.col-xl-push-4{left:33.333333%}.col-xl-push-5{left:41.666667%}.col-xl-push-6{left:50%}.col-xl-push-7{left:58.333333%}.col-xl-push-8{left:66.666667%}.col-xl-push-9{left:75%}.col-xl-push-10{left:83.333333%}.col-xl-push-11{left:91.666667%}.col-xl-push-12{left:100%}.col-xl-offset-0{margin-left:0}.col-xl-offset-1{margin-left:8.333333%}.col-xl-offset-2{margin-left:16.666667%}.col-xl-offset-3{margin-left:25%}.col-xl-offset-4{margin-left:33.333333%}.col-xl-offset-5{margin-left:41.666667%}.col-xl-offset-6{margin-left:50%}.col-xl-offset-7{margin-left:58.333333%}.col-xl-offset-8{margin-left:66.666667%}.col-xl-offset-9{margin-left:75%}.col-xl-offset-10{margin-left:83.333333%}.col-xl-offset-11{margin-left:91.666667%}.col-xl-offset-12{margin-left:100%}}.table{width:100%;max-width:100%;margin-bottom:1rem}.table td,.table th{padding:.75rem;line-height:1.5;vertical-align:top;border-top:1px solid #eceeef}.table thead th{vertical-align:bottom;border-bottom:2px solid #eceeef}.table tbody+tbody{border-top:2px solid #eceeef}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #eceeef}.table-bordered td,.table-bordered th{border:1px solid #eceeef}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover tbody tr:hover{background-color:#f5f5f5}.table-active,.table-active>td,.table-active>th{background-color:#f5f5f5}.table-hover .table-active:hover{background-color:#e8e8e8}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:#e8e8e8}.table-success,.table-success>td,.table-success>th{background-color:#dff0d8}.table-hover .table-success:hover{background-color:#d0e9c6}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#d0e9c6}.table-info,.table-info>td,.table-info>th{background-color:#d9edf7}.table-hover .table-info:hover{background-color:#c4e3f3}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#c4e3f3}.table-warning,.table-warning>td,.table-warning>th{background-color:#fcf8e3}.table-hover .table-warning:hover{background-color:#faf2cc}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#faf2cc}.table-danger,.table-danger>td,.table-danger>th{background-color:#f2dede}.table-hover .table-danger:hover{background-color:#ebcccc}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#ebcccc}.table-responsive{display:block;width:100%;min-height:.01%;overflow-x:auto}.thead-inverse th{color:#fff;background-color:#373a3c}.thead-default th{color:#55595c;background-color:#eceeef}.table-inverse{color:#eceeef;background-color:#373a3c}.table-inverse.table-bordered{border:0}.table-inverse td,.table-inverse th,.table-inverse thead th{border-color:#55595c}.table-reflow thead{float:left}.table-reflow tbody{display:block;white-space:nowrap}.table-reflow td,.table-reflow th{border-top:1px solid #eceeef;border-left:1px solid #eceeef}.table-reflow td:last-child,.table-reflow th:last-child{border-right:1px solid #eceeef}.table-reflow tbody:last-child tr:last-child td,.table-reflow tbody:last-child tr:last-child th,.table-reflow tfoot:last-child tr:last-child td,.table-reflow tfoot:last-child tr:last-child th,.table-reflow thead:last-child tr:last-child td,.table-reflow thead:last-child tr:last-child th{border-bottom:1px solid #eceeef}.table-reflow tr{float:left}.table-reflow tr td,.table-reflow tr th{display:block!important;border:1px solid #eceeef}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#55595c;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:.25rem}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{border-color:#66afe9;outline:0}.form-control::-webkit-input-placeholder{color:#999;opacity:1}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999;opacity:1}.form-control::placeholder{color:#999;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#eceeef;opacity:1}.form-control:disabled{cursor:not-allowed}.form-control-file,.form-control-range{display:block}.form-control-label{padding:.375rem .75rem;margin-bottom:0}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:2.25rem}.input-group-sm input[type=date].form-control,.input-group-sm input[type=time].form-control,.input-group-sm input[type=datetime-local].form-control,.input-group-sm input[type=month].form-control,input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:1.8625rem}.input-group-lg input[type=date].form-control,.input-group-lg input[type=time].form-control,.input-group-lg input[type=datetime-local].form-control,.input-group-lg input[type=month].form-control,input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:3.166667rem}}.form-control-static{min-height:2.25rem;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0}.form-control-static.form-control-lg,.form-control-static.form-control-sm,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{padding:.275rem .75rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{padding:.75rem 1.25rem;font-size:1.25rem;line-height:1.333333;border-radius:.3rem}.form-group{margin-bottom:1rem}.checkbox,.radio{position:relative;display:block;margin-bottom:.75rem}.checkbox label,.radio label{padding-left:1.25rem;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox label input:only-child,.radio label input:only-child{position:static}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:.25rem;margin-left:-1.25rem}.checkbox+.checkbox,.radio+.radio{margin-top:-.25rem}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:1.25rem;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:.75rem}input[type=checkbox].disabled,input[type=checkbox]:disabled,input[type=radio].disabled,input[type=radio]:disabled{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label{cursor:not-allowed}.form-control-danger,.form-control-success,.form-control-warning{padding-right:2.25rem;background-repeat:no-repeat;background-position:center right .5625rem;-webkit-background-size:1.4625rem 1.4625rem;background-size:1.4625rem 1.4625rem}.has-success .checkbox,.has-success .checkbox-inline,.has-success .form-control-label,.has-success .radio,.has-success .radio-inline,.has-success .text-help,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#5cb85c}.has-success .form-control{border-color:#5cb85c}.has-success .input-group-addon{color:#5cb85c;background-color:#eaf6ea;border-color:#5cb85c}.has-success .form-control-feedback{color:#5cb85c}.has-success .form-control-success{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MTIgNzkyIj48cGF0aCBmaWxsPSIjNWNiODVjIiBkPSJNMjMzLjggNjEwYy0xMy4zIDAtMjYtNi0zNC0xNi44TDkwLjUgNDQ4LjhDNzYuMyA0MzAgODAgNDAzLjMgOTguOCAzODljMTguOC0xNC4yIDQ1LjUtMTAuNCA1OS44IDguNGw3MiA5NUw0NTEuMyAyNDJjMTIuNS0yMCAzOC44LTI2LjIgNTguOC0xMy43IDIwIDEyLjQgMjYgMzguNyAxMy43IDU4LjhMMjcwIDU5MGMtNy40IDEyLTIwLjIgMTkuNC0zNC4zIDIwaC0yeiIvPjwvc3ZnPg==)}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .form-control-label,.has-warning .radio,.has-warning .radio-inline,.has-warning .text-help,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#f0ad4e}.has-warning .form-control{border-color:#f0ad4e}.has-warning .input-group-addon{color:#f0ad4e;background-color:#fff;border-color:#f0ad4e}.has-warning .form-control-feedback{color:#f0ad4e}.has-warning .form-control-warning{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MTIgNzkyIj48cGF0aCBmaWxsPSIjZjBhZDRlIiBkPSJNNjAzIDY0MC4ybC0yNzguNS01MDljLTMuOC02LjYtMTAuOC0xMC42LTE4LjUtMTAuNnMtMTQuNyA0LTE4LjUgMTAuNkw5IDY0MC4yYy0zLjcgNi41LTMuNiAxNC40LjIgMjAuOCAzLjggNi41IDEwLjggMTAuNCAxOC4zIDEwLjRoNTU3YzcuNiAwIDE0LjYtNCAxOC40LTEwLjQgMy41LTYuNCAzLjYtMTQuNCAwLTIwLjh6bS0yNjYuNC0zMGgtNjEuMlY1NDloNjEuMnY2MS4yem0wLTEwN2gtNjEuMlYzMDRoNjEuMnYxOTl6Ii8+PC9zdmc+)}.has-danger .checkbox,.has-danger .checkbox-inline,.has-danger .form-control-label,.has-danger .radio,.has-danger .radio-inline,.has-danger .text-help,.has-danger.checkbox label,.has-danger.checkbox-inline label,.has-danger.radio label,.has-danger.radio-inline label{color:#d9534f}.has-danger .form-control{border-color:#d9534f}.has-danger .input-group-addon{color:#d9534f;background-color:#fdf7f7;border-color:#d9534f}.has-danger .form-control-feedback{color:#d9534f}.has-danger .form-control-danger{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MTIgNzkyIj48cGF0aCBmaWxsPSIjZDk1MzRmIiBkPSJNNDQ3IDU0NC40Yy0xNC40IDE0LjQtMzcuNiAxNC40LTUyIDBsLTg5LTkyLjctODkgOTIuN2MtMTQuNSAxNC40LTM3LjcgMTQuNC01MiAwLTE0LjQtMTQuNC0xNC40LTM3LjYgMC01Mmw5Mi40LTk2LjMtOTIuNC05Ni4zYy0xNC40LTE0LjQtMTQuNC0zNy42IDAtNTJzMzcuNi0xNC4zIDUyIDBsODkgOTIuOCA4OS4yLTkyLjdjMTQuNC0xNC40IDM3LjYtMTQuNCA1MiAwIDE0LjMgMTQuNCAxNC4zIDM3LjYgMCA1MkwzNTQuNiAzOTZsOTIuNCA5Ni40YzE0LjQgMTQuNCAxNC40IDM3LjYgMCA1MnoiLz48L3N2Zz4=)}@media (min-width:544px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .form-control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.btn{display:inline-block;padding:.375rem 1rem;font-size:1rem;font-weight:400;line-height:1.5;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;border-radius:.25rem}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:focus,.btn:hover{text-decoration:none}.btn.focus{text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0}.btn.disabled,.btn:disabled{cursor:not-allowed;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-primary:hover{color:#fff;background-color:#025aa5;border-color:#01549b}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#025aa5;border-color:#01549b}.btn-primary.active,.btn-primary:active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#025aa5;background-image:none;border-color:#01549b}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.btn-primary.dropdown-toggle.focus,.open>.btn-primary.dropdown-toggle:focus,.open>.btn-primary.dropdown-toggle:hover{color:#fff;background-color:#014682;border-color:#01315a}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary:disabled.focus,.btn-primary:disabled:focus{background-color:#0275d8;border-color:#0275d8}.btn-primary.disabled:hover,.btn-primary:disabled:hover{background-color:#0275d8;border-color:#0275d8}.btn-secondary{color:#373a3c;background-color:#fff;border-color:#ccc}.btn-secondary:hover{color:#373a3c;background-color:#e6e6e6;border-color:#adadad}.btn-secondary.focus,.btn-secondary:focus{color:#373a3c;background-color:#e6e6e6;border-color:#adadad}.btn-secondary.active,.btn-secondary:active,.open>.btn-secondary.dropdown-toggle{color:#373a3c;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-secondary.active.focus,.btn-secondary.active:focus,.btn-secondary.active:hover,.btn-secondary:active.focus,.btn-secondary:active:focus,.btn-secondary:active:hover,.open>.btn-secondary.dropdown-toggle.focus,.open>.btn-secondary.dropdown-toggle:focus,.open>.btn-secondary.dropdown-toggle:hover{color:#373a3c;background-color:#d4d4d4;border-color:#8c8c8c}.btn-secondary.disabled.focus,.btn-secondary.disabled:focus,.btn-secondary:disabled.focus,.btn-secondary:disabled:focus{background-color:#fff;border-color:#ccc}.btn-secondary.disabled:hover,.btn-secondary:disabled:hover{background-color:#fff;border-color:#ccc}.btn-info{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#2aabd2}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#2aabd2}.btn-info.active,.btn-info:active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;background-image:none;border-color:#2aabd2}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.btn-info.dropdown-toggle.focus,.open>.btn-info.dropdown-toggle:focus,.open>.btn-info.dropdown-toggle:hover{color:#fff;background-color:#269abc;border-color:#1f7e9a}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info:disabled.focus,.btn-info:disabled:focus{background-color:#5bc0de;border-color:#5bc0de}.btn-info.disabled:hover,.btn-info:disabled:hover{background-color:#5bc0de;border-color:#5bc0de}.btn-success{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#419641}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#419641}.btn-success.active,.btn-success:active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;background-image:none;border-color:#419641}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.btn-success.dropdown-toggle.focus,.open>.btn-success.dropdown-toggle:focus,.open>.btn-success.dropdown-toggle:hover{color:#fff;background-color:#398439;border-color:#2d672d}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success:disabled.focus,.btn-success:disabled:focus{background-color:#5cb85c;border-color:#5cb85c}.btn-success.disabled:hover,.btn-success:disabled:hover{background-color:#5cb85c;border-color:#5cb85c}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#eb9316}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#eb9316}.btn-warning.active,.btn-warning:active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;background-image:none;border-color:#eb9316}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.btn-warning.dropdown-toggle.focus,.open>.btn-warning.dropdown-toggle:focus,.open>.btn-warning.dropdown-toggle:hover{color:#fff;background-color:#d58512;border-color:#b06d0f}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning:disabled.focus,.btn-warning:disabled:focus{background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning.disabled:hover,.btn-warning:disabled:hover{background-color:#f0ad4e;border-color:#f0ad4e}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#c12e2a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#c12e2a}.btn-danger.active,.btn-danger:active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;background-image:none;border-color:#c12e2a}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.btn-danger.dropdown-toggle.focus,.open>.btn-danger.dropdown-toggle:focus,.open>.btn-danger.dropdown-toggle:hover{color:#fff;background-color:#ac2925;border-color:#8b211e}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger:disabled.focus,.btn-danger:disabled:focus{background-color:#d9534f;border-color:#d9534f}.btn-danger.disabled:hover,.btn-danger:disabled:hover{background-color:#d9534f;border-color:#d9534f}.btn-primary-outline{color:#0275d8;background-color:transparent;background-image:none;border-color:#0275d8}.btn-primary-outline.active,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline:focus,.open>.btn-primary-outline.dropdown-toggle{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-primary-outline:hover{color:#fff;background-color:#0275d8;border-color:#0275d8}.btn-primary-outline.disabled.focus,.btn-primary-outline.disabled:focus,.btn-primary-outline:disabled.focus,.btn-primary-outline:disabled:focus{border-color:#43a7fd}.btn-primary-outline.disabled:hover,.btn-primary-outline:disabled:hover{border-color:#43a7fd}.btn-secondary-outline{color:#ccc;background-color:transparent;background-image:none;border-color:#ccc}.btn-secondary-outline.active,.btn-secondary-outline.focus,.btn-secondary-outline:active,.btn-secondary-outline:focus,.open>.btn-secondary-outline.dropdown-toggle{color:#fff;background-color:#ccc;border-color:#ccc}.btn-secondary-outline:hover{color:#fff;background-color:#ccc;border-color:#ccc}.btn-secondary-outline.disabled.focus,.btn-secondary-outline.disabled:focus,.btn-secondary-outline:disabled.focus,.btn-secondary-outline:disabled:focus{border-color:#fff}.btn-secondary-outline.disabled:hover,.btn-secondary-outline:disabled:hover{border-color:#fff}.btn-info-outline{color:#5bc0de;background-color:transparent;background-image:none;border-color:#5bc0de}.btn-info-outline.active,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline:focus,.open>.btn-info-outline.dropdown-toggle{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-info-outline:hover{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-info-outline.disabled.focus,.btn-info-outline.disabled:focus,.btn-info-outline:disabled.focus,.btn-info-outline:disabled:focus{border-color:#b0e1ef}.btn-info-outline.disabled:hover,.btn-info-outline:disabled:hover{border-color:#b0e1ef}.btn-success-outline{color:#5cb85c;background-color:transparent;background-image:none;border-color:#5cb85c}.btn-success-outline.active,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline:focus,.open>.btn-success-outline.dropdown-toggle{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-success-outline:hover{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-success-outline.disabled.focus,.btn-success-outline.disabled:focus,.btn-success-outline:disabled.focus,.btn-success-outline:disabled:focus{border-color:#a3d7a3}.btn-success-outline.disabled:hover,.btn-success-outline:disabled:hover{border-color:#a3d7a3}.btn-warning-outline{color:#f0ad4e;background-color:transparent;background-image:none;border-color:#f0ad4e}.btn-warning-outline.active,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline:focus,.open>.btn-warning-outline.dropdown-toggle{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning-outline:hover{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning-outline.disabled.focus,.btn-warning-outline.disabled:focus,.btn-warning-outline:disabled.focus,.btn-warning-outline:disabled:focus{border-color:#f8d9ac}.btn-warning-outline.disabled:hover,.btn-warning-outline:disabled:hover{border-color:#f8d9ac}.btn-danger-outline{color:#d9534f;background-color:transparent;background-image:none;border-color:#d9534f}.btn-danger-outline.active,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline:focus,.open>.btn-danger-outline.dropdown-toggle{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger-outline:hover{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger-outline.disabled.focus,.btn-danger-outline.disabled:focus,.btn-danger-outline:disabled.focus,.btn-danger-outline:disabled:focus{border-color:#eba5a3}.btn-danger-outline.disabled:hover,.btn-danger-outline:disabled:hover{border-color:#eba5a3}.btn-link{font-weight:400;color:#0275d8;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link:disabled{background-color:transparent}.btn-link,.btn-link:active,.btn-link:focus{border-color:transparent}.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#014c8c;text-decoration:underline;background-color:transparent}.btn-link:disabled:focus,.btn-link:disabled:hover{color:#818a91;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:.75rem 1.25rem;font-size:1.25rem;line-height:1.333333;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .75rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height;-o-transition-property:height;transition-property:height}.dropdown,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-right:.25rem;margin-left:.25rem;vertical-align:middle;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-left:.3em solid transparent}.dropdown-toggle:focus{outline:0}.dropup .dropdown-toggle::after{border-top:0;border-bottom:.3em solid}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:1rem;color:#373a3c;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-divider{height:1px;margin:.5rem 0;overflow:hidden;background-color:#e5e5e5}.dropdown-item{display:block;width:100%;padding:3px 20px;clear:both;font-weight:400;line-height:1.5;color:#373a3c;text-align:inherit;white-space:nowrap;background:0 0;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#2b2d2f;text-decoration:none;background-color:#f5f5f5}.dropdown-item.active,.dropdown-item.active:focus,.dropdown-item.active:hover{color:#fff;text-decoration:none;background-color:#0275d8;outline:0}.dropdown-item.disabled,.dropdown-item.disabled:focus,.dropdown-item.disabled:hover{color:#818a91}.dropdown-item.disabled:focus,.dropdown-item.disabled:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:"progid:DXImageTransform.Microsoft.gradient(enabled = false)"}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:.875rem;line-height:1.5;color:#818a91;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:.3em solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:2}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar::after{display:table;clear:both;content:""}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group-lg.btn-group>.btn+.dropdown-toggle,.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn .caret{margin-left:0}.btn-group-lg>.btn .caret,.btn-lg .caret{border-width:.3em .3em 0;border-bottom-width:0}.dropup .btn-group-lg>.btn .caret,.dropup .btn-lg .caret{border-width:0 .3em .3em}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group::after{display:table;clear:both;content:""}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:.25rem;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:.25rem}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:active,.input-group .form-control:focus,.input-group .form-control:hover{z-index:3}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1;color:#55595c;text-align:center;background-color:#eceeef;border:1px solid #ccc;border-radius:.25rem}.input-group-addon.form-control-sm,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:.275rem .75rem;font-size:.875rem;border-radius:.2rem}.input-group-addon.form-control-lg,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:.75rem 1.25rem;font-size:1.25rem;border-radius:.3rem}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:3}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.input-group-btn:last-child>.btn-group:active,.input-group-btn:last-child>.btn-group:focus,.input-group-btn:last-child>.btn-group:hover,.input-group-btn:last-child>.btn:active,.input-group-btn:last-child>.btn:focus,.input-group-btn:last-child>.btn:hover{z-index:3}.c-input{position:relative;display:inline;padding-left:1.5rem;color:#555;cursor:pointer}.c-input>input{position:absolute;z-index:-1;opacity:0}.c-input>input:checked~.c-indicator{color:#fff;background-color:#0074d9}.c-input>input:focus~.c-indicator{-webkit-box-shadow:0 0 0 .075rem #fff,0 0 0 .2rem #0074d9;box-shadow:0 0 0 .075rem #fff,0 0 0 .2rem #0074d9}.c-input>input:active~.c-indicator{color:#fff;background-color:#84c6ff}.c-input+.c-input{margin-left:1rem}.c-indicator{position:absolute;top:0;left:0;display:block;width:1rem;height:1rem;font-size:65%;line-height:1rem;color:#eee;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#eee;background-repeat:no-repeat;background-position:center center;-webkit-background-size:50% 50%;background-size:50% 50%}.c-checkbox .c-indicator{border-radius:.25rem}.c-checkbox input:checked~.c-indicator{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgOCA4IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA4IDgiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTYuNCwxTDUuNywxLjdMMi45LDQuNUwyLjEsMy43TDEuNCwzTDAsNC40bDAuNywwLjdsMS41LDEuNWwwLjcsMC43bDAuNy0wLjdsMy41LTMuNWwwLjctMC43TDYuNCwxTDYuNCwxeiINCgkvPg0KPC9zdmc+DQo=)}.c-checkbox input:indeterminate~.c-indicator{background-color:#0074d9;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iOHB4IiBoZWlnaHQ9IjhweCIgdmlld0JveD0iMCAwIDggOCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOCA4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0wLDN2Mmg4VjNIMHoiLz4NCjwvc3ZnPg0K)}.c-radio .c-indicator{border-radius:50%}.c-radio input:checked~.c-indicator{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgOCA4IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA4IDgiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTQsMUMyLjMsMSwxLDIuMywxLDRzMS4zLDMsMywzczMtMS4zLDMtM1M1LjcsMSw0LDF6Ii8+DQo8L3N2Zz4NCg==)}.c-inputs-stacked .c-input{display:inline}.c-inputs-stacked .c-input::after{display:block;margin-bottom:.25rem;content:""}.c-inputs-stacked .c-input+.c-input{margin-left:0}.c-select{display:inline-block;max-width:100%;-webkit-appearance:none;padding:.375rem 1.75rem .375rem .75rem;padding-right:.75rem\9;color:#55595c;vertical-align:middle;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC) no-repeat right .75rem center;background-image:none\9;-webkit-background-size:8px 10px;background-size:8px 10px;border:1px solid #ccc;-moz-appearance:none}.c-select:focus{border-color:#51a7e8;outline:0}.c-select::-ms-expand{opacity:0}.c-select-sm{padding-top:3px;padding-bottom:3px;font-size:12px}.c-select-sm:not([multiple]){height:26px;min-height:26px}.file{position:relative;display:inline-block;height:2.5rem;cursor:pointer}.file input{min-width:14rem;margin:0;filter:alpha(opacity=0);opacity:0}.file-custom{position:absolute;top:0;right:0;left:0;z-index:5;height:2.5rem;padding:.5rem 1rem;line-height:1.5;color:#555;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#fff;border:1px solid #ddd;border-radius:.25rem}.file-custom::after{content:"Choose file..."}.file-custom::before{position:absolute;top:-.075rem;right:-.075rem;bottom:-.075rem;z-index:6;display:block;height:2.5rem;padding:.5rem 1rem;line-height:1.5;color:#555;content:"Browse";background-color:#eee;border:1px solid #ddd;border-radius:0 .25rem .25rem 0}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:inline-block}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#818a91}.nav-link.disabled,.nav-link.disabled:focus,.nav-link.disabled:hover{color:#818a91;cursor:not-allowed;background-color:transparent}.nav-inline .nav-item{display:inline-block}.nav-inline .nav-item+.nav-item,.nav-inline .nav-link+.nav-link{margin-left:1rem}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs::after{display:table;clear:both;content:""}.nav-tabs .nav-item{float:left;margin-bottom:-1px}.nav-tabs .nav-item+.nav-item{margin-left:.2rem}.nav-tabs .nav-link{display:block;padding:.5em 1em;border:1px solid transparent;border-radius:.25rem .25rem 0 0}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#eceeef #eceeef #ddd}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link.disabled:focus,.nav-tabs .nav-link.disabled:hover{color:#818a91;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.open .nav-link,.nav-tabs .nav-item.open .nav-link:focus,.nav-tabs .nav-item.open .nav-link:hover,.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus,.nav-tabs .nav-link.active:hover{color:#55595c;background-color:#fff;border-color:#ddd #ddd transparent}.nav-pills::after{display:table;clear:both;content:""}.nav-pills .nav-item{float:left}.nav-pills .nav-item+.nav-item{margin-left:.2rem}.nav-pills .nav-link{display:block;padding:.5em 1em;border-radius:.25rem}.nav-pills .nav-item.open .nav-link,.nav-pills .nav-item.open .nav-link:focus,.nav-pills .nav-item.open .nav-link:hover,.nav-pills .nav-link.active,.nav-pills .nav-link.active:focus,.nav-pills .nav-link.active:hover{color:#fff;cursor:default;background-color:#0275d8}.nav-stacked .nav-item{display:block;float:none}.nav-stacked .nav-item+.nav-item{margin-top:.2rem;margin-left:0}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;padding:.5rem 1rem}.navbar::after{display:table;clear:both;content:""}@media (min-width:544px){.navbar{border-radius:.25rem}}.navbar-full{z-index:1000}@media (min-width:544px){.navbar-full{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:544px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0}.navbar-sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1030;width:100%}@media (min-width:544px){.navbar-sticky-top{border-radius:0}}.navbar-brand{float:left;padding-top:.25rem;padding-bottom:.25rem;margin-right:1rem;font-size:1.25rem}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}.navbar-divider{float:left;width:1px;padding-top:.425rem;padding-bottom:.425rem;margin-right:1rem;margin-left:1rem;overflow:hidden}.navbar-divider::before{content:"\00a0"}.navbar-toggler{padding:.5rem .75rem;font-size:1.25rem;line-height:1;background:0 0;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}@media (min-width:544px){.navbar-toggleable-xs{display:block!important}}@media (min-width:768px){.navbar-toggleable-sm{display:block!important}}@media (min-width:992px){.navbar-toggleable-md{display:block!important}}.navbar-nav .nav-item{float:left}.navbar-nav .nav-link{display:block;padding-top:.425rem;padding-bottom:.425rem}.navbar-nav .nav-link+.nav-link{margin-left:1rem}.navbar-nav .nav-item+.nav-item{margin-left:1rem}.navbar-light .navbar-brand{color:rgba(0,0,0,.8)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.8)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.6)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .active>.nav-link:focus,.navbar-light .navbar-nav .active>.nav-link:hover,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.active:focus,.navbar-light .navbar-nav .nav-link.active:hover,.navbar-light .navbar-nav .nav-link.open,.navbar-light .navbar-nav .nav-link.open:focus,.navbar-light .navbar-nav .nav-link.open:hover,.navbar-light .navbar-nav .open>.nav-link,.navbar-light .navbar-nav .open>.nav-link:focus,.navbar-light .navbar-nav .open>.nav-link:hover{color:rgba(0,0,0,.8)}.navbar-light .navbar-divider{background-color:rgba(0,0,0,.075)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .active>.nav-link:focus,.navbar-dark .navbar-nav .active>.nav-link:hover,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.active:focus,.navbar-dark .navbar-nav .nav-link.active:hover,.navbar-dark .navbar-nav .nav-link.open,.navbar-dark .navbar-nav .nav-link.open:focus,.navbar-dark .navbar-nav .nav-link.open:hover,.navbar-dark .navbar-nav .open>.nav-link,.navbar-dark .navbar-nav .open>.nav-link:focus,.navbar-dark .navbar-nav .open>.nav-link:hover{color:#fff}.navbar-dark .navbar-divider{background-color:rgba(255,255,255,.075)}.card{position:relative;display:block;margin-bottom:.75rem;background-color:#fff;border:1px solid #e5e5e5;border-radius:.25rem}.card-block{padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card>.list-group:first-child .list-group-item:first-child{border-radius:.25rem .25rem 0 0}.card>.list-group:last-child .list-group-item:last-child{border-radius:0 0 .25rem .25rem}.card-header{padding:.75rem 1.25rem;background-color:#f5f5f5;border-bottom:1px solid #e5e5e5}.card-header:first-child{border-radius:.25rem .25rem 0 0}.card-footer{padding:.75rem 1.25rem;background-color:#f5f5f5;border-top:1px solid #e5e5e5}.card-footer:last-child{border-radius:0 0 .25rem .25rem}.card-primary{background-color:#0275d8;border-color:#0275d8}.card-success{background-color:#5cb85c;border-color:#5cb85c}.card-info{background-color:#5bc0de;border-color:#5bc0de}.card-warning{background-color:#f0ad4e;border-color:#f0ad4e}.card-danger{background-color:#d9534f;border-color:#d9534f}.card-primary-outline{background-color:transparent;border-color:#0275d8}.card-secondary-outline{background-color:transparent;border-color:#ccc}.card-info-outline{background-color:transparent;border-color:#5bc0de}.card-success-outline{background-color:transparent;border-color:#5cb85c}.card-warning-outline{background-color:transparent;border-color:#f0ad4e}.card-danger-outline{background-color:transparent;border-color:#d9534f}.card-inverse .card-footer,.card-inverse .card-header{border-bottom:1px solid rgba(255,255,255,.2)}.card-inverse .card-blockquote,.card-inverse .card-footer,.card-inverse .card-header,.card-inverse .card-title{color:#fff}.card-inverse .card-blockquote>footer,.card-inverse .card-link,.card-inverse .card-text{color:rgba(255,255,255,.65)}.card-inverse .card-link:focus,.card-inverse .card-link:hover{color:#fff}.card-blockquote{padding:0;margin-bottom:0;border-left:0}.card-img{border-radius:.25rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img-top{border-radius:.25rem .25rem 0 0}.card-img-bottom{border-radius:0 0 .25rem .25rem}@media (min-width:544px){.card-deck{display:table;table-layout:fixed;border-spacing:1.25rem 0}.card-deck .card{display:table-cell;width:1%;vertical-align:top}.card-deck-wrapper{margin-right:-1.25rem;margin-left:-1.25rem}}@media (min-width:544px){.card-group{display:table;width:100%;table-layout:fixed}.card-group .card{display:table-cell;vertical-align:top}.card-group .card+.card{margin-left:0;border-left:0}.card-group .card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group .card:first-child .card-img-top{border-top-right-radius:0}.card-group .card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group .card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group .card:last-child .card-img-top{border-top-left-radius:0}.card-group .card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group .card:not(:first-child):not(:last-child){border-radius:0}.card-group .card:not(:first-child):not(:last-child) .card-img-bottom,.card-group .card:not(:first-child):not(:last-child) .card-img-top{border-radius:0}}@media (min-width:544px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.card-columns .card{display:inline-block;width:100%}}.breadcrumb{padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#eceeef;border-radius:.25rem}.breadcrumb::after{display:table;clear:both;content:""}.breadcrumb>li{float:left}.breadcrumb>li+li::before{padding-right:.5rem;padding-left:.5rem;color:#818a91;content:"/"}.breadcrumb>.active{color:#818a91}.pagination{display:inline-block;padding-left:0;margin-top:1rem;margin-bottom:1rem;border-radius:.25rem}.page-item{display:inline}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link,.page-item.active .page-link:focus,.page-item.active .page-link:hover{z-index:2;color:#fff;cursor:default;background-color:#0275d8;border-color:#0275d8}.page-item.disabled .page-link,.page-item.disabled .page-link:focus,.page-item.disabled .page-link:hover{color:#818a91;cursor:not-allowed;background-color:#fff;border-color:#ddd}.page-link{position:relative;float:left;padding:.5rem .75rem;margin-left:-1px;line-height:1.5;color:#0275d8;text-decoration:none;background-color:#fff;border:1px solid #ddd}.page-link:focus,.page-link:hover{color:#014c8c;background-color:#eceeef;border-color:#ddd}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.333333}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.275rem .75rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.pager{padding-left:0;margin-top:1rem;margin-bottom:1rem;text-align:center;list-style:none}.pager::after{display:table;clear:both;content:""}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eceeef}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover{color:#818a91;cursor:not-allowed;background-color:#fff}.pager .disabled>span{color:#818a91;cursor:not-allowed;background-color:#fff}.pager-next>a,.pager-next>span{float:right}.pager-prev>a,.pager-prev>span{float:left}.label{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.label:empty{display:none}.btn .label{position:relative;top:-1px}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.label-default{background-color:#818a91}.label-default[href]:focus,.label-default[href]:hover{background-color:#687077}.label-primary{background-color:#0275d8}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#025aa5}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#eceeef;border-radius:.3rem}@media (min-width:544px){.jumbotron{padding:4rem 2rem}}.jumbotron-hr{border-top-color:#d0d5d8}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{padding:15px;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:35px}.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d0e9c6}.alert-success hr{border-top-color:#c1e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bcdff1}.alert-info hr{border-top-color:#a6d5ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faf2cc}.alert-warning hr{border-top-color:#f7ecb5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebcccc}.alert-danger hr{border-top-color:#e4b9b9}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:block;width:100%;height:1rem;margin-bottom:1rem}.progress[value]{-webkit-appearance:none;color:#0074d9;border:0;-moz-appearance:none;appearance:none}.progress[value]::-webkit-progress-bar{background-color:#eee;border-radius:.25rem}.progress[value]::-webkit-progress-value::before{content:attr(value)}.progress[value]::-webkit-progress-value{background-color:#0074d9;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.progress[value="100"]::-webkit-progress-value{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}@media screen and (min-width:0\0){.progress{background-color:#eee;border-radius:.25rem}.progress-bar{display:inline-block;height:1rem;text-indent:-999rem;background-color:#0074d9;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.progress[width^="0"]{min-width:2rem;color:#818a91;background-color:transparent;background-image:none}.progress[width="100%"]{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}}.progress-striped[value]::-webkit-progress-value{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:1rem 1rem;background-size:1rem 1rem}.progress-striped[value]::-moz-progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}@media screen and (min-width:0\0){.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:1rem 1rem;background-size:1rem 1rem}}.progress-animated[value]::-webkit-progress-value{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-animated[value]::-moz-progress-bar{animation:progress-bar-stripes 2s linear infinite}@media screen and (min-width:0\0){.progress-animated .progress-bar-striped{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}}.progress-success[value]::-webkit-progress-value{background-color:#5cb85c}.progress-success[value]::-moz-progress-bar{background-color:#5cb85c}@media screen and (min-width:0\0){.progress-success .progress-bar{background-color:#5cb85c}}.progress-info[value]::-webkit-progress-value{background-color:#5bc0de}.progress-info[value]::-moz-progress-bar{background-color:#5bc0de}@media screen and (min-width:0\0){.progress-info .progress-bar{background-color:#5bc0de}}.progress-warning[value]::-webkit-progress-value{background-color:#f0ad4e}.progress-warning[value]::-moz-progress-bar{background-color:#f0ad4e}@media screen and (min-width:0\0){.progress-warning .progress-bar{background-color:#f0ad4e}}.progress-danger[value]::-webkit-progress-value{background-color:#d9534f}.progress-danger[value]::-moz-progress-bar{background-color:#d9534f}@media screen and (min-width:0\0){.progress-danger .progress-bar{background-color:#d9534f}}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right{padding-left:10px}.media-left{padding-right:10px}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:0}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-flush .list-group-item{border-width:1px 0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}a.list-group-item,button.list-group-item{width:100%;color:#555;text-align:inherit}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#818a91;cursor:not-allowed;background-color:#eceeef}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#818a91}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#0275d8;border-color:#0275d8}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#a8d6fe}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9{padding-bottom:42.857143%}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.embed-responsive-1by1{padding-bottom:100%}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:transform .3s ease-out,-o-transform .3s ease-out;transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.in{opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header::after{display:table;clear:both;content:""}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.5}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer::after{display:table;clear:both;content:""}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:544px){.modal-dialog{width:600px;margin:30px auto}.modal-sm{width:300px}}@media (min-width:768px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:.875rem;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;opacity:0;line-break:auto}.tooltip.in{opacity:.9}.tooltip.bs-tether-element-attached-bottom,.tooltip.tooltip-top{padding:5px 0;margin-top:-3px}.tooltip.bs-tether-element-attached-bottom .tooltip-arrow,.tooltip.tooltip-top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.bs-tether-element-attached-left,.tooltip.tooltip-right{padding:0 5px;margin-left:3px}.tooltip.bs-tether-element-attached-left .tooltip-arrow,.tooltip.tooltip-right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.bs-tether-element-attached-top,.tooltip.tooltip-bottom{padding:5px 0;margin-top:3px}.tooltip.bs-tether-element-attached-top .tooltip-arrow,.tooltip.tooltip-bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bs-tether-element-attached-right,.tooltip.tooltip-left{padding:0 5px;margin-left:-3px}.tooltip.bs-tether-element-attached-right .tooltip-arrow,.tooltip.tooltip-left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:.875rem;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;line-break:auto}.popover.bs-tether-element-attached-bottom,.popover.popover-top{margin-top:-10px}.popover.bs-tether-element-attached-bottom .popover-arrow,.popover.popover-top .popover-arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.bs-tether-element-attached-bottom .popover-arrow::after,.popover.popover-top .popover-arrow::after{bottom:1px;margin-left:-10px;content:"";border-top-color:#fff;border-bottom-width:0}.popover.bs-tether-element-attached-left,.popover.popover-right{margin-left:10px}.popover.bs-tether-element-attached-left .popover-arrow,.popover.popover-right .popover-arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.bs-tether-element-attached-left .popover-arrow::after,.popover.popover-right .popover-arrow::after{bottom:-10px;left:1px;content:"";border-right-color:#fff;border-left-width:0}.popover.bs-tether-element-attached-top,.popover.popover-bottom{margin-top:10px}.popover.bs-tether-element-attached-top .popover-arrow,.popover.popover-bottom .popover-arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-top .popover-arrow::after,.popover.popover-bottom .popover-arrow::after{top:1px;margin-left:-10px;content:"";border-top-width:0;border-bottom-color:#fff}.popover.bs-tether-element-attached-right,.popover.popover-left{margin-left:-10px}.popover.bs-tether-element-attached-right .popover-arrow,.popover.popover-left .popover-arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:rgba(0,0,0,.25)}.popover.bs-tether-element-attached-right .popover-arrow::after,.popover.popover-left .popover-arrow::after{right:1px;bottom:-10px;content:"";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:-.7rem -.7rem 0 0}.popover-content{padding:9px 14px}.popover-arrow,.popover-arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover-arrow{border-width:11px}.popover-arrow::after{content:"";border-width:10px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.carousel-item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.carousel-item>a>img,.carousel-inner>.carousel-item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.carousel-item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:transform .6s ease-in-out,-o-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.carousel-item.active.right,.carousel-inner>.carousel-item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.carousel-item.active.left,.carousel-inner>.carousel-item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.carousel-item.active,.carousel-inner>.carousel-item.next.left,.carousel-inner>.carousel-item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);opacity:.5}.carousel-control.left{background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-prev::before{content:"\2039"}.carousel-control .icon-next::before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:transparent;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media (min-width:544px){.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .icon-prev{margin-left:-15px}.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix::after{display:table;clear:both;content:""}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-xs-left{float:left!important}.pull-xs-right{float:right!important}.pull-xs-none{float:none!important}@media (min-width:544px){.pull-sm-left{float:left!important}.pull-sm-right{float:right!important}.pull-sm-none{float:none!important}}@media (min-width:768px){.pull-md-left{float:left!important}.pull-md-right{float:right!important}.pull-md-none{float:none!important}}@media (min-width:992px){.pull-lg-left{float:left!important}.pull-lg-right{float:right!important}.pull-lg-none{float:none!important}}@media (min-width:1200px){.pull-xl-left{float:left!important}.pull-xl-right{float:right!important}.pull-xl-none{float:none!important}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.invisible{visibility:hidden!important}.text-hide{font:"0/0" a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-xs-left{text-align:left!important}.text-xs-right{text-align:right!important}.text-xs-center{text-align:center!important}@media (min-width:544px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-normal{font-weight:400}.font-weight-bold{font-weight:700}.font-italic{font-style:italic}.text-muted{color:#818a91}.text-primary{color:#0275d8!important}a.text-primary:focus,a.text-primary:hover{color:#025aa5}.text-success{color:#5cb85c!important}a.text-success:focus,a.text-success:hover{color:#449d44}.text-info{color:#5bc0de!important}a.text-info:focus,a.text-info:hover{color:#31b0d5}.text-warning{color:#f0ad4e!important}a.text-warning:focus,a.text-warning:hover{color:#ec971f}.text-danger{color:#d9534f!important}a.text-danger:focus,a.text-danger:hover{color:#c9302c}.bg-inverse{color:#eceeef;background-color:#373a3c}.bg-faded{background-color:#f7f7f9}.bg-primary{color:#fff!important;background-color:#0275d8!important}a.bg-primary:focus,a.bg-primary:hover{background-color:#025aa5}.bg-success{color:#fff!important;background-color:#5cb85c!important}a.bg-success:focus,a.bg-success:hover{background-color:#449d44}.bg-info{color:#fff!important;background-color:#5bc0de!important}a.bg-info:focus,a.bg-info:hover{background-color:#31b0d5}.bg-warning{color:#fff!important;background-color:#f0ad4e!important}a.bg-warning:focus,a.bg-warning:hover{background-color:#ec971f}.bg-danger{color:#fff!important;background-color:#d9534f!important}a.bg-danger:focus,a.bg-danger:hover{background-color:#c9302c}.m-x-auto{margin-right:auto!important;margin-left:auto!important}.m-a-0{margin:0 0!important}.m-t-0{margin-top:0!important}.m-r-0{margin-right:0!important}.m-b-0{margin-bottom:0!important}.m-l-0{margin-left:0!important}.m-x-0{margin-right:0!important;margin-left:0!important}.m-y-0{margin-top:0!important;margin-bottom:0!important}.m-a-1{margin:1rem 1rem!important}.m-t-1{margin-top:1rem!important}.m-r-1{margin-right:1rem!important}.m-b-1{margin-bottom:1rem!important}.m-l-1{margin-left:1rem!important}.m-x-1{margin-right:1rem!important;margin-left:1rem!important}.m-y-1{margin-top:1rem!important;margin-bottom:1rem!important}.m-a-2{margin:1.5rem 1.5rem!important}.m-t-2{margin-top:1.5rem!important}.m-r-2{margin-right:1.5rem!important}.m-b-2{margin-bottom:1.5rem!important}.m-l-2{margin-left:1.5rem!important}.m-x-2{margin-right:1.5rem!important;margin-left:1.5rem!important}.m-y-2{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-a-3{margin:3rem 3rem!important}.m-t-3{margin-top:3rem!important}.m-r-3{margin-right:3rem!important}.m-b-3{margin-bottom:3rem!important}.m-l-3{margin-left:3rem!important}.m-x-3{margin-right:3rem!important;margin-left:3rem!important}.m-y-3{margin-top:3rem!important;margin-bottom:3rem!important}.p-a-0{padding:0 0!important}.p-t-0{padding-top:0!important}.p-r-0{padding-right:0!important}.p-b-0{padding-bottom:0!important}.p-l-0{padding-left:0!important}.p-x-0{padding-right:0!important;padding-left:0!important}.p-y-0{padding-top:0!important;padding-bottom:0!important}.p-a-1{padding:1rem 1rem!important}.p-t-1{padding-top:1rem!important}.p-r-1{padding-right:1rem!important}.p-b-1{padding-bottom:1rem!important}.p-l-1{padding-left:1rem!important}.p-x-1{padding-right:1rem!important;padding-left:1rem!important}.p-y-1{padding-top:1rem!important;padding-bottom:1rem!important}.p-a-2{padding:1.5rem 1.5rem!important}.p-t-2{padding-top:1.5rem!important}.p-r-2{padding-right:1.5rem!important}.p-b-2{padding-bottom:1.5rem!important}.p-l-2{padding-left:1.5rem!important}.p-x-2{padding-right:1.5rem!important;padding-left:1.5rem!important}.p-y-2{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-a-3{padding:3rem 3rem!important}.p-t-3{padding-top:3rem!important}.p-r-3{padding-right:3rem!important}.p-b-3{padding-bottom:3rem!important}.p-l-3{padding-left:3rem!important}.p-x-3{padding-right:3rem!important;padding-left:3rem!important}.p-y-3{padding-top:3rem!important;padding-bottom:3rem!important}.pos-f-t{position:fixed;top:0;right:0;left:0;z-index:1030}.hidden-xs-up{display:none!important}@media (max-width:543px){.hidden-xs-down{display:none!important}}@media (min-width:544px){.hidden-sm-up{display:none!important}}@media (max-width:767px){.hidden-sm-down{display:none!important}}@media (min-width:768px){.hidden-md-up{display:none!important}}@media (max-width:991px){.hidden-md-down{display:none!important}}@media (min-width:992px){.hidden-lg-up{display:none!important}}@media (max-width:1199px){.hidden-lg-down{display:none!important}}@media (min-width:1200px){.hidden-xl-up{display:none!important}}.hidden-xl-down{display:none!important}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}
6 | /*# sourceMappingURL=bootstrap.min.css.map */
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/style/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
7 | .App {
8 | text-align: center;
9 | }
10 |
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | height: 80px;
14 | }
15 |
16 | .App-header {
17 | background-color: #222;
18 | height: 150px;
19 | padding: 20px;
20 | color: white;
21 | }
22 |
23 | .App-title {
24 | font-size: 1.5em;
25 | }
26 |
27 | .App-intro {
28 | font-size: large;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from { transform: rotate(0deg); }
33 | to { transform: rotate(360deg); }
34 | }
35 |
36 | .Test-container{
37 | width: 50%;
38 | margin: 10px auto 0;
39 | /*margin-top: 10px;*/
40 | }
41 |
42 | ul{
43 | width: 150px;
44 | margin: 10px auto 0;
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/test/actions/index_test.js:
--------------------------------------------------------------------------------
1 | import { expect } from '../test_helper';
2 |
3 | import { SAVE_COMMENT } from '../../src/actions/types';
4 | import { saveComment } from '../../src/actions';
5 |
6 | describe('actions', () => {
7 | describe('saveComment', () => {
8 | it('has the correct type', () => {
9 |
10 | // action creator returns an action
11 | const action = saveComment();
12 | expect(action.type).to.equal(SAVE_COMMENT);
13 | });
14 |
15 | it('has the correct payload', () => {
16 |
17 | const action = saveComment('new comment');
18 | expect(action.payload).to.equal('new comment');
19 | });
20 |
21 | });
22 |
23 | });
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/test/components/app_test.js:
--------------------------------------------------------------------------------
1 | import { renderComponent, expect } from '../test_helper';
2 | import App from '../../src/components/app';
3 |
4 | // Use 'describe' to group together similar tests
5 | describe('App', () => {
6 |
7 | let component;
8 |
9 | // First create an instance of App
10 | beforeEach(() => {
11 | component = renderComponent(App);
12 | });
13 |
14 | // Use 'it' to test a single attribute of a target
15 | it('shows comment box', () => {
16 |
17 | // Use 'expect' to make an 'assertion' about a target
18 | expect(component.find('.comment-box')).to.exist;
19 | });
20 |
21 | it('shows comment list', () =>{
22 | expect(component.find('.comment-list')).to.exist;
23 | });
24 | });
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/test/components/comment_box_test.js:
--------------------------------------------------------------------------------
1 | import { renderComponent, expect } from '../test_helper';
2 | import CommentBox from '../../src/components/comment_box';
3 |
4 | describe('CommentBox', () => {
5 |
6 | let component;
7 |
8 | beforeEach(() => {
9 | component = renderComponent(CommentBox);
10 | });
11 |
12 | it('has the correct class', () => {
13 | expect(component).to.have.class('comment-box');
14 | });
15 |
16 | it('has a text area', () => {
17 | expect(component.find('textarea')).to.exist;
18 | });
19 |
20 | it('has a button', () => {
21 | expect(component.find('button')).to.exist;
22 | });
23 |
24 | describe('entering some text', () => {
25 |
26 | beforeEach(() => {
27 | component.find('textarea').simulate('change', 'new comment');
28 | });
29 |
30 | it('shows that text in the textarea', () => {
31 | expect(component.find('textarea')).to.have.value('new comment');
32 | });
33 |
34 | it('when submitted, clears the input', () => {
35 | // console.log(component)'s output:
36 | //
40 | component.simulate('submit');
41 | expect(component.find('textarea')).to.have.value('');
42 | });
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/test/components/comment_list_test.js:
--------------------------------------------------------------------------------
1 | import { renderComponent, expect } from '../test_helper';
2 | import CommentList from '../../src/components/comment_list';
3 |
4 | describe('CommentList', () => {
5 |
6 | let component;
7 |
8 | beforeEach(() => {
9 | // renderComponents makes props available
10 | // const props = { comments: ['New Comment', 'New Other Comment'] };
11 | // component = renderComponent(CommentList, null, props);
12 | const initialState = { comments: ['New Comment', 'New Other Comment'] }; // better call it initialState, not props
13 | component = renderComponent(CommentList, null, initialState);
14 | });
15 |
16 | it('shows an LI for each comment', () => {
17 | expect(component.find('li').length).to.equal(2);
18 |
19 | });
20 |
21 | it('shows each comment that is provided', () => {
22 | expect(component).to.contain('New Comment');
23 | expect(component).to.contain('New Other Comment');
24 | });
25 |
26 | });
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/test/reducers/comments_test.js:
--------------------------------------------------------------------------------
1 | import { expect } from '../test_helper';
2 | import commentReducer from '../../src/reducers/comments';
3 | import { SAVE_COMMENT } from '../../src/actions/types';
4 |
5 | describe('Comments Reducer', () => {
6 |
7 | // in case there is a weird input, we react with the default state
8 | it('handles action with unknown type', () => {
9 |
10 | // expect(commentReducer()).to.be.instanceof(Array);
11 | // better:
12 | // eql compares deeply
13 | // so we are sure the array is really empty
14 | // expect(commentReducer()).to.eql([]);
15 | // failed after we completed the reducer: TypeError: Cannot read property 'type' of undefined
16 | // so we have to pass in values here:
17 | expect(commentReducer(undefined, {})).to.eql([]);
18 | });
19 |
20 | // it('handles action of type SAVE_COMMENT', () => {
21 | //
22 | // });
23 | // better:
24 | it('SAVE_COMMENT', () => {
25 |
26 | const action = { type: SAVE_COMMENT, payload: 'new comment'};
27 | expect(commentReducer([], action)).to.eql(['new comment']);
28 |
29 | });
30 |
31 | });
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/test/test_helper.js:
--------------------------------------------------------------------------------
1 | // https://github.com/jsdom/jsdom
2 | // A fake HTML Document run in the terminal (Node.js)
3 | // A JavaScript implementation of the WHATWG DOM and HTML standards, for use with Node.js
4 | import jsdom from 'jsdom';
5 | import _$ from 'jquery';
6 | import TestUtils from 'react-addons-test-utils';
7 | import ReactDOM from 'react-dom';
8 | import chai, { expect } from 'chai';
9 | import React from 'react'; // needed whenever we use JSX
10 | import { Provider } from 'react-redux';
11 | import { createStore } from 'redux';
12 | import reducers from '../src/reducers';
13 | import chaiJquery from 'chai-jquery';
14 |
15 | // Set up testing environment to run like a browser in the command line
16 | // not window, but global variable
17 |
18 | const { JSDOM } = jsdom;
19 | const { document } = (new JSDOM('')).window;
20 | global.document = document;
21 |
22 | global.window = global.document.defaultView;
23 | // overwrite default $ of jquery / hook up our fake dom to jquery
24 | const $ = _$(global.window);
25 |
26 | // build 'renderComponent' helper that should render a given react class
27 | function renderComponent(ComponentClass, props, state){
28 |
29 | // https://reactjs.org/docs/test-utils.html
30 | const componentInstance = TestUtils.renderIntoDocument(
31 |
32 |
33 |
34 | );
35 |
36 | return $(ReactDOM.findDOMNode(componentInstance)); // produces HTML and wraps it into jquery to be accessible
37 |
38 |
39 | }
40 |
41 | // build helper to for simulating events
42 | // makes this possible for all jquery elements: $('div').simulate()
43 | $.fn.simulate = function(eventName, value){
44 |
45 | if (value){
46 | this.val(value); // val() is a jquery function
47 | }
48 |
49 | // this is the reference to the dom element, and we need the first one of the array
50 | TestUtils.Simulate[eventName](this[0]);
51 |
52 | };
53 |
54 | // Set up chai-jquery
55 | chaiJquery(chai, chai.util, $);
56 |
57 | export { renderComponent, expect };
58 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'development',
3 | entry: [
4 | './src/index.js'
5 | ],
6 | output: {
7 | path: __dirname,
8 | publicPath: '/',
9 | filename: 'bundle.js'
10 | },
11 | module: {
12 | rules: [{
13 | exclude: /node_modules/,
14 | loader: 'babel-loader'
15 | }]
16 | },
17 | resolve: {
18 | extensions: ['.js', '.jsx']
19 | },
20 | devServer: {
21 | historyApiFallback: true,
22 | contentBase: './'
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/unit-testing-mocha-chai/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | module.exports = {
3 | mode: 'production',
4 | entry: [
5 | './src/index.js'
6 | ],
7 | output: {
8 | path: path.resolve(__dirname, 'dist'),
9 | publicPath: '/',
10 | filename: 'bundle.js'
11 | },
12 | module: {
13 | rules: [{
14 | exclude: /node_modules/,
15 | loader: 'babel-loader'
16 | }]
17 | },
18 | resolve: {
19 | extensions: ['.js', '.jsx']
20 | },
21 | devServer: {
22 | historyApiFallback: true,
23 | contentBase: './'
24 | }
25 | };
26 |
--------------------------------------------------------------------------------