diff --git a/.circleci/config.yml b/.circleci/config.yml
index ff23b07..403f576 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,18 +1,10 @@
# Javascript Node CircleCI 2.0 configuration file
#
-# Check https://circleci.com/docs/2.0/language-javascript/ for more details
-#
-version: 2
-jobs:
- build:
- docker:
- # specify the version you desire here
- - image: circleci/node:8.10
- # Specify service dependencies here if necessary
- # CircleCI maintains a library of pre-built images
- # documented at https://circleci.com/docs/2.0/circleci-images/
- # - image: circleci/mongo:3.4.4
+jobs:
+ test-job:
+ docker:
+ - image: 'circleci/node:lts'
working_directory: ~/repo
@@ -22,15 +14,34 @@ jobs:
# Download and cache dependencies
- restore_cache:
keys:
- - v1-dependencies-{{ checksum "yarn.lock" }}
+ - dependencies-{{ checksum "yarn.lock" }}
# fallback to using the latest cache if no exact match is found
- - v1-dependencies-
+ - dependencies-
- - run: yarn install
+ - run: yarn setup
- save_cache:
paths:
- node_modules
- key: v1-dependencies-{{ checksum "yarn.lock" }}
+ - client/node_modules
+ - server/node_modules
+ key: dependencies-{{ checksum "yarn.lock" }}
- - run: yarn test
\ No newline at end of file
+ - run:
+ command: yarn test
+ environment:
+ TZ: UTC
+ REACT_APP_COMMIT_SHA: some_sha
+
+ - store_artifacts: # For coverage report
+ path: client/coverage
+
+orbs: # declare what orbs we are going to use
+ node: circleci/node@2.0.2 # the node orb provides common node-related configuration
+
+version: 2.1
+
+workflows:
+ tests:
+ jobs:
+ - test-job
diff --git a/client/.env.dist b/client/.env.dist
index 2695add..07e63f9 100644
--- a/client/.env.dist
+++ b/client/.env.dist
@@ -1,4 +1,5 @@
REACT_APP_API_HOST=localhost
REACT_APP_API_PROTOCOL=http
REACT_APP_API_PORT=3001
-REACT_APP_COMMIT_SHA=some_sha
\ No newline at end of file
+REACT_APP_COMMIT_SHA=some_sha
+TZ=UTC
diff --git a/client/package.json b/client/package.json
index 4054d9a..bdd5122 100644
--- a/client/package.json
+++ b/client/package.json
@@ -43,7 +43,8 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
- "test": "react-scripts test",
+ "test": "react-scripts test --env=jest-environment-jsdom-sixteen",
+ "coverage": "react-scripts test --env=jest-environment-jsdom-sixteen --coverage --watchAll=false",
"eject": "react-scripts eject"
},
"eslintConfig": {
@@ -62,8 +63,13 @@
]
},
"devDependencies": {
+ "@peculiar/webcrypto": "^1.1.1",
+ "@testing-library/jest-dom": "^5.5.0",
+ "@testing-library/react": "^10.0.4",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
- "enzyme-to-json": "^3.3.5"
+ "enzyme-to-json": "^3.3.5",
+ "jest-environment-jsdom-sixteen": "^1.0.3",
+ "jest-fetch-mock": "^3.0.3"
}
}
diff --git a/client/src/actions/app.test.js b/client/src/actions/app.test.js
new file mode 100644
index 0000000..5589671
--- /dev/null
+++ b/client/src/actions/app.test.js
@@ -0,0 +1,53 @@
+import * as actions from './app';
+
+describe('App actions', () => {
+ it('should create an action to scroll to bottom', () => {
+ expect(actions.setScrolledToBottom('test')).toEqual({
+ type: 'SET_SCROLLED_TO_BOTTOM',
+ payload: 'test',
+ });
+ });
+
+ it('should create an action to close modal', () => {
+ expect(actions.closeModal()).toEqual({
+ type: 'CLOSE_MODAL',
+ });
+ });
+
+ it('should create an action to open modal', () => {
+ expect(actions.openModal('test')).toEqual({
+ type: 'OPEN_MODAL',
+ payload: 'test',
+ });
+ });
+
+ it('should create an action to clear activities', () => {
+ const mockDispatch = jest.fn();
+
+ actions.clearActivities()(mockDispatch);
+
+ expect(mockDispatch).toHaveBeenLastCalledWith({
+ type: 'CLEAR_ACTIVITIES',
+ });
+ });
+ it('should create all actions', () => {
+ const mockDispatch = jest.fn();
+
+ const actionsResults = [
+ [actions.toggleWindowFocus('test'), 'TOGGLE_WINDOW_FOCUS'],
+ [actions.showNotice('test'), 'SHOW_NOTICE'],
+ [actions.toggleSoundEnabled('test'), 'TOGGLE_SOUND_ENABLED'],
+ [actions.toggleSocketConnected('test'), 'TOGGLE_SOCKET_CONNECTED'],
+ [actions.createUser('test'), 'CREATE_USER'],
+ [actions.setLanguage('test'), 'CHANGE_LANGUAGE'],
+ ];
+
+ actionsResults.forEach(([action, type]) => {
+ action(mockDispatch);
+ expect(mockDispatch).toHaveBeenLastCalledWith({
+ type,
+ payload: 'test',
+ });
+ });
+ });
+});
diff --git a/client/src/actions/encrypted_messages.test.js b/client/src/actions/encrypted_messages.test.js
new file mode 100644
index 0000000..9687322
--- /dev/null
+++ b/client/src/actions/encrypted_messages.test.js
@@ -0,0 +1,49 @@
+import * as actions from './encrypted_messages';
+import { getSocket } from 'utils/socket';
+import { prepare as prepareMessage, process as processMessage } from 'utils/message';
+
+jest.mock('utils/message', () => {
+ return {
+ prepare: jest
+ .fn()
+ .mockResolvedValue({ original: { type: 'messageType', payload: 'test' }, toSend: 'encryptedpayload' }),
+ process: jest.fn().mockResolvedValue({ type: 'messageType', payload: 'test' }),
+ };
+});
+
+const mockEmit = jest.fn();
+
+jest.mock('utils/socket', () => {
+ return {
+ getSocket: jest.fn().mockImplementation(() => ({
+ emit: mockEmit,
+ })),
+ };
+});
+
+describe('Encrypted messages actions', () => {
+ it('should create an action to send message', async () => {
+ const mockDispatch = jest.fn();
+
+ await actions.sendEncryptedMessage({ payload: 'payload' })(mockDispatch, jest.fn().mockReturnValue({ state: {} }));
+
+ expect(prepareMessage).toHaveBeenLastCalledWith({ payload: 'payload' }, { state: {} });
+ expect(mockDispatch).toHaveBeenLastCalledWith({ payload: 'test', type: 'SEND_ENCRYPTED_MESSAGE_messageType' });
+ expect(getSocket().emit).toHaveBeenLastCalledWith('ENCRYPTED_MESSAGE', 'encryptedpayload');
+ });
+
+ it('should create an action to receive message', async () => {
+ const mockDispatch = jest.fn();
+
+ await actions.receiveEncryptedMessage({ payload: 'encrypted' })(
+ mockDispatch,
+ jest.fn().mockReturnValue({ state: {} }),
+ );
+
+ expect(processMessage).toHaveBeenLastCalledWith({ payload: 'encrypted' }, { state: {} });
+ expect(mockDispatch).toHaveBeenLastCalledWith({
+ payload: { payload: 'test', state: { state: {} } },
+ type: 'RECEIVE_ENCRYPTED_MESSAGE_messageType',
+ });
+ });
+});
diff --git a/client/src/actions/index.js b/client/src/actions/index.js
index 3504084..bb6fcbf 100644
--- a/client/src/actions/index.js
+++ b/client/src/actions/index.js
@@ -1,3 +1,5 @@
+/* istanbul ignore file */
+
export * from './app'
export * from './unencrypted_messages'
export * from './encrypted_messages'
diff --git a/client/src/actions/unencrypted_messages.test.js b/client/src/actions/unencrypted_messages.test.js
new file mode 100644
index 0000000..dc52cd5
--- /dev/null
+++ b/client/src/actions/unencrypted_messages.test.js
@@ -0,0 +1,125 @@
+import * as actions from './unencrypted_messages';
+import { getSocket } from 'utils/socket';
+
+const mockEmit = jest.fn((_type, _null, callback) => {
+ callback({ isLocked: true });
+});
+
+jest.mock('utils/socket', () => {
+ return {
+ getSocket: jest.fn().mockImplementation(() => ({
+ emit: mockEmit,
+ })),
+ };
+});
+
+describe('Reveice unencrypted message actions', () => {
+ it('should create no action', () => {
+ const mockDispatch = jest.fn();
+ actions.receiveUnencryptedMessage('FAKE')(mockDispatch, jest.fn().mockReturnValue({}));
+ expect(mockDispatch).not.toHaveBeenCalled();
+ });
+
+ it('should create user enter action', () => {
+ const mockDispatch = jest.fn();
+ actions.receiveUnencryptedMessage('USER_ENTER', 'test')(mockDispatch, jest.fn().mockReturnValue({ state: {} }));
+ expect(mockDispatch).toHaveBeenLastCalledWith({ type: 'USER_ENTER', payload: 'test' });
+ });
+
+ it('should create user exit action', () => {
+ const mockDispatch = jest.fn();
+ const state = {
+ room: {
+ members: [
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan' },
+ { publicKey: { n: 'dankey' }, id: 'dankey', username: 'dan' },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'dan' },
+ ],
+ },
+ };
+ const mockGetState = jest.fn().mockReturnValue(state);
+ const payload1 = [
+ { publicKey: { n: 'alankey' } },
+ { publicKey: { n: 'dankey' } },
+ { publicKey: { n: 'alicekey' } },
+ ];
+ const payload2 = [{ publicKey: { n: 'dankey' } }, { publicKey: { n: 'alicekey' } }];
+
+ // Nobody left
+ actions.receiveUnencryptedMessage('USER_EXIT', payload1)(mockDispatch, mockGetState);
+
+ expect(mockDispatch).not.toHaveBeenCalled();
+
+ actions.receiveUnencryptedMessage('USER_EXIT', payload2)(mockDispatch, mockGetState);
+ expect(mockDispatch).toHaveBeenLastCalledWith({
+ payload: {
+ id: 'alankey',
+ members: [{ publicKey: { n: 'dankey' } }, { publicKey: { n: 'alicekey' } }],
+ username: 'alan',
+ },
+ type: 'USER_EXIT',
+ });
+ });
+
+ it('should create receive toggle lock room action', () => {
+ const mockDispatch = jest.fn();
+ const state = {
+ room: {
+ members: [
+ { publicKey: { n: 'alankey' }, id: 'idalan', username: 'alan' },
+ { publicKey: { n: 'dankey' }, id: 'iddan', username: 'dan' },
+ ],
+ },
+ };
+ const mockGetState = jest.fn().mockReturnValue(state);
+ const payload = { publicKey: { n: 'alankey' } };
+
+ actions.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload)(mockDispatch, mockGetState);
+ expect(mockDispatch).toHaveBeenLastCalledWith({
+ payload: { id: 'idalan', locked: undefined, username: 'alan' },
+ type: 'RECEIVE_TOGGLE_LOCK_ROOM',
+ });
+ });
+
+ it('should create receive toggle lock room action', () => {
+ const mockDispatch = jest.fn();
+ const state = {
+ user: {
+ username: 'alan',
+ id: 'idalan',
+ },
+ };
+ const mockGetState = jest.fn().mockReturnValue(state);
+
+ actions.sendUnencryptedMessage('TOGGLE_LOCK_ROOM')(mockDispatch, mockGetState);
+ expect(mockDispatch).toHaveBeenLastCalledWith({
+ payload: { locked: true, sender: 'idalan', username: 'alan' },
+ type: 'TOGGLE_LOCK_ROOM',
+ });
+ });
+});
+
+describe('Send unencrypted message actions', () => {
+ it('should create no action', () => {
+ const mockDispatch = jest.fn();
+ actions.sendUnencryptedMessage('FAKE')(mockDispatch, jest.fn().mockReturnValue({}));
+ expect(mockDispatch).not.toHaveBeenCalled();
+ });
+
+ it('should create toggle lock room action', () => {
+ const mockDispatch = jest.fn();
+ const state = {
+ user: {
+ username: 'alan',
+ id: 'idalan',
+ },
+ };
+ const mockGetState = jest.fn().mockReturnValue(state);
+
+ actions.sendUnencryptedMessage('TOGGLE_LOCK_ROOM')(mockDispatch, mockGetState);
+ expect(mockDispatch).toHaveBeenLastCalledWith({
+ payload: { locked: true, sender: 'idalan', username: 'alan' },
+ type: 'TOGGLE_LOCK_ROOM',
+ });
+ });
+});
diff --git a/client/src/api/config.js b/client/src/api/config.js
index 30e1c50..0aa2bd1 100644
--- a/client/src/api/config.js
+++ b/client/src/api/config.js
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
let host
let protocol
let port
diff --git a/client/src/api/generator.js b/client/src/api/generator.js
index 9dd44f1..293c12b 100644
--- a/client/src/api/generator.js
+++ b/client/src/api/generator.js
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
import config from './config'
export default (resourceName = '') => {
diff --git a/client/src/components/About/About.test.js b/client/src/components/About/About.test.js
new file mode 100644
index 0000000..74846e3
--- /dev/null
+++ b/client/src/components/About/About.test.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import About from '.';
+import fetchMock from 'jest-fetch-mock';
+
+jest.useFakeTimers();
+
+// Mock Api generator
+
+jest.mock('../../api/generator', () => {
+ return path => {
+ return `http://fakedomain/${path}`;
+ };
+});
+
+describe('About component', () => {
+ afterEach(() => {
+ fetchMock.resetMocks();
+ });
+
+ it('should display', async () => {
+ const { asFragment } = render( );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should report abuse', async () => {
+ const { getByText, queryByText } = render( );
+
+ expect(queryByText('Thank you!')).not.toBeInTheDocument();
+
+ fireEvent.click(getByText('Submit'));
+
+ expect(fetchMock).toHaveBeenCalledWith('http://fakedomain/abuse/test', { method: 'POST' });
+
+ expect(getByText('Thank you!')).toBeInTheDocument();
+ });
+
+ it('should change room id', async () => {
+ const { getByPlaceholderText, getByText, queryByText } = render( );
+
+ expect(queryByText('Thank you!')).not.toBeInTheDocument();
+
+ fireEvent.change(getByPlaceholderText('Room ID'), { target: { value: 'newRoomName' } });
+
+ jest.runAllTimers();
+
+ fireEvent.click(getByText('Submit'));
+
+ expect(fetchMock).toHaveBeenLastCalledWith('http://fakedomain/abuse/newRoomName', { method: 'POST' });
+ });
+});
diff --git a/client/src/components/About/__snapshots__/About.test.js.snap b/client/src/components/About/__snapshots__/About.test.js.snap
new file mode 100644
index 0000000..5d3841a
--- /dev/null
+++ b/client/src/components/About/__snapshots__/About.test.js.snap
@@ -0,0 +1,513 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`About component should display 1`] = `
+
+
+
+
+
+
+
+ Report Abuse
+
+
+ We encourage you to report problematic content to us. Please keep in mind that to help ensure the safety, confidentiality and security of your messages, we do not have the contents of messages available to us, which limits our ability to verify the report and take action.
+
+
+ When needed, you can take a screenshot of the content and share it, along with any available contact info, with appropriate law enforcement authorities.
+
+
+ To report any content, email us at abuse[at]darkwire.io or submit the room ID below to report anonymously.
+
+
+
+
+ If you feel you or anyone else is in immediate danger, please contact your local emergency services.
+
+
+ If you receive content from someone who wishes to hurt themselves, and you're concerned for their safety, please contact your local emergency services or a
+
+ suicide prevention hotline
+
+ .
+
+
+ If you receive or encounter content indicating abuse or exploitation of a child, please contact the
+
+ National Center for Missing and Exploited Children (NCMEC)
+
+ .
+
+
+
+
+ Acceptable Use Policy
+
+
+ This Acceptable Use Policy (this “Policy”) describes prohibited uses of the web services offered by Darkwire and its affiliates (the “Services”) and the website located at https://darkwire.io (the “Darkwire Site”). The examples described in this Policy are not exhaustive. We may modify this Policy at any time by posting a revised version on the Darkwire Site. By using the Services or accessing the Darkwire Site, you agree to the latest version of this Policy. If you violate the Policy or authorize or help others to do so, we may suspend or terminate your use of the Services.
+
+
+ No Illegal, Harmful, or Offensive Use or Content
+
+
+ You may not use, or encourage, promote, facilitate or instruct others to use, the Services or Darkwire Site for any illegal, harmful, fraudulent, infringing or offensive use, or to transmit, store, display, distribute or otherwise make available content that is illegal, harmful, fraudulent, infringing or offensive. Prohibited activities or content include:
+
+
+
+
+ Illegal, Harmful or Fraudulent Activities.
+
+ Any activities that are illegal, that violate the rights of others, or that may be harmful to others, our operations or reputation, including disseminating, promoting or facilitating child pornography, offering or disseminating fraudulent goods, services, schemes, or promotions, make-money-fast schemes, ponzi and pyramid schemes, phishing, or pharming.
+
+
+
+ Infringing Content.
+
+ Content that infringes or misappropriates the intellectual property or proprietary rights of others.
+
+
+
+ Offensive Content.
+
+ Content that is defamatory, obscene, abusive, invasive of privacy, or otherwise objectionable, including content that constitutes child pornography, relates to bestiality, or depicts non-consensual sex acts.
+
+
+
+ Harmful Content.
+
+ Content or other computer technology that may damage, interfere with, surreptitiously intercept, or expropriate any system, program, or data, including viruses, Trojan horses, worms, time bombs, or cancelbots.
+
+
+
+ No Security Violations
+
+
+ You may not use the Services to violate the security or integrity of any network, computer or communications system, software application, or network or computing device (each, a “System”). Prohibited activities include:
+
+
+
+ Unauthorized Access.
+
+ Accessing or using any System without permission, including attempting to probe, scan, or test the vulnerability of a System or to breach any security or authentication measures used by a System.
+
+
+
+ Interception.
+
+ Monitoring of data or traffic on a System without permission.
+
+
+
+ Falsification of Origin.
+
+ Forging TCP-IP packet headers, e-mail headers, or any part of a message describing its origin or route. The legitimate use of aliases and anonymous remailers is not prohibited by this provision.
+
+
+
+ No Network Abuse
+
+
+ You may not make network connections to any users, hosts, or networks unless you have permission to communicate with them. Prohibited activities include:
+
+
+
+ Monitoring or Crawling.
+
+ Monitoring or crawling of a System that impairs or disrupts the System being monitored or crawled.
+
+
+
+ Denial of Service (DoS).
+
+ Inundating a target with communications requests so the target either cannot respond to legitimate traffic or responds so slowly that it becomes ineffective.
+
+
+
+ Intentional Interference.
+
+ Interfering with the proper functioning of any System, including any deliberate attempt to overload a system by mail bombing, news bombing, broadcast attacks, or flooding techniques.
+
+
+
+ Operation of Certain Network Services.
+
+ Operating network services like open proxies, open mail relays, or open recursive domain name servers.
+
+
+
+ Avoiding System Restrictions.
+
+ Using manual or electronic means to avoid any use limitations placed on a System, such as access and storage restrictions.
+
+
+
+ No E-Mail or Other Message Abuse
+
+
+ You will not distribute, publish, send, or facilitate the sending of unsolicited mass e-mail or other messages, promotions, advertising, or solicitations (like “spam”), including commercial advertising and informational announcements. You will not alter or obscure mail headers or assume a sender’s identity without the sender’s explicit permission. You will not collect replies to messages sent from another internet service provider if those messages violate this Policy or the acceptable use policy of that provider.
+
+ Our Monitoring and Enforcement
+
+
+ We reserve the right, but do not assume the obligation, to investigate any violation of this Policy or misuse of the Services or Darkwire Site. We may:
+
+
+ investigate violations of this Policy or misuse of the Services or Darkwire Site; or
+
+
+ remove, disable access to, or modify any content or resource that violates this Policy or any other agreement we have with you for use of the Services or the Darkwire Site.
+
+
+ We may report any activity that we suspect violates any law or regulation to appropriate law enforcement officials, regulators, or other appropriate third parties. Our reporting may include disclosing appropriate customer information. We also may cooperate with appropriate law enforcement agencies, regulators, or other appropriate third parties to help with the investigation and prosecution of illegal conduct by providing network and systems information related to alleged violations of this Policy.
+
+
+ Reporting of Violations of this Policy
+
+ If you become aware of any violation of this Policy, you will immediately notify us and provide us with assistance, as requested, to stop or remedy the violation. To report any violation of this Policy, please follow our abuse reporting process.
+
+
+
+ Terms of Service ("Terms")
+
+
+ Last updated: December 11, 2017
+
+
+ Please read these Terms of Service ("Terms", "Terms of Service") carefully before using the https://darkwire.io website (the "Service") operated by Darkwire ("us", "we", or "our").
+
+
+ Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. These Terms apply to all visitors, users and others who access or use the Service.
+
+
+ By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service.
+
+
+ Links To Other Web Sites
+
+
+ Our Service may contain links to third-party web sites or services that are not owned or controlled by Darkwire.
+
+
+ Darkwire has no control over, and assumes no responsibility for, the content, privacy policies, or practices of any third party web sites or services. You further acknowledge and agree that Darkwire shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such web sites or services.
+
+
+ We strongly advise you to read the terms and conditions and privacy policies of any third-party web sites or services that you visit.
+
+
+ Termination
+
+
+ We may terminate or suspend access to our Service immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.
+
+
+ All provisions of the Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability.
+
+
+ Governing Law
+
+
+ These Terms shall be governed and construed in accordance with the laws of New York, United States, without regard to its conflict of law provisions.
+
+
+ Our failure to enforce any right or provision of these Terms will not be considered a waiver of those rights. If any provision of these Terms is held to be invalid or unenforceable by a court, the remaining provisions of these Terms will remain in effect. These Terms constitute the entire agreement between us regarding our Service, and supersede and replace any prior agreements we might have between us regarding the Service.
+
+
+
+
+ Disclaimer
+
+
+ WARNING: Darkwire does not mask IP addresses nor can verify the integrity of parties recieving messages. Proceed with caution and always confirm recipients beforre starting a chat session.
+
+
+ Please also note that
+
+ ALL CHATROOMS
+
+ are public. Anyone can guess your room URL. If you need a more-private room, use the lock feature or set the URL manually by entering a room ID after "darkwire.io/".
+
+
+
+ No Warranties; Exclusion of Liability; Indemnification
+
+
+
+ OUR WEBSITE IS OPERATED BY Darkwire ON AN "AS IS," "AS AVAILABLE" BASIS, WITHOUT REPRESENTATIONS OR WARRANTIES OF ANY KIND. TO THE FULLEST EXTENT PERMITTED BY LAW, Darkwire SPECIFICALLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, INCLUDING ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NONINFRINGEMENT FOR OUR WEBSITE AND ANY CONTRACTS AND SERVICES YOU PURCHASE THROUGH IT. Darkwire SHALL NOT HAVE ANY LIABILITY OR RESPONSIBILITY FOR ANY ERRORS OR OMISSIONS IN THE CONTENT OF OUR WEBSITE, FOR CONTRACTS OR SERVICES SOLD THROUGH OUR WEBSITE, FOR YOUR ACTION OR INACTION IN CONNECTION WITH OUR WEBSITE OR FOR ANY DAMAGE TO YOUR COMPUTER OR DATA OR ANY OTHER DAMAGE YOU MAY INCUR IN CONNECTION WITH OUR WEBSITE. YOUR USE OF OUR WEBSITE AND ANY CONTRACTS OR SERVICES ARE AT YOUR OWN RISK. IN NO EVENT SHALL EITHER Darkwire OR THEIR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OF OUR WEBSITE, CONTRACTS AND SERVICES PURCHASED THROUGH OUR WEBSITE, THE DELAY OR INABILITY TO USE OUR WEBSITE OR OTHERWISE ARISING IN CONNECTION WITH OUR WEBSITE, CONTRACTS OR RELATED SERVICES, WHETHER BASED ON CONTRACT, TORT, STRICT LIABILITY OR OTHERWISE, EVEN IF ADVISED OF THE POSSIBILITY OF ANY SUCH DAMAGES. IN NO EVENT SHALL Darkwire’s LIABILITY FOR ANY DAMAGE CLAIM EXCEED THE AMOUNT PAID BY YOU TO Darkwire FOR THE TRANSACTION GIVING RISE TO SUCH DAMAGE CLAIM.
+
+
+
+
+ SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO YOU.
+
+
+
+
+ WITHOUT LIMITING THE FOREGOING, Darkwire DO NOT REPRESENT OR WARRANT THAT THE INFORMATION ON THE WEBITE IS ACCURATE, COMPLETE, RELIABLE, USEFUL, TIMELY OR CURRENT OR THAT OUR WEBSITE WILL OPERATE WITHOUT INTERRUPTION OR ERROR.
+
+
+
+
+ YOU AGREE THAT ALL TIMES, YOU WILL LOOK TO ATTORNEYS FROM WHOM YOU PURCHASE SERVICES FOR ANY CLAIMS OF ANY NATURE, INCLUDING LOSS, DAMAGE, OR WARRANTY. Darkwire AND THEIR RESPECTIVE AFFILIATES MAKE NO REPRESENTATION OR GUARANTEES ABOUT ANY CONTRACTS AND SERVICES OFFERED THROUGH OUR WEBSITE.
+
+
+
+
+ Darkwire MAKES NO REPRESENTATION THAT CONTENT PROVIDED ON OUR WEBSITE, CONTRACTS, OR RELATED SERVICES ARE APPLICABLE OR APPROPRIATE FOR USE IN ALL JURISDICTIONS.
+
+
+
+ Indemnification
+
+
+ You agree to defend, indemnify and hold Darkwire harmless from and against any and all claims, damages, costs and expenses, including attorneys' fees, arising from or related to your use of our Website or any Contracts or Services you purchase through it.
+
+
+ Changes
+
+
+ We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will try to provide at least 30 days notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.
+
+
+ By continuing to access or use our Service after those revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, please stop using the Service.
+
+
+ Contact Us
+
+
+ If you have any questions about these Terms, please contact us at hello[at]darkwire.io.
+
+
+
+
+
+ Donate
+
+
+ Darkwire is maintained and hosted by two developers with full-time jobs. If you get some value from this service we would appreciate any donation you can afford. We use these funds for server and DNS costs. Thank you!
+
+
+ Bitcoin
+
+
+ 189sPnHGcjP5uteg2UuNgcJ5eoaRAP4Bw4
+
+
+ Ethereum
+
+
+ 0x36dc407bB28aA1EE6AafBee0379Fe6Cff881758E
+
+
+ Litecoin
+
+
+ LUViQeSggBBtYoN2qNtXSuxYoRMzRY8CSX
+
+
+ PayPal:
+
+
+
+
+
+
+`;
diff --git a/client/src/components/Chat/Chat.js b/client/src/components/Chat/Chat.js
new file mode 100644
index 0000000..9790b44
--- /dev/null
+++ b/client/src/components/Chat/Chat.js
@@ -0,0 +1,297 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import sanitizeHtml from 'sanitize-html';
+import FileTransfer from 'components/FileTransfer';
+import { CornerDownRight } from 'react-feather';
+import { getSelectedText, hasTouchSupport } from '../../utils/dom';
+
+// Disable for now
+// import autosize from 'autosize'
+
+export class Chat extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ message: '',
+ touchSupport: hasTouchSupport,
+ shiftKeyDown: false,
+ };
+
+ this.commands = [
+ {
+ command: 'nick',
+ description: 'Changes nickname.',
+ paramaters: ['{username}'],
+ usage: '/nick {username}',
+ scope: 'global',
+ action: params => {
+ // eslint-disable-line
+ let newUsername = params.join(' ') || ''; // eslint-disable-line
+
+ // Remove things that arent digits or chars
+ newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-');
+
+ const errors = [];
+
+ if (!newUsername.trim().length) {
+ errors.push('Username cannot be blank');
+ }
+
+ if (newUsername.toString().length > 16) {
+ errors.push('Username cannot be greater than 16 characters');
+ }
+
+ if (!newUsername.match(/^[A-Z]/i)) {
+ errors.push('Username must start with a letter');
+ }
+
+ if (errors.length) {
+ return this.props.showNotice({
+ message: `${errors.join(', ')}`,
+ level: 'error',
+ });
+ }
+
+ this.props.sendEncryptedMessage({
+ type: 'CHANGE_USERNAME',
+ payload: {
+ id: this.props.userId,
+ newUsername,
+ currentUsername: this.props.username,
+ },
+ });
+ },
+ },
+ {
+ command: 'help',
+ description: 'Shows a list of commands.',
+ paramaters: [],
+ usage: '/help',
+ scope: 'local',
+ action: params => {
+ // eslint-disable-line
+ const validCommands = this.commands.map(command => `/${command.command}`);
+ this.props.showNotice({
+ message: `Valid commands: ${validCommands.sort().join(', ')}`,
+ level: 'info',
+ });
+ },
+ },
+ {
+ command: 'me',
+ description: 'Invoke virtual action',
+ paramaters: ['{action}'],
+ usage: '/me {action}',
+ scope: 'global',
+ action: params => {
+ // eslint-disable-line
+ const actionMessage = params.join(' ');
+ if (!actionMessage.trim().length) {
+ return false;
+ }
+
+ this.props.sendEncryptedMessage({
+ type: 'USER_ACTION',
+ payload: {
+ action: actionMessage,
+ },
+ });
+ },
+ },
+ {
+ command: 'clear',
+ description: 'Clears the chat screen',
+ paramaters: [],
+ usage: '/clear',
+ scope: 'local',
+ action: (params = null) => {
+ // eslint-disable-line
+ this.props.clearActivities();
+ },
+ },
+ ];
+ }
+
+ componentDidMount() {
+ if (!hasTouchSupport) {
+ // Disable for now due to vary issues:
+ // Paste not working, shift+enter line breaks
+ // autosize(this.textInput);
+ this.textInput.addEventListener('autosize:resized', () => {
+ this.props.scrollToBottom();
+ });
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.focusChat) {
+ if (!getSelectedText()) {
+ // Don't focus for now, evaluate UX benfits
+ // this.textInput.focus()
+ }
+ }
+ }
+
+ componentDidUpdate(nextProps, nextState) {
+ if (!nextState.message.trim().length) {
+ // autosize.update(this.textInput)
+ }
+ }
+
+ handleKeyUp(e) {
+ if (e.key === 'Shift') {
+ this.setState({
+ shiftKeyDown: false,
+ });
+ }
+ }
+
+ handleKeyPress(e) {
+ if (e.key === 'Shift') {
+ this.setState({
+ shiftKeyDown: true,
+ });
+ }
+ // Fix when autosize is enabled - line breaks require shift+enter twice
+ if (e.key === 'Enter' && !hasTouchSupport && !this.state.shiftKeyDown) {
+ e.preventDefault();
+ if (this.canSend()) {
+ this.sendMessage();
+ } else {
+ this.setState({
+ message: '',
+ });
+ }
+ }
+ }
+
+ executeCommand(command) {
+ const commandToExecute = this.commands.find(cmnd => cmnd.command === command.command);
+
+ if (commandToExecute) {
+ const { params } = command;
+ const commandResult = commandToExecute.action(params);
+
+ return commandResult;
+ }
+
+ return null;
+ }
+
+ handleSendClick() {
+ this.sendMessage.bind(this);
+ this.textInput.focus();
+ }
+
+ handleFormSubmit(evt) {
+ evt.preventDefault();
+ this.sendMessage();
+ }
+
+ parseCommand(message) {
+ const commandTrigger = {
+ command: null,
+ params: [],
+ };
+
+ if (message.charAt(0) === '/') {
+ const parsedCommand = message.replace('/', '').split(' ');
+ commandTrigger.command = sanitizeHtml(parsedCommand[0]) || null;
+ // Get params
+ if (parsedCommand.length >= 2) {
+ for (let i = 1; i < parsedCommand.length; i++) {
+ commandTrigger.params.push(parsedCommand[i]);
+ }
+ }
+
+ return commandTrigger;
+ }
+
+ return false;
+ }
+
+ sendMessage() {
+ if (!this.canSend()) {
+ return;
+ }
+
+ const { message } = this.state;
+ const isCommand = this.parseCommand(message);
+
+ if (isCommand) {
+ const res = this.executeCommand(isCommand);
+ if (res === false) {
+ return;
+ }
+ } else {
+ this.props.sendEncryptedMessage({
+ type: 'TEXT_MESSAGE',
+ payload: {
+ text: message,
+ timestamp: Date.now(),
+ },
+ });
+ }
+
+ this.setState({
+ message: '',
+ });
+ }
+
+ handleInputChange(evt) {
+ this.setState({
+ message: evt.target.value,
+ });
+ }
+
+ canSend() {
+ return this.state.message.trim().length;
+ }
+
+ render() {
+ const touchSupport = this.state.touchSupport;
+
+ return (
+
+ );
+ }
+}
+
+Chat.propTypes = {
+ sendEncryptedMessage: PropTypes.func.isRequired,
+ showNotice: PropTypes.func.isRequired,
+ userId: PropTypes.string.isRequired,
+ username: PropTypes.string.isRequired,
+ clearActivities: PropTypes.func.isRequired,
+ focusChat: PropTypes.bool.isRequired,
+ scrollToBottom: PropTypes.func.isRequired,
+ translations: PropTypes.object.isRequired,
+};
+
+export default Chat;
diff --git a/client/src/components/Chat/Chat.test.js b/client/src/components/Chat/Chat.test.js
index cf1b6ae..b14a717 100644
--- a/client/src/components/Chat/Chat.test.js
+++ b/client/src/components/Chat/Chat.test.js
@@ -1,17 +1,253 @@
-import React from 'react'
-import { mount } from 'enzyme'
-import toJson from 'enzyme-to-json'
-import { Chat } from './index.js'
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
-const sendEncryptedMessage = jest.fn()
+import { Chat } from './Chat';
-test('Chat Component', () => {
- const component = mount(
- {}} focusChat={false} userId="foo" username="user" showNotice={() => {}} clearActivities={() => {}} sendEncryptedMessage={sendEncryptedMessage} translations={{}}/>
- )
+import * as dom from 'utils/dom';
- const componentJSON = toJson(component)
+const translations = {
+ typePlaceholder: 'inputplaceholder',
+};
- expect(component).toMatchSnapshot()
- expect(componentJSON.children.length).toBe(1)
-})
+// Fake date
+jest.spyOn(global.Date, 'now').mockImplementation(() => new Date('2020-03-14T11:01:58.135Z').valueOf());
+
+// To change touch support
+jest.mock('../../utils/dom');
+
+describe('Chat component', () => {
+ afterEach(() => {
+ // Reset touch support
+ dom.hasTouchSupport = false;
+ });
+
+ it('should display', () => {
+ const { asFragment } = render(
+ {}}
+ focusChat={false}
+ userId="foo"
+ username="user"
+ showNotice={() => {}}
+ clearActivities={() => {}}
+ sendEncryptedMessage={() => {}}
+ translations={{}}
+ />,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('can send message', () => {
+ const sendEncryptedMessage = jest.fn();
+
+ render(
+ {}}
+ focusChat={false}
+ userId="foo"
+ username="user"
+ showNotice={() => {}}
+ clearActivities={() => {}}
+ sendEncryptedMessage={sendEncryptedMessage}
+ translations={translations}
+ />,
+ );
+
+ const textarea = screen.getByPlaceholderText(translations.typePlaceholder);
+
+ // Validate but without text
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).not.toHaveBeenCalled();
+
+ // Type test
+ fireEvent.change(textarea, { target: { value: 'test' } });
+ // Validate
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).toHaveBeenLastCalledWith({
+ payload: { text: 'test', timestamp: 1584183718135 },
+ type: 'TEXT_MESSAGE',
+ });
+
+ // Validate (textarea should be empty)
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).toHaveBeenCalledTimes(1);
+ });
+
+ it("shouldn't send message with Shift+enter", () => {
+ const sendEncryptedMessage = jest.fn();
+
+ render(
+ {}}
+ focusChat={false}
+ userId="foo"
+ username="user"
+ showNotice={() => {}}
+ clearActivities={() => {}}
+ sendEncryptedMessage={sendEncryptedMessage}
+ translations={translations}
+ />,
+ );
+
+ const textarea = screen.getByPlaceholderText(translations.typePlaceholder);
+
+ // Test shift effect
+ fireEvent.change(textarea, { target: { value: 'test2' } });
+ fireEvent.keyDown(textarea, { key: 'Shift' });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+ fireEvent.keyUp(textarea, { key: 'Shift' });
+
+ expect(sendEncryptedMessage).toHaveBeenCalledTimes(0);
+
+ // Now we want to send the message
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).toHaveBeenCalledTimes(1);
+
+ expect(sendEncryptedMessage).toHaveBeenLastCalledWith({
+ payload: { text: 'test2', timestamp: 1584183718135 },
+ type: 'TEXT_MESSAGE',
+ });
+ });
+
+ it('should send commands', () => {
+ const sendEncryptedMessage = jest.fn();
+ const showNotice = jest.fn();
+ const clearActivities = jest.fn();
+
+ render(
+ {}}
+ focusChat={false}
+ userId="foo"
+ username="user"
+ showNotice={showNotice}
+ clearActivities={clearActivities}
+ sendEncryptedMessage={sendEncryptedMessage}
+ translations={translations}
+ />,
+ );
+
+ const textarea = screen.getByPlaceholderText(translations.typePlaceholder);
+
+ // Test /help
+ fireEvent.change(textarea, { target: { value: '/help' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(showNotice).toHaveBeenLastCalledWith({
+ level: 'info',
+ message: 'Valid commands: /clear, /help, /me, /nick',
+ });
+
+ // Test /me
+ fireEvent.change(textarea, { target: { value: '/me' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).not.toHaveBeenCalled();
+
+ fireEvent.change(textarea, { target: { value: '/me action' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).toHaveBeenLastCalledWith({
+ payload: { action: 'action' },
+ type: 'USER_ACTION',
+ });
+
+ // Test /clear
+ fireEvent.change(textarea, { target: { value: '/clear' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(clearActivities).toHaveBeenLastCalledWith();
+
+ // Test /nick/clear
+ fireEvent.change(textarea, { target: { value: '/nick john!Th3Ripp&3r' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).toHaveBeenLastCalledWith({
+ payload: { currentUsername: 'user', id: 'foo', newUsername: 'john-Th3Ripp-3r' },
+ type: 'CHANGE_USERNAME',
+ });
+
+ // Test /nick
+ fireEvent.change(textarea, { target: { value: '/nick' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(showNotice).toHaveBeenLastCalledWith({
+ level: 'error',
+ message: 'Username cannot be blank, Username must start with a letter',
+ });
+
+ // Test /nick
+ fireEvent.change(textarea, { target: { value: '/nick 3po' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(showNotice).toHaveBeenLastCalledWith({
+ level: 'error',
+ message: 'Username must start with a letter',
+ });
+
+ // Test /nick
+ fireEvent.change(textarea, {
+ target: { value: '/nick 3po3ralotsofcrapscharactersforyourpleasureandnotmine' },
+ });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+
+ expect(sendEncryptedMessage).toHaveBeenLastCalledWith({
+ payload: { currentUsername: 'user', id: 'foo', newUsername: 'john-Th3Ripp-3r' },
+ type: 'CHANGE_USERNAME',
+ });
+
+ // Test badcommand
+ fireEvent.change(textarea, { target: { value: '/void' } });
+ fireEvent.keyDown(textarea, { key: 'Enter' });
+ });
+
+ it('should work with touch support', () => {
+ // Enable touch support
+ dom.hasTouchSupport = true;
+
+ jest.mock('../../utils/dom', () => {
+ return {
+ getSelectedText: jest.fn(),
+ hasTouchSupport: true,
+ };
+ });
+
+ const sendEncryptedMessage = jest.fn();
+
+ const { getByTitle } = render(
+ {}}
+ focusChat={false}
+ userId="foo"
+ username="user"
+ showNotice={() => {}}
+ clearActivities={() => {}}
+ sendEncryptedMessage={sendEncryptedMessage}
+ translations={translations}
+ />,
+ );
+
+ const textarea = screen.getByPlaceholderText(translations.typePlaceholder);
+
+ // Type test
+ fireEvent.change(textarea, { target: { value: 'test' } });
+
+ // Touch send button
+ fireEvent.click(getByTitle('Send'));
+
+ expect(sendEncryptedMessage).toHaveBeenLastCalledWith({
+ payload: { text: 'test', timestamp: 1584183718135 },
+ type: 'TEXT_MESSAGE',
+ });
+
+ // Should not send message
+ fireEvent.click(getByTitle('Send'));
+
+ expect(sendEncryptedMessage).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/client/src/components/Chat/__snapshots__/Chat.test.js.snap b/client/src/components/Chat/__snapshots__/Chat.test.js.snap
index 0627a01..26d06c8 100644
--- a/client/src/components/Chat/__snapshots__/Chat.test.js.snap
+++ b/client/src/components/Chat/__snapshots__/Chat.test.js.snap
@@ -1,3 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Chat Component 1`] = `ReactWrapper {}`;
+exports[`Chat component should display 1`] = `
+
+
+
+
+
+
+`;
diff --git a/client/src/components/Chat/index.js b/client/src/components/Chat/index.js
index 62f7ed0..338a49b 100644
--- a/client/src/components/Chat/index.js
+++ b/client/src/components/Chat/index.js
@@ -1,284 +1,7 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import sanitizeHtml from 'sanitize-html'
-import FileTransfer from 'components/FileTransfer'
-import { CornerDownRight } from 'react-feather'
+import Chat from './Chat'
import { connect } from 'react-redux'
import { clearActivities, showNotice, sendEncryptedMessage } from '../../actions'
-import { getSelectedText, hasTouchSupport } from '../../utils/dom'
-// Disable for now
-// import autosize from 'autosize'
-
-export class Chat extends Component {
- constructor(props) {
- super(props)
- this.state = {
- message: '',
- touchSupport: hasTouchSupport,
- shiftKeyDown: false,
- }
-
- this.commands = [{
- command: 'nick',
- description: 'Changes nickname.',
- paramaters: ['{username}'],
- usage: '/nick {username}',
- scope: 'global',
- action: (params) => { // eslint-disable-line
- let newUsername = params.join(' ') || '' // eslint-disable-line
-
- // Remove things that arent digits or chars
- newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-')
-
- const errors = []
-
- if (!newUsername.trim().length) {
- errors.push('Username cannot be blank')
- }
-
- if (newUsername.toString().length > 16) {
- errors.push('Username cannot be greater than 16 characters')
- }
-
- if (!newUsername.match(/^[A-Z]/i)) {
- errors.push('Username must start with a letter')
- }
-
- if (errors.length) {
- return this.props.showNotice({
- message: `${errors.join(', ')}`,
- level: 'error',
- })
- }
-
- this.props.sendEncryptedMessage({
- type: 'CHANGE_USERNAME',
- payload: {
- id: this.props.userId,
- newUsername,
- currentUsername: this.props.username,
- },
- })
- },
- }, {
- command: 'help',
- description: 'Shows a list of commands.',
- paramaters: [],
- usage: '/help',
- scope: 'local',
- action: (params) => { // eslint-disable-line
- const validCommands = this.commands.map(command => `/${command.command}`)
- this.props.showNotice({
- message: `Valid commands: ${validCommands.sort().join(', ')}`,
- level: 'info',
- })
- },
- }, {
- command: 'me',
- description: 'Invoke virtual action',
- paramaters: ['{action}'],
- usage: '/me {action}',
- scope: 'global',
- action: (params) => { // eslint-disable-line
- const actionMessage = params.join(' ')
- if (!actionMessage.trim().length) {
- return false
- }
-
- this.props.sendEncryptedMessage({
- type: 'USER_ACTION',
- payload: {
- action: actionMessage,
- },
- })
- },
- }, {
- command: 'clear',
- description: 'Clears the chat screen',
- paramaters: [],
- usage: '/clear',
- scope: 'local',
- action: (params = null) => { // eslint-disable-line
- this.props.clearActivities()
- },
- }]
- }
-
- componentDidMount() {
- if (!hasTouchSupport) {
- // Disable for now due to vary issues:
- // Paste not working, shift+enter line breaks
- // autosize(this.textInput);
- this.textInput.addEventListener('autosize:resized', () => {
- this.props.scrollToBottom()
- })
- }
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.focusChat) {
- if (!getSelectedText()) {
- // Don't focus for now, evaulate UX benfits
- // this.textInput.focus()
- }
- }
- }
-
- componentDidUpdate(nextProps, nextState) {
- if (!nextState.message.trim().length) {
- // autosize.update(this.textInput)
- }
- }
-
- handleKeyUp(e) {
- if (e.key === 'Shift') {
- this.setState({
- shiftKeyDown: false,
- })
- }
- }
-
- handleKeyPress(e) {
- if (e.key === 'Shift') {
- this.setState({
- shiftKeyDown: true,
- })
- }
- // Fix when autosize is enabled - line breaks require shift+enter twice
- if (e.key === 'Enter' && !hasTouchSupport && !this.state.shiftKeyDown) {
- e.preventDefault()
- if (this.canSend()) {
- this.sendMessage()
- } else {
- this.setState({
- message: '',
- })
- }
- }
- }
-
- executeCommand(command) {
- const commandToExecute = this.commands.find(cmnd => cmnd.command === command.command)
-
- if (commandToExecute) {
- const { params } = command
- const commandResult = commandToExecute.action(params)
-
- return commandResult
- }
-
- return null
- }
-
- handleSendClick() {
- this.sendMessage.bind(this)
- this.textInput.focus()
- }
-
- handleFormSubmit(evt) {
- evt.preventDefault()
- this.sendMessage()
- }
-
- parseCommand(message) {
- const commandTrigger = {
- command: null,
- params: [],
- }
-
- if (message.charAt(0) === '/') {
- const parsedCommand = message.replace('/', '').split(' ')
- commandTrigger.command = sanitizeHtml(parsedCommand[0]) || null
- // Get params
- if (parsedCommand.length >= 2) {
- for (let i = 1; i < parsedCommand.length; i++) {
- commandTrigger.params.push(parsedCommand[i])
- }
- }
-
- return commandTrigger
- }
-
- return false
- }
-
- sendMessage() {
- if (!this.canSend()) {
- return
- }
-
- const { message } = this.state
- const isCommand = this.parseCommand(message)
-
- if (isCommand) {
- const res = this.executeCommand(isCommand)
- if (res === false) {
- return
- }
- } else {
- this.props.sendEncryptedMessage({
- type: 'TEXT_MESSAGE',
- payload: {
- text: message,
- timestamp: Date.now(),
- },
- })
- }
-
- this.setState({
- message: '',
- })
- }
-
- handleInputChange(evt) {
- this.setState({
- message: evt.target.value,
- })
- }
-
- canSend() {
- return this.state.message.trim().length
- }
-
- render() {
- const touchSupport = this.state.touchSupport
-
- return (
-
- { this.textInput = input }}
- autoFocus
- className="chat"
- value={this.state.message}
- placeholder={this.props.translations.typePlaceholder}
- onChange={this.handleInputChange.bind(this)} />
-
-
- {touchSupport &&
-
-
-
- }
-
-
- )
- }
-}
-
-Chat.propTypes = {
- sendEncryptedMessage: PropTypes.func.isRequired,
- showNotice: PropTypes.func.isRequired,
- userId: PropTypes.string.isRequired,
- username: PropTypes.string.isRequired,
- clearActivities: PropTypes.func.isRequired,
- focusChat: PropTypes.bool.isRequired,
- scrollToBottom: PropTypes.func.isRequired,
- translations: PropTypes.object.isRequired,
-}
const mapStateToProps = state => ({
username: state.user.username,
diff --git a/client/src/components/Connecting/Connecting.test.js b/client/src/components/Connecting/Connecting.test.js
new file mode 100644
index 0000000..faa9bdb
--- /dev/null
+++ b/client/src/components/Connecting/Connecting.test.js
@@ -0,0 +1,9 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import Connecting from '.'
+
+test('Connecting component is displaying', async () => {
+ const {asFragment} = render( )
+
+ expect(asFragment()).toMatchSnapshot()
+})
\ No newline at end of file
diff --git a/client/src/components/Connecting/__snapshots__/Connecting.test.js.snap b/client/src/components/Connecting/__snapshots__/Connecting.test.js.snap
new file mode 100644
index 0000000..1362428
--- /dev/null
+++ b/client/src/components/Connecting/__snapshots__/Connecting.test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Connecting component is displaying 1`] = `
+
+
+ Please wait while we secure a connection to Darkwire...
+
+
+`;
diff --git a/client/src/components/FileTransfer/FileTransfer.test.js b/client/src/components/FileTransfer/FileTransfer.test.js
new file mode 100644
index 0000000..73ca8d3
--- /dev/null
+++ b/client/src/components/FileTransfer/FileTransfer.test.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import { render, screen, fireEvent, createEvent } from '@testing-library/react';
+import FileTransfer from '.';
+
+test('FileTransfer component is displaying', async () => {
+ const { asFragment } = render( {}} />);
+
+ expect(asFragment()).toMatchSnapshot();
+});
+
+// Skipped as broken in this component version. Should be fixed later.
+test.skip('FileTransfer component detect bad browser support', async () => {
+ const { File } = window;
+
+ // Remove one of component dependency
+ delete window.File;
+
+ const { asFragment } = render( {}} />);
+
+ expect(asFragment()).toMatchSnapshot();
+
+ window.File = File;
+});
+
+test('Try to send file', async done => {
+ const sendEncryptedMessage = data => {
+ try {
+ expect(data).toMatchObject({
+ payload: { encodedFile: 'dGV4dGZpbGU=', fileName: 'filename-png', fileType: 'text/plain' },
+ type: 'SEND_FILE',
+ });
+ done();
+ } catch (error) {
+ done(error);
+ }
+ };
+
+ render( );
+
+ const input = screen.getByPlaceholderText('Choose a file...');
+
+ const testFile = new File(['textfile'], 'filename.png', { type: 'text/plain' });
+
+ // Fire change event
+ fireEvent.change(input, { target: { files: [testFile] } });
+});
+
+test('Try to send no file', async () => {
+ render( {}} />);
+
+ const input = screen.getByPlaceholderText('Choose a file...');
+
+ // Fire change event
+ fireEvent.change(input, { target: { files: [] } });
+});
+
+test('Try to send unsupported file', async () => {
+ window.alert = jest.fn();
+
+ render( {}} />);
+
+ const input = screen.getByPlaceholderText('Choose a file...');
+
+ const testFile = new File(['textfile'], 'filename.fake', { type: 'text/plain' });
+
+ // Create thange event with fake file
+ const changeEvent = createEvent.change(input, { target: { files: [testFile] } });
+
+ // Fire change event
+ fireEvent(input, changeEvent);
+
+ expect(window.alert).toHaveBeenCalledWith('File type not supported');
+});
+
+test('Try to send too big file', async () => {
+ window.alert = jest.fn();
+
+ render( {}} />);
+
+ const input = screen.getByPlaceholderText('Choose a file...');
+
+ var fileContent = new Uint8Array(4000001);
+
+ const testFile = new File([fileContent], 'filename.png', { type: 'text/plain' });
+
+ // Fire change event
+ fireEvent.change(input, { target: { files: [testFile] } });
+
+ expect(window.alert).toHaveBeenCalledWith('Max filesize is 4MB');
+});
diff --git a/client/src/components/FileTransfer/__snapshots__/FileTransfer.test.js.snap b/client/src/components/FileTransfer/__snapshots__/FileTransfer.test.js.snap
new file mode 100644
index 0000000..221cab1
--- /dev/null
+++ b/client/src/components/FileTransfer/__snapshots__/FileTransfer.test.js.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FileTransfer component is displaying 1`] = `
+
+
+
+`;
diff --git a/client/src/components/FileTransfer/index.js b/client/src/components/FileTransfer/index.js
index 00381a5..040cc46 100644
--- a/client/src/components/FileTransfer/index.js
+++ b/client/src/components/FileTransfer/index.js
@@ -102,7 +102,7 @@ export default class FileTransfer extends Component {
}
return (
-
this._fileInput = c} />
+
this._fileInput = c} />
diff --git a/client/src/components/Home/Activity.test.js b/client/src/components/Home/Activity.test.js
new file mode 100644
index 0000000..8b1d7cf
--- /dev/null
+++ b/client/src/components/Home/Activity.test.js
@@ -0,0 +1,201 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import Activity from './Activity';
+import { Provider } from 'react-redux';
+import configureStore from 'store';
+
+const store = configureStore();
+
+//jest.mock('components/T'); // Need store
+
+describe('Activity component', () => {
+ it('should display', () => {
+ const activity = {
+ type: '',
+ };
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display TEXT_MESSAGE', () => {
+ const activity = {
+ type: 'TEXT_MESSAGE',
+ username: 'alice',
+ timestamp: new Date('2020-03-14T11:01:58.135Z').valueOf(),
+ text: 'Hi!',
+ };
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display USER_ENTER', () => {
+ const activity = {
+ type: 'USER_ENTER',
+ username: 'alice',
+ };
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display USER_EXIT', () => {
+ const activity = {
+ type: 'USER_EXIT',
+ username: 'alice',
+ };
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display TOGGLE_LOCK_ROOM', () => {
+ const activity = {
+ type: 'TOGGLE_LOCK_ROOM',
+ locked: true,
+ username: 'alice',
+ };
+ const { asFragment, rerender } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ activity.locked = false;
+
+ rerender(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display NOTICE', () => {
+ const activity = {
+ type: 'NOTICE',
+ message: 'Hello world!',
+ };
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display CHANGE_USERNAME', () => {
+ const activity = {
+ type: 'CHANGE_USERNAME',
+ currentUsername: 'alice',
+ newUsername: 'alicette',
+ };
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display USER_ACTION', () => {
+ const activity = {
+ type: 'USER_ACTION',
+ username: 'alice',
+ action: 'did right!',
+ };
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display RECEIVE_FILE', () => {
+ const activity = {
+ type: 'RECEIVE_FILE',
+ username: 'alice',
+ fileName: 'alice.pdf',
+ encodedFile: 'dGV4dGZpbGU=',
+ fileType: 'text/plain',
+ };
+ global.URL.createObjectURL = jest.fn(data => `url:${data}`);
+
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display RECEIVE_FILE with image', () => {
+ global.URL.createObjectURL = jest.fn(data => `url:${data}`);
+
+ const mockScrollToBottom = jest.fn();
+
+ const activity = {
+ type: 'RECEIVE_FILE',
+ username: 'alice',
+ fileName: 'alice.jpg',
+ encodedFile: 'dGV4dGZpbGU=',
+ fileType: 'image/jpg',
+ };
+
+ const { asFragment, getByAltText } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ const image = getByAltText('alice.jpg from alice');
+ fireEvent.load(image);
+ expect(mockScrollToBottom).toHaveBeenCalled();
+ });
+
+ it('should display SEND_FILE', () => {
+ global.URL.createObjectURL = jest.fn(data => `url:${data}`);
+ const activity = {
+ type: 'SEND_FILE',
+ username: 'alice',
+ fileName: 'alice.pdf',
+ encodedFile: 'dGV4dGZpbGU=',
+ fileType: 'text/plain',
+ };
+
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+});
diff --git a/client/src/components/Home/ActivityList.js b/client/src/components/Home/ActivityList.js
index 3c05a71..1e4f019 100644
--- a/client/src/components/Home/ActivityList.js
+++ b/client/src/components/Home/ActivityList.js
@@ -72,7 +72,7 @@ class ActivityList extends Component {
render() {
return (
-
this.messageStream = el}>
+
this.messageStream = el} data-testid="main-div">
this.activitiesList = el}>
{this.props.activities.map((activity, index) => (
diff --git a/client/src/components/Home/ActivityList.test.js b/client/src/components/Home/ActivityList.test.js
new file mode 100644
index 0000000..af1c769
--- /dev/null
+++ b/client/src/components/Home/ActivityList.test.js
@@ -0,0 +1,160 @@
+import React from 'react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import ActivityList from './ActivityList';
+import { Provider } from 'react-redux';
+import configureStore from 'store';
+
+const store = configureStore();
+
+jest.useFakeTimers();
+
+describe('ActivityList component', () => {
+ it('should display', () => {
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should display with activities', () => {
+ const activities = [
+ {
+ type: 'TEXT_MESSAGE',
+ username: 'alice',
+ timestamp: new Date('2020-03-14T11:01:58.135Z').valueOf(),
+ text: 'Hi!',
+ },
+ {
+ type: 'USER_ENTER',
+ username: 'alice',
+ },
+ {
+ type: 'CHANGE_USERNAME',
+ currentUsername: 'alice',
+ newUsername: 'alicette',
+ },
+ ];
+ const { asFragment } = render(
+
+
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should show About modal', async () => {
+ const mockOpenModal = jest.fn();
+
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ fireEvent.click(getByText('By using Darkwire, you are agreeing to our Acceptable Use Policy and Terms of Service'));
+ jest.runAllTimers();
+
+ expect(mockOpenModal.mock.calls[0][0]).toBe('About');
+ });
+
+ it('should focus chat', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+ fireEvent.click(getByTestId('main-div'));
+ jest.runAllTimers();
+ });
+
+ it('should handle scroll', () => {
+ const mockSetScrollToBottom = jest.fn();
+ jest
+ .spyOn(Element.prototype, 'clientHeight', 'get')
+ .mockReturnValueOnce(400)
+ .mockReturnValueOnce(200)
+ .mockReturnValueOnce(400)
+ .mockReturnValueOnce(200);
+
+ Element.prototype.getBoundingClientRect = jest
+ .fn()
+ .mockReturnValueOnce({ top: 0 })
+ .mockReturnValueOnce({ top: 60 })
+ .mockReturnValueOnce({ top: 0 })
+ .mockReturnValueOnce({ top: 261 });
+
+ const { getByTestId, rerender } = render(
+
+
+ ,
+ );
+ fireEvent.scroll(getByTestId('main-div'));
+
+ expect(mockSetScrollToBottom).toHaveBeenLastCalledWith(true);
+
+ rerender(
+
+
+ ,
+ );
+
+ fireEvent.scroll(getByTestId('main-div'));
+
+ expect(mockSetScrollToBottom).toHaveBeenCalledTimes(2);
+ expect(mockSetScrollToBottom).toHaveBeenLastCalledWith(false);
+ });
+
+ it('should scroll to bottom on new message', () => {
+ const mockSetScrollToBottom = jest.fn();
+
+ jest.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(42);
+ const mockScrollTop = jest.spyOn(Element.prototype, 'scrollTop', 'set');
+
+ const { rerender } = render(
+
+
+ ,
+ );
+
+ rerender(
+
+
+ ,
+ );
+
+ jest.runAllTimers();
+
+ expect(mockScrollTop).toHaveBeenLastCalledWith(42);
+ });
+});
diff --git a/client/src/components/Home/Home.js b/client/src/components/Home/Home.js
index 32e717c..ef2761b 100644
--- a/client/src/components/Home/Home.js
+++ b/client/src/components/Home/Home.js
@@ -249,6 +249,7 @@ Home.defaultProps = {
Home.propTypes = {
receiveEncryptedMessage: PropTypes.func.isRequired,
+ receiveUnencryptedMessage: PropTypes.func.isRequired,
createUser: PropTypes.func.isRequired,
activities: PropTypes.array.isRequired,
username: PropTypes.string.isRequired,
diff --git a/client/src/components/Home/Home.test.js b/client/src/components/Home/Home.test.js
new file mode 100644
index 0000000..e1ef836
--- /dev/null
+++ b/client/src/components/Home/Home.test.js
@@ -0,0 +1,73 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import Home from './Home';
+import { Provider } from 'react-redux';
+import configureStore from 'store';
+
+const store = configureStore();
+
+jest.mock('react-modal'); // Cant load modal without root app element
+jest.mock('utils/socket', () => {
+ // Avoid exception
+ return {
+ connect: jest.fn().mockImplementation(() => {
+ return {
+ on: jest.fn(),
+ emit: jest.fn(),
+ };
+ }),
+ };
+}); //
+
+jest.mock('utils/crypto', () => {
+ // Need window.crytpo.subtle
+ return jest.fn().mockImplementation(() => {
+ return {
+ createEncryptDecryptKeys: () => {
+ return {
+ privateKey: 'private',
+ publicKey: 'public',
+ };
+ },
+ exportKey: () => {
+ return 'exportedkey';
+ },
+ };
+ });
+});
+
+test('Home component is displaying', async () => {
+ const { asFragment } = render(
+
+ {}}
+ activities={[]}
+ match={{ params: { roomId: 'roomTest' } }}
+ createUser={() => {}}
+ toggleSocketConnected={() => {}}
+ receiveEncryptedMessage={() => {}}
+ receiveUnencryptedMessage={() => {}}
+ scrolledToBottom={true}
+ setScrolledToBottom={() => {}}
+ iAmOwner={true}
+ roomLocked={false}
+ userId={'userId'}
+ roomId={'testId'}
+ sendEncryptedMessage={() => {}}
+ sendUnencryptedMessage={() => {}}
+ socketConnected={false}
+ toggleSoundEnabled={() => {}}
+ soundIsEnabled={false}
+ faviconCount={0}
+ toggleWindowFocus={() => {}}
+ closeModal={() => {}}
+ publicKey={{}}
+ username={'linus'}
+ />
+ ,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+});
diff --git a/client/src/components/Home/__snapshots__/Activity.test.js.snap b/client/src/components/Home/__snapshots__/Activity.test.js.snap
new file mode 100644
index 0000000..ce3e8a3
--- /dev/null
+++ b/client/src/components/Home/__snapshots__/Activity.test.js.snap
@@ -0,0 +1,269 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Activity component should display 1`] = ` `;
+
+exports[`Activity component should display CHANGE_USERNAME 1`] = `
+
+
+
+
+
+
+ alice
+
+ changed their name to
+
+ alicette
+
+
+
+
+
+
+`;
+
+exports[`Activity component should display NOTICE 1`] = `
+
+
+
+`;
+
+exports[`Activity component should display RECEIVE_FILE 1`] = `
+
+
+
+`;
+
+exports[`Activity component should display RECEIVE_FILE with image 1`] = `
+
+
+
+`;
+
+exports[`Activity component should display SEND_FILE 1`] = `
+
+
+
+`;
+
+exports[`Activity component should display TEXT_MESSAGE 1`] = `
+
+
+
+
+ alice
+
+
+ 11:01 AM
+
+
+
+
+ Hi!
+
+
+
+
+`;
+
+exports[`Activity component should display TOGGLE_LOCK_ROOM 1`] = `
+
+
+
+
+
+
+ alice
+
+ locked the room
+
+
+
+
+
+`;
+
+exports[`Activity component should display TOGGLE_LOCK_ROOM 2`] = `
+
+
+
+
+
+
+ alice
+
+ unlocked the room
+
+
+
+
+
+`;
+
+exports[`Activity component should display USER_ACTION 1`] = `
+
+
+
+
+ *
+
+ alice
+
+ did right!
+
+
+
+
+`;
+
+exports[`Activity component should display USER_ENTER 1`] = `
+
+
+
+
+
+
+ alice
+
+ joined
+
+
+
+
+
+`;
+
+exports[`Activity component should display USER_EXIT 1`] = `
+
+
+
+
+
+
+ alice
+
+ left
+
+
+
+
+
+`;
diff --git a/client/src/components/Home/__snapshots__/ActivityList.test.js.snap b/client/src/components/Home/__snapshots__/ActivityList.test.js.snap
new file mode 100644
index 0000000..8cc1f2d
--- /dev/null
+++ b/client/src/components/Home/__snapshots__/ActivityList.test.js.snap
@@ -0,0 +1,235 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ActivityList component should display 1`] = `
+
+
+
+`;
+
+exports[`ActivityList component should display with activities 1`] = `
+
+
+
+`;
diff --git a/client/src/components/Home/__snapshots__/Home.test.js.snap b/client/src/components/Home/__snapshots__/Home.test.js.snap
new file mode 100644
index 0000000..8d4ab90
--- /dev/null
+++ b/client/src/components/Home/__snapshots__/Home.test.js.snap
@@ -0,0 +1,365 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Home component is displaying 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Disconnected
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/client/src/components/Message/Message.test.js b/client/src/components/Message/Message.test.js
new file mode 100644
index 0000000..bb222cc
--- /dev/null
+++ b/client/src/components/Message/Message.test.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import Message from '.';
+
+test('Message component is displaying', async () => {
+ const { asFragment } = render(
+
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+});
diff --git a/client/src/components/Message/__snapshots__/Message.test.js.snap b/client/src/components/Message/__snapshots__/Message.test.js.snap
new file mode 100644
index 0000000..70ee819
--- /dev/null
+++ b/client/src/components/Message/__snapshots__/Message.test.js.snap
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Message component is displaying 1`] = `
+
+
+
+
+ linus
+
+
+ 7:44 PM
+
+
+
+
+ we come in peace
+
+
+
+
+`;
diff --git a/client/src/components/Nav/Nav.test.js b/client/src/components/Nav/Nav.test.js
new file mode 100644
index 0000000..125ecef
--- /dev/null
+++ b/client/src/components/Nav/Nav.test.js
@@ -0,0 +1,303 @@
+import React from 'react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import Nav from '.';
+import mock$ from 'jquery';
+
+const mockTooltip = jest.fn().mockImplementation(param => {
+ // console.log('tooltip', param);
+});
+
+const mockCollapse = jest.fn().mockImplementation(param => {
+ // console.log('collapse', param);
+});
+
+jest.mock('jquery', () => {
+ return jest.fn().mockImplementation(param => {
+ // console.log('$', param);
+ if (typeof param === 'function') {
+ param();
+ }
+ return {
+ tooltip: mockTooltip,
+ collapse: mockCollapse,
+ };
+ });
+});
+
+jest.mock('shortid', () => {
+ return {
+ generate() {
+ return 'fakeid';
+ },
+ };
+});
+
+jest.useFakeTimers();
+
+const mockTranslations = {
+ newRoomButton: 'new room',
+ settingsButton: 'settings',
+ aboutButton: 'about',
+};
+
+test('Nav component is displaying', async () => {
+ const { asFragment } = render(
+ {}}
+ openModal={() => {}}
+ iAmOwner={true}
+ translations={{}}
+ />,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ expect(mock$).toHaveBeenCalledWith('.room-id');
+ expect(mock$).toHaveBeenLastCalledWith('.lock-room');
+ expect(mockTooltip).toHaveBeenLastCalledWith({ trigger: 'manual' });
+});
+
+test('Nav component is displaying with another configuration and can rerender', async () => {
+ const { asFragment, rerender } = render(
+ {}}
+ openModal={() => {}}
+ iAmOwner={false}
+ translations={{}}
+ />,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ rerender(
+ {}}
+ openModal={() => {}}
+ iAmOwner={false}
+ translations={{}}
+ />,
+ );
+
+ expect(mock$).toHaveBeenCalledWith('.me-icon-wrap');
+ expect(mock$).toHaveBeenLastCalledWith('.owner-icon-wrap');
+});
+
+test('Can copy room url', async () => {
+ document.execCommand = jest.fn(() => true);
+
+ const toggleLockRoom = jest.fn();
+
+ const { getByText } = render(
+ {}}
+ iAmOwner={false}
+ translations={{}}
+ />,
+ );
+
+ fireEvent.click(getByText(`/testRoom`));
+
+ expect(document.execCommand).toHaveBeenLastCalledWith('copy');
+ expect(mock$).toHaveBeenCalledTimes(15);
+ expect(mockTooltip).toHaveBeenLastCalledWith('show');
+
+ // Wait tooltip closing
+ jest.runAllTimers();
+
+ expect(mock$).toHaveBeenCalledTimes(18);
+ expect(mockTooltip).toHaveBeenLastCalledWith('hide');
+});
+
+test('Can lock/unlock room is room owner only', async () => {
+ const toggleLockRoom = jest.fn();
+
+ const { rerender, getByTitle } = render(
+ {}}
+ iAmOwner={true}
+ translations={{}}
+ />,
+ );
+
+ const toggleLockRoomButton = getByTitle('You must be the owner to lock or unlock the room');
+
+ fireEvent.click(toggleLockRoomButton);
+
+ expect(toggleLockRoom).toHaveBeenCalledWith();
+
+ fireEvent.click(toggleLockRoomButton);
+
+ expect(toggleLockRoom).toHaveBeenCalledTimes(2);
+
+ // We are not the room owner anymore
+ rerender(
+ {}}
+ iAmOwner={false}
+ translations={{}}
+ />,
+ );
+
+ fireEvent.click(toggleLockRoomButton);
+
+ expect(toggleLockRoom).toHaveBeenCalledTimes(2);
+ expect(mock$).toHaveBeenLastCalledWith('.lock-room');
+ expect(mockTooltip).toHaveBeenLastCalledWith('show');
+});
+
+test('Can show user list', async () => {
+ // Test with one user owner and me
+ const { getByTitle, getByText, queryByTitle, rerender } = render(
+ {}}
+ openModal={() => {}}
+ iAmOwner={true}
+ translations={{}}
+ />,
+ );
+
+ fireEvent.click(getByTitle('Users'));
+
+ await waitFor(() => expect(getByText('alan')).toBeInTheDocument());
+ await waitFor(() => expect(getByTitle('Owner')).toBeInTheDocument());
+ await waitFor(() => expect(getByTitle('Me')).toBeInTheDocument());
+
+ // Test with two user not owner, not me
+ rerender(
+ {}}
+ openModal={() => {}}
+ iAmOwner={true}
+ translations={{}}
+ />,
+ );
+ await waitFor(() => expect(getByText('alan')).toBeInTheDocument());
+ await waitFor(() => expect(getByText('dan')).toBeInTheDocument());
+ expect(queryByTitle('Owner')).not.toBeInTheDocument();
+ expect(queryByTitle('Me')).not.toBeInTheDocument();
+});
+
+test('Can open settings', async () => {
+ const openModal = jest.fn();
+
+ // Test with one user owner and me
+ const { getByText } = render(
+ {}}
+ openModal={openModal}
+ iAmOwner={true}
+ translations={mockTranslations}
+ />,
+ );
+
+ fireEvent.click(getByText(mockTranslations.settingsButton));
+
+ expect(mock$).toHaveBeenLastCalledWith('.navbar-collapse');
+ expect(mockCollapse).toHaveBeenLastCalledWith('hide');
+ expect(openModal).toHaveBeenLastCalledWith('Settings');
+});
+
+test('Can open About', async () => {
+ const openModal = jest.fn();
+
+ // Test with one user owner and me
+ const { getByText } = render(
+ {}}
+ openModal={openModal}
+ iAmOwner={true}
+ translations={mockTranslations}
+ />,
+ );
+
+ fireEvent.click(getByText(mockTranslations.aboutButton));
+
+ expect(mock$).toHaveBeenLastCalledWith('.navbar-collapse');
+ expect(mockCollapse).toHaveBeenLastCalledWith('hide');
+ expect(openModal).toHaveBeenLastCalledWith('About');
+});
+
+test('Can open About', async () => {
+ window.open = jest.fn();
+
+ // Test with one user owner and me
+ const { getByText } = render(
+ {}}
+ openModal={() => {}}
+ iAmOwner={true}
+ translations={mockTranslations}
+ />,
+ );
+
+ fireEvent.click(getByText(mockTranslations.newRoomButton));
+
+ expect(mock$).toHaveBeenLastCalledWith('.navbar-collapse');
+ expect(mockCollapse).toHaveBeenLastCalledWith('hide');
+ expect(window.open).toHaveBeenLastCalledWith('/fakeid');
+});
diff --git a/client/src/components/Nav/__snapshots__/Nav.test.js.snap b/client/src/components/Nav/__snapshots__/Nav.test.js.snap
new file mode 100644
index 0000000..babce1f
--- /dev/null
+++ b/client/src/components/Nav/__snapshots__/Nav.test.js.snap
@@ -0,0 +1,531 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Nav component is displaying 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Nav component is displaying with another configuration and can rerender 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/client/src/components/Nav/index.js b/client/src/components/Nav/index.js
index c2c125b..aa474c4 100644
--- a/client/src/components/Nav/index.js
+++ b/client/src/components/Nav/index.js
@@ -1,63 +1,63 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import shortId from 'shortid'
-import { Info, Settings, PlusCircle, User, Users, Lock, Unlock, Star } from 'react-feather'
-import logoImg from 'img/logo.png'
-import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'
-import Username from 'components/Username'
-import Clipboard from 'clipboard'
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import shortId from 'shortid';
+import { Info, Settings, PlusCircle, User, Users, Lock, Unlock, Star } from 'react-feather';
+import logoImg from 'img/logo.png';
+import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
+import Username from 'components/Username';
+import Clipboard from 'clipboard';
import $ from 'jquery';
class Nav extends Component {
componentDidMount() {
- const clip = new Clipboard('.clipboard-trigger')
+ const clip = new Clipboard('.clipboard-trigger');
clip.on('success', () => {
- $('.room-id').tooltip('show')
+ $('.room-id').tooltip('show');
setTimeout(() => {
- $('.room-id').tooltip('hide')
- }, 3000)
- })
+ $('.room-id').tooltip('hide');
+ }, 3000);
+ });
$(() => {
$('.room-id').tooltip({
trigger: 'manual',
- })
+ });
$('.lock-room').tooltip({
trigger: 'manual',
- })
- })
+ });
+ });
}
componentDidUpdate() {
$(() => {
- $('.me-icon-wrap').tooltip()
- $('.owner-icon-wrap').tooltip()
- })
+ $('.me-icon-wrap').tooltip();
+ $('.owner-icon-wrap').tooltip();
+ });
}
newRoom() {
- $('.navbar-collapse').collapse('hide')
- window.open(`/${shortId.generate()}`)
+ $('.navbar-collapse').collapse('hide');
+ window.open(`/${shortId.generate()}`);
}
handleSettingsClick() {
- $('.navbar-collapse').collapse('hide')
- this.props.openModal('Settings')
+ $('.navbar-collapse').collapse('hide');
+ this.props.openModal('Settings');
}
handleAboutClick() {
- $('.navbar-collapse').collapse('hide')
- this.props.openModal('About')
+ $('.navbar-collapse').collapse('hide');
+ this.props.openModal('About');
}
handleToggleLock() {
if (!this.props.iAmOwner) {
- $('.lock-room').tooltip('show')
- setTimeout(() => $('.lock-room').tooltip('hide'), 3000)
- return
+ $('.lock-room').tooltip('show');
+ setTimeout(() => $('.lock-room').tooltip('hide'), 3000);
+ return;
}
- this.props.toggleLockRoom()
+ this.props.toggleLockRoom();
}
render() {
@@ -71,7 +71,8 @@ class Nav extends Component {
data-placement="bottom"
title={this.props.translations.copyButtonTooltip}
data-clipboard-text={`${window.location.origin}/${this.props.roomId}`}
- className="btn btn-plain btn-link clipboard-trigger room-id ellipsis">
+ className="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
+ >
{`/${this.props.roomId}`}
@@ -83,18 +84,14 @@ class Nav extends Component {
data-placement="bottom"
title="You must be the owner to lock or unlock the room"
>
- {this.props.roomLocked &&
-
- }
- {!this.props.roomLocked &&
-
- }
+ {this.props.roomLocked && }
+ {!this.props.roomLocked && }
-
+
{this.props.members.length}
@@ -105,16 +102,16 @@ class Nav extends Component {
- {member.id === this.props.userId &&
+ {member.id === this.props.userId && (
- }
- {member.isOwner &&
+ )}
+ {member.isOwner && (
- }
+ )}
))}
@@ -130,25 +127,34 @@ class Nav extends Component {
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
- aria-label="Toggle navigation">
+ aria-label="Toggle navigation"
+ >
- {this.props.translations.newRoomButton}
+
+ {this.props.translations.newRoomButton}
+
-
- {this.props.translations.settingsButton}
+
+
+ {this.props.translations.settingsButton}
+
- {this.props.translations.aboutButton}
+
+ {this.props.translations.aboutButton}
+
- )
+ );
}
}
@@ -161,6 +167,6 @@ Nav.propTypes = {
openModal: PropTypes.func.isRequired,
iAmOwner: PropTypes.bool.isRequired,
translations: PropTypes.object.isRequired,
-}
+};
-export default Nav
+export default Nav;
diff --git a/client/src/components/Notice/Notice.test.js b/client/src/components/Notice/Notice.test.js
index a844390..42c0bbb 100644
--- a/client/src/components/Notice/Notice.test.js
+++ b/client/src/components/Notice/Notice.test.js
@@ -1,14 +1,13 @@
-import React from 'react'
-import renderer from 'react-test-renderer'
-import Notice from './index.js'
-import { mount } from 'enzyme'
+import React from 'react';
+import { render } from '@testing-library/react';
+import Notice from '.';
-test.skip('Notice Component', () => {
- const component = mount(
-
+test('Notice component is displaying', async () => {
+ const { asFragment } = render(
+
Hello world
- )
+ );
- expect(component).toMatchSnapshot()
-})
+ expect(asFragment()).toMatchSnapshot();
+});
diff --git a/client/src/components/Notice/__snapshots__/Notice.test.js.snap b/client/src/components/Notice/__snapshots__/Notice.test.js.snap
index 878cbc9..a862493 100644
--- a/client/src/components/Notice/__snapshots__/Notice.test.js.snap
+++ b/client/src/components/Notice/__snapshots__/Notice.test.js.snap
@@ -1,15 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Notice Component 1`] = `
-
-
-
- Hello world
+exports[`Notice component is displaying 1`] = `
+
+
-
+
`;
diff --git a/client/src/components/RoomLink/RoomLink.test.js b/client/src/components/RoomLink/RoomLink.test.js
new file mode 100644
index 0000000..5d528d2
--- /dev/null
+++ b/client/src/components/RoomLink/RoomLink.test.js
@@ -0,0 +1,68 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import RoomLink from '.';
+import mock$ from 'jquery';
+
+const mockTooltip = jest.fn().mockImplementation(param => {
+ // console.log('tooltip', param);
+});
+
+jest.mock('jquery', () => {
+ return jest.fn().mockImplementation(param => {
+ // console.log('$', param);
+ if (typeof param === 'function') {
+ param();
+ }
+ return {
+ tooltip: mockTooltip,
+ };
+ });
+});
+
+jest.useFakeTimers();
+
+const mockTranslations = {
+ copyButtonTooltip: 'copyButton',
+};
+
+describe('RoomLink', () => {
+ afterEach(() => {
+ mock$.mockClear();
+ });
+
+ it('should display', async () => {
+ const { asFragment, unmount } = render(
);
+
+ expect(asFragment()).toMatchSnapshot();
+
+ expect(mock$).toHaveBeenLastCalledWith('.copy-room');
+ expect(mockTooltip).toHaveBeenLastCalledWith({ trigger: 'manual' });
+ mock$.mockClear();
+
+ unmount();
+
+ expect(mock$).toHaveBeenLastCalledWith('.copy-room');
+ expect(mockTooltip).toHaveBeenLastCalledWith('hide');
+ });
+
+ it('should copy link', async () => {
+ // Mock execCommand for paste
+ document.execCommand = jest.fn(() => true);
+
+ const { getByTitle } = render(
);
+
+ fireEvent.click(getByTitle(mockTranslations.copyButtonTooltip));
+
+ expect(document.execCommand).toHaveBeenLastCalledWith('copy');
+ expect(mock$).toHaveBeenCalledTimes(4);
+ expect(mock$).toHaveBeenLastCalledWith('.copy-room');
+ expect(mockTooltip).toHaveBeenLastCalledWith('show');
+
+ // Wait for tooltip to close
+ jest.runAllTimers();
+
+ expect(mock$).toHaveBeenCalledTimes(6);
+ expect(mock$).toHaveBeenLastCalledWith('.copy-room');
+ expect(mockTooltip).toHaveBeenLastCalledWith('hide');
+ });
+});
diff --git a/client/src/components/RoomLink/__snapshots__/RoomLink.test.js.snap b/client/src/components/RoomLink/__snapshots__/RoomLink.test.js.snap
new file mode 100644
index 0000000..cf33dd8
--- /dev/null
+++ b/client/src/components/RoomLink/__snapshots__/RoomLink.test.js.snap
@@ -0,0 +1,59 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RoomLink should display 1`] = `
+
+
+
+
+
+`;
diff --git a/client/src/components/RoomLink/index.js b/client/src/components/RoomLink/index.js
index 3b3c3af..3dcdb7a 100644
--- a/client/src/components/RoomLink/index.js
+++ b/client/src/components/RoomLink/index.js
@@ -1,50 +1,56 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import { Copy } from 'react-feather'
-import Clipboard from 'clipboard'
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Copy } from 'react-feather';
+import Clipboard from 'clipboard';
import $ from 'jquery';
class RoomLink extends Component {
constructor(props) {
- super(props)
+ super(props);
this.state = {
roomUrl: `${window.location.origin}/${props.roomId}`,
- }
+ };
}
componentDidMount() {
- const clip = new Clipboard('.copy-room')
+ const clip = new Clipboard('.copy-room');
clip.on('success', () => {
- $('.copy-room').tooltip('show')
+ $('.copy-room').tooltip('show');
setTimeout(() => {
- $('.copy-room').tooltip('hide')
- }, 3000)
- })
+ $('.copy-room').tooltip('hide');
+ }, 3000);
+ });
$(() => {
$('.copy-room').tooltip({
trigger: 'manual',
- })
- })
+ });
+ });
}
componentWillUnmount() {
- $('.copy-room').tooltip('hide')
+ if ($('.copy-room').tooltip) $('.copy-room').tooltip('hide');
}
render() {
return (
-
-
-
-
+
+
+
+
@@ -54,13 +60,13 @@ class RoomLink extends Component {
- )
+ );
}
}
RoomLink.propTypes = {
roomId: PropTypes.string.isRequired,
translations: PropTypes.object.isRequired,
-}
+};
-export default RoomLink
+export default RoomLink;
diff --git a/client/src/components/RoomLocked/RoomLocked.test.js b/client/src/components/RoomLocked/RoomLocked.test.js
new file mode 100644
index 0000000..269f93a
--- /dev/null
+++ b/client/src/components/RoomLocked/RoomLocked.test.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import RoomLocked from '.';
+
+test('RoomLocked component should display', () => {
+ const { asFragment } = render(
);
+ expect(asFragment()).toMatchSnapshot();
+});
diff --git a/client/src/components/RoomLocked/__snapshots__/RoomLocked.test.js.snap b/client/src/components/RoomLocked/__snapshots__/RoomLocked.test.js.snap
new file mode 100644
index 0000000..3f1e273
--- /dev/null
+++ b/client/src/components/RoomLocked/__snapshots__/RoomLocked.test.js.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RoomLocked component should display 1`] = `
+
+
+ test
+
+
+`;
diff --git a/client/src/components/Settings/Settings.test.js b/client/src/components/Settings/Settings.test.js
new file mode 100644
index 0000000..8bab556
--- /dev/null
+++ b/client/src/components/Settings/Settings.test.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+
+import Settings from '.';
+
+const mockTranslations = {
+ sound: 'soundCheck',
+};
+
+jest.mock('components/T', () => {
+ return jest.fn().mockImplementation(({ path }) => {
+ return mockTranslations[path] || 'default';
+ });
+});
+
+// To avoid missing provider
+jest.mock('components/T');
+jest.mock('components/RoomLink');
+
+describe('Settings component', () => {
+ it('should display', async () => {
+ const { asFragment } = render(
+
{}}
+ roomId="roomId"
+ setLanguage={() => {}}
+ translations={mockTranslations}
+ />,
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it('should toggle sound', async () => {
+ const toggleSound = jest.fn();
+ const { getAllByText } = render(
+ {}}
+ translations={mockTranslations}
+ />,
+ );
+
+ //console.log(getAllByText(mockTranslations.sound)[1]);
+ fireEvent.click(getAllByText(mockTranslations.sound)[1]);
+
+ expect(toggleSound).toHaveBeenCalledWith(false);
+ });
+
+ it('should change lang', async () => {
+ const changeLang = jest.fn();
+
+ const { getByDisplayValue } = render(
+ {}}
+ roomId="roomId"
+ setLanguage={changeLang}
+ translations={{}}
+ />,
+ );
+
+ fireEvent.change(getByDisplayValue('English'), { target: { value: 'de' } });
+
+ expect(changeLang).toHaveBeenCalledWith('de');
+ });
+});
diff --git a/client/src/components/Settings/__snapshots__/Settings.test.js.snap b/client/src/components/Settings/__snapshots__/Settings.test.js.snap
new file mode 100644
index 0000000..d0bb90a
--- /dev/null
+++ b/client/src/components/Settings/__snapshots__/Settings.test.js.snap
@@ -0,0 +1,157 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Settings component should display 1`] = `
+
+
+
+
+
+
+ default
+
+
+
+ default
+
+
+
+
+
+ English
+
+
+ Français
+
+
+ Occitan
+
+
+ Deutsch
+
+
+ Nederlands
+
+
+ Italiano
+
+
+ 中文
+
+
+
+
+
+
+ default
+
+
+ default
+
+
+
+
+ default
+
+
+ default
+
+
+
+
+ default
+
+
+ default
+
+
+
+ /nick [username]
+
+ default
+
+
+
+ /me [action]
+
+ default
+
+
+
+ /clear
+
+ default
+
+
+
+ /help
+
+ default
+
+
+
+
+
+
+`;
diff --git a/client/src/components/T/T.js b/client/src/components/T/T.js
new file mode 100644
index 0000000..80a70c2
--- /dev/null
+++ b/client/src/components/T/T.js
@@ -0,0 +1,32 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { getTranslations } from 'i18n';
+import _ from 'lodash';
+
+const regex = /{(.*?)}/g;
+
+class T extends Component {
+ render() {
+ const t = getTranslations(this.props.language);
+ const englishT = getTranslations('en');
+ const str =
+ _.get(t, this.props.path, '') || _.get(englishT, this.props.path, '');
+ let string = str.split(regex);
+ if (this.props.data) {
+ string = string.map((word) => {
+ if (this.props.data[word]) {
+ return this.props.data[word];
+ }
+ return word;
+ });
+ return {string} ;
+ }
+ return string;
+ }
+}
+
+T.propTypes = {
+ path: PropTypes.string.isRequired,
+};
+
+export default T;
diff --git a/client/src/components/T/T.test.js b/client/src/components/T/T.test.js
new file mode 100644
index 0000000..f204d64
--- /dev/null
+++ b/client/src/components/T/T.test.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+
+import T from './T';
+
+// To avoid missing provider
+jest.mock('components/T');
+
+test('T component is displaying', async () => {
+ const { asFragment, rerender } = render( );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ rerender( );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ rerender( );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ rerender( );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ rerender( );
+
+ expect(asFragment()).toMatchSnapshot();
+
+ rerender( );
+
+ expect(asFragment()).toMatchSnapshot();
+});
diff --git a/client/src/components/T/__snapshots__/T.test.js.snap b/client/src/components/T/__snapshots__/T.test.js.snap
new file mode 100644
index 0000000..9f66168
--- /dev/null
+++ b/client/src/components/T/__snapshots__/T.test.js.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`T component is displaying 1`] = `
+
+ Welcome to Darkwire v2.0
+
+`;
+
+exports[`T component is displaying 2`] = `
+
+ Bienvenue sur Darkwire v2.0
+
+`;
+
+exports[`T component is displaying 3`] = `
+
+ Welcome to Darkwire v2.0
+
+`;
+
+exports[`T component is displaying 4`] = ` `;
+
+exports[`T component is displaying 5`] = `
+
+
+ Alan joined
+
+
+`;
+
+exports[`T component is displaying 6`] = `
+
+ username joined
+
+`;
diff --git a/client/src/components/T/index.js b/client/src/components/T/index.js
index 9579543..8996ac9 100644
--- a/client/src/components/T/index.js
+++ b/client/src/components/T/index.js
@@ -1,36 +1,6 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import {getTranslations} from 'i18n';
-import _ from 'lodash';
-import { connect } from 'react-redux'
+import { connect } from 'react-redux';
+import T from 'components/T/T';
-const regex = /{(.*?)}/g;
-
-class T extends Component {
- render() {
- const t = getTranslations(this.props.language);
- const englishT = getTranslations('en');
- const str = _.get(t, this.props.path, '') || _.get(englishT, this.props.path, '')
- let string = str.split(regex);
- if (this.props.data) {
- string = string.map(word => {
- if (this.props.data[word]) {
- return this.props.data[word];
- }
- return word;
- });
- return {string}
- }
- return string;
- }
-}
-
-T.propTypes = {
- path: PropTypes.string.isRequired,
-}
-
-export default connect(
- (state, ownProps) => ({
- language: state.app.language,
- }),
-)(T)
\ No newline at end of file
+export default connect((state, ownProps) => ({
+ language: state.app.language,
+}))(T);
diff --git a/client/src/components/Username/Username.test.js b/client/src/components/Username/Username.test.js
new file mode 100644
index 0000000..5105c0e
--- /dev/null
+++ b/client/src/components/Username/Username.test.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+
+import Username from '.';
+
+test('Username component is displaying', async () => {
+ const { asFragment } = render( );
+
+ expect(asFragment()).toMatchSnapshot();
+});
diff --git a/client/src/components/Username/__snapshots__/Username.test.js.snap b/client/src/components/Username/__snapshots__/Username.test.js.snap
new file mode 100644
index 0000000..c924a18
--- /dev/null
+++ b/client/src/components/Username/__snapshots__/Username.test.js.snap
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Username component is displaying 1`] = `
+
+
+ paul
+
+
+`;
diff --git a/client/src/components/Welcome/Welcome.test.js b/client/src/components/Welcome/Welcome.test.js
new file mode 100644
index 0000000..34fd80c
--- /dev/null
+++ b/client/src/components/Welcome/Welcome.test.js
@@ -0,0 +1,12 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+
+import Welcome from '.';
+
+test('Welcome component is displaying', async () => {
+ const { asFragment } = render(
+ {}} translations={{}} />
+ );
+
+ expect(asFragment()).toMatchSnapshot();
+});
diff --git a/client/src/components/Welcome/__snapshots__/Welcome.test.js.snap b/client/src/components/Welcome/__snapshots__/Welcome.test.js.snap
new file mode 100644
index 0000000..878b5bb
--- /dev/null
+++ b/client/src/components/Welcome/__snapshots__/Welcome.test.js.snap
@@ -0,0 +1,106 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Welcome component is displaying 1`] = `
+
+
+
+ v2.0 is a complete rewrite and includes several new features. Here are some highlights:
+
+
+ Support on all modern browsers (Chrome, Firefox, Safari, Safari iOS, Android)
+
+
+ Slash commands (/nick, /me, /clear)
+
+
+ Room owners can lock the room, preventing anyone else from joining
+
+
+ Front-end rewritten in React.js and Redux
+
+
+ Send files up to 4 MB
+
+
+
+
+
+
+ Others can join this room using the following URL:
+
+
+
+
+
+
+
+`;
diff --git a/client/src/config/env.js b/client/src/config/env.js
index dd7d4d2..1341d29 100644
--- a/client/src/config/env.js
+++ b/client/src/config/env.js
@@ -1 +1,2 @@
+/* istanbul ignore file */
export default process.env.NODE_ENV
diff --git a/client/src/i18n/i18n.test.js b/client/src/i18n/i18n.test.js
index f2b7318..1ad61fe 100644
--- a/client/src/i18n/i18n.test.js
+++ b/client/src/i18n/i18n.test.js
@@ -1,9 +1,10 @@
-import { getTranslations } from './index.js'
+import { getTranslations } from './index.js';
test('Get translation', () => {
- expect(getTranslations('en').welcomeHeader).toBe("Welcome to Darkwire v2.0");
- expect(getTranslations('fr').welcomeHeader).toBe("Bienvenue sur Darkwire v2.0");
- expect(getTranslations('zh-CN').welcomeHeader).toBe("欢迎来到Darkwire v2.0");
- expect(getTranslations('en-US').welcomeHeader).toBe("Welcome to Darkwire v2.0");
- expect(getTranslations('ru-CH').welcomeHeader).toBe("Welcome to Darkwire v2.0");
-})
+ expect(getTranslations('en').welcomeHeader).toBe('Welcome to Darkwire v2.0');
+ expect(getTranslations().welcomeHeader).toBe('Welcome to Darkwire v2.0');
+ expect(getTranslations('fr').welcomeHeader).toBe('Bienvenue sur Darkwire v2.0');
+ expect(getTranslations('zh-CN').welcomeHeader).toBe('欢迎来到Darkwire v2.0');
+ expect(getTranslations('en-US').welcomeHeader).toBe('Welcome to Darkwire v2.0');
+ expect(getTranslations('ru-CH').welcomeHeader).toBe('Welcome to Darkwire v2.0');
+});
diff --git a/client/src/index.js b/client/src/index.js
index f580875..92b0034 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
diff --git a/client/src/index.test.js b/client/src/index.test.js
new file mode 100644
index 0000000..3ee3ce7
--- /dev/null
+++ b/client/src/index.test.js
@@ -0,0 +1,5 @@
+describe('Timezones', () => {
+ it('should always be UTC', () => {
+ expect(new Date().getTimezoneOffset()).toBe(0);
+ });
+});
diff --git a/client/src/reducers/activities.test.js b/client/src/reducers/activities.test.js
new file mode 100644
index 0000000..af8a94d
--- /dev/null
+++ b/client/src/reducers/activities.test.js
@@ -0,0 +1,183 @@
+import reducer from './activities';
+
+describe('Activities reducer', () => {
+ it('should return the initial state', () => {
+ expect(reducer(undefined, {})).toEqual({
+ items: [],
+ });
+ });
+
+ it('should handle CLEAR_ACTVITIES', () => {
+ expect(reducer({ items: [{}, {}] }, { type: 'CLEAR_ACTIVITIES' })).toEqual({
+ items: [],
+ });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_SLASH_COMMAND', () => {
+ expect(
+ reducer({ items: [] }, { type: 'SEND_ENCRYPTED_MESSAGE_SLASH_COMMAND', payload: { payload: 'content' } }),
+ ).toEqual({ items: [{ payload: 'content', type: 'SLASH_COMMAND' }] });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_FILE_TRANSFER', () => {
+ expect(
+ reducer({ items: [] }, { type: 'SEND_ENCRYPTED_MESSAGE_FILE_TRANSFER', payload: { payload: 'content' } }),
+ ).toEqual({ items: [{ payload: 'content', type: 'FILE' }] });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_TEXT_MESSAGE', () => {
+ expect(
+ reducer({ items: [] }, { type: 'SEND_ENCRYPTED_MESSAGE_TEXT_MESSAGE', payload: { payload: 'content' } }),
+ ).toEqual({ items: [{ payload: 'content', type: 'TEXT_MESSAGE' }] });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE', () => {
+ expect(
+ reducer(
+ { items: [] },
+ { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE', payload: { payload: { payload: 'content' } } },
+ ),
+ ).toEqual({ items: [{ payload: 'content', type: 'TEXT_MESSAGE' }] });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_SEND_FILE', () => {
+ expect(
+ reducer({ items: [] }, { type: 'SEND_ENCRYPTED_MESSAGE_SEND_FILE', payload: { payload: 'content' } }),
+ ).toEqual({ items: [{ payload: 'content', type: 'SEND_FILE' }] });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_SEND_FILE', () => {
+ expect(
+ reducer(
+ { items: [] },
+ { type: 'RECEIVE_ENCRYPTED_MESSAGE_SEND_FILE', payload: { payload: { message: 'content' } } },
+ ),
+ ).toEqual({ items: [{ message: 'content', type: 'RECEIVE_FILE' }] });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_ADD_USER', () => {
+ const payload1 = {
+ payload: { content: 'content', id: 'idalan', username: 'alan' },
+ state: {
+ room: {
+ members: [{ id: 'iddan' }],
+ },
+ },
+ };
+ expect(reducer({ items: [] }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER', payload: payload1 })).toEqual({
+ items: [{ type: 'USER_ENTER', userId: 'idalan', username: 'alan' }],
+ });
+
+ // Test already reveived user
+ const payload2 = {
+ payload: { content: 'content', id: 'idalan', username: 'alan' },
+ state: {
+ room: {
+ members: [{ id: 'idalan' }],
+ },
+ },
+ };
+ expect(reducer({ items: [] }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER', payload: payload2 })).toEqual({
+ items: [],
+ });
+ });
+
+ it('should handle USER_EXIT', () => {
+ expect(reducer({ items: [] }, { type: 'USER_EXIT', payload: { id: 'idalan', username: 'alan' } })).toEqual({
+ items: [{ type: 'USER_EXIT', userId: 'idalan', username: 'alan' }],
+ });
+ // Without id
+ expect(reducer({ items: [] }, { type: 'USER_EXIT', payload: { username: 'alan' } })).toEqual({
+ items: [],
+ });
+ });
+
+ it('should handle TOGGLE_LOCK_ROOM', () => {
+ expect(
+ reducer(
+ { items: [] },
+ { type: 'TOGGLE_LOCK_ROOM', payload: { id: 'idalan', username: 'alan', locked: true, sender: 'alan' } },
+ ),
+ ).toEqual({
+ items: [{ locked: true, sender: 'alan', type: 'TOGGLE_LOCK_ROOM', userId: 'idalan', username: 'alan' }],
+ });
+ });
+
+ it('should handle RECEIVE_TOGGLE_LOCK_ROOM', () => {
+ expect(
+ reducer(
+ { items: [] },
+ { type: 'RECEIVE_TOGGLE_LOCK_ROOM', payload: { id: 'idalan', username: 'alan', locked: true, sender: 'alan' } },
+ ),
+ ).toEqual({
+ items: [{ locked: true, sender: 'alan', type: 'TOGGLE_LOCK_ROOM', userId: 'idalan', username: 'alan' }],
+ });
+ });
+
+ it('should handle SHOW_NOTICE', () => {
+ expect(reducer({ items: [] }, { type: 'SHOW_NOTICE', payload: { message: 'Hello wordld!' } })).toEqual({
+ items: [{ message: 'Hello wordld!', type: 'NOTICE' }],
+ });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
+ const payload1 = { currentUsername: 'alan', newUsername: 'dan', sender: 'alan' };
+ expect(
+ reducer(
+ {
+ items: [
+ { sender: 'alan', username: 'alan' },
+ { sender: 'alice', username: 'alice' },
+ ],
+ },
+ { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload: payload1 },
+ ),
+ ).toEqual({
+ items: [
+ { sender: 'alan', username: 'dan' },
+ { sender: 'alice', username: 'alice' },
+ { currentUsername: 'alan', newUsername: 'dan', type: 'CHANGE_USERNAME' },
+ ],
+ });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
+ const payload1 = { payload: { currentUsername: 'alan', newUsername: 'dan', sender: 'alan' } };
+ expect(
+ reducer(
+ {
+ items: [
+ { sender: 'alan', username: 'alan', type: 'USER_ACTION' },
+ { sender: 'alan', username: 'alan', type: 'TEXT_MESSAGE' },
+ { sender: 'alan', username: 'alan', type: 'ANOTHER_TYPE' },
+ { sender: 'alice', username: 'alice' },
+ ],
+ },
+ { type: 'RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload: payload1 },
+ ),
+ ).toEqual({
+ items: [
+ { sender: 'alan', type: 'USER_ACTION', username: 'dan' },
+ { sender: 'alan', type: 'TEXT_MESSAGE', username: 'dan' },
+ { sender: 'alan', type: 'ANOTHER_TYPE', username: 'alan' },
+ { sender: 'alice', username: 'alice' },
+ { currentUsername: 'alan', newUsername: 'dan', type: 'CHANGE_USERNAME' },
+ ],
+ });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_USER_ACTION', () => {
+ expect(
+ reducer({ items: [] }, { type: 'SEND_ENCRYPTED_MESSAGE_USER_ACTION', payload: { message: 'Hello wordld!' } }),
+ ).toEqual({ items: [{ message: 'Hello wordld!', type: 'USER_ACTION' }] });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_USER_ACTION', () => {
+ expect(
+ reducer(
+ { items: [] },
+ { type: 'RECEIVE_ENCRYPTED_MESSAGE_USER_ACTION', payload: { payload: { message: 'Hello wordld!' } } },
+ ),
+ ).toEqual({ items: [{ message: 'Hello wordld!', type: 'USER_ACTION' }] });
+ });
+});
diff --git a/client/src/reducers/app.test.js b/client/src/reducers/app.test.js
new file mode 100644
index 0000000..3177f90
--- /dev/null
+++ b/client/src/reducers/app.test.js
@@ -0,0 +1,72 @@
+import reducer from './app';
+import { getTranslations } from 'i18n';
+
+jest.mock('i18n', () => {
+ return {
+ getTranslations: jest.fn().mockReturnValue({ path: 'test' }),
+ };
+});
+
+describe('App reducer', () => {
+ it('should return the initial state', () => {
+ expect(reducer(undefined, {})).toEqual({
+ language: 'en-US',
+ modalComponent: null,
+ scrolledToBottom: true,
+ socketConnected: false,
+ soundIsEnabled: true,
+ translations: { path: 'test' },
+ unreadMessageCount: 0,
+ windowIsFocused: true,
+ });
+ });
+
+ it('should handle OPEN_MODAL', () => {
+ expect(reducer({}, { type: 'OPEN_MODAL', payload: 'test' })).toEqual({ modalComponent: 'test' });
+ });
+
+ it('should handle CLOSE_MODAL', () => {
+ expect(reducer({}, { type: 'CLOSE_MODAL' })).toEqual({ modalComponent: null });
+ });
+
+ it('should handle SET_SCROLLED_TO_BOTTOM', () => {
+ expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: true })).toEqual({ scrolledToBottom: true });
+ expect(reducer({}, { type: 'SET_SCROLLED_TO_BOTTOM', payload: false })).toEqual({ scrolledToBottom: false });
+ });
+
+ it('should handle TOGGLE_WINDOW_FOCUS', () => {
+ expect(reducer({ unreadMessageCount: 10 }, { type: 'TOGGLE_WINDOW_FOCUS', payload: true })).toEqual({
+ windowIsFocused: true,
+ unreadMessageCount: 0,
+ });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE', () => {
+ expect(
+ reducer({ unreadMessageCount: 10, windowIsFocused: false }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
+ ).toEqual({ unreadMessageCount: 11, windowIsFocused: false });
+ expect(
+ reducer({ unreadMessageCount: 10, windowIsFocused: true }, { type: 'RECEIVE_ENCRYPTED_MESSAGE_TEXT_MESSAGE' }),
+ ).toEqual({ unreadMessageCount: 0, windowIsFocused: true });
+ });
+
+ it('should handle TOGGLE_SOUND_ENABLED', () => {
+ expect(reducer({}, { type: 'TOGGLE_SOUND_ENABLED', payload: true })).toEqual({
+ soundIsEnabled: true,
+ });
+ });
+
+ it('should handle TOGGLE_SOCKET_CONNECTED', () => {
+ expect(reducer({}, { type: 'TOGGLE_SOCKET_CONNECTED', payload: true })).toEqual({
+ socketConnected: true,
+ });
+ });
+
+ it('should handle CHANGE_LANGUAGE', () => {
+ getTranslations.mockReturnValueOnce({ path: 'new lang' });
+ expect(reducer({}, { type: 'CHANGE_LANGUAGE', payload: 'fr' })).toEqual({
+ language: 'fr',
+ translations: { path: 'new lang' },
+ });
+ });
+});
diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js
index c88c06f..808aa47 100644
--- a/client/src/reducers/index.js
+++ b/client/src/reducers/index.js
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
import { combineReducers } from 'redux'
import app from './app'
import activities from './activities'
diff --git a/client/src/reducers/room.test.js b/client/src/reducers/room.test.js
new file mode 100644
index 0000000..6d32d8e
--- /dev/null
+++ b/client/src/reducers/room.test.js
@@ -0,0 +1,154 @@
+import reducer from './room';
+
+describe('Room reducer', () => {
+ it('should return the initial state', () => {
+ expect(reducer(undefined, {})).toEqual({ id: '', isLocked: false, members: [] });
+ });
+
+ it('should handle USER_EXIT', () => {
+ const state = {
+ members: [
+ { publicKey: { n: 'dankey' }, id: 'dankey', username: 'dan', isOwner: true },
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan', isOwner: false },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'alice', isOwner: false },
+ ],
+ };
+ const payload = {
+ members: [
+ { publicKey: { n: 'alankey' }, isOwner: true },
+ { publicKey: { n: 'alicekey' }, isOwner: false },
+ ],
+ };
+ expect(reducer(state, { type: 'USER_EXIT', payload: payload })).toEqual({
+ members: [
+ { id: 'alankey', isOwner: true, publicKey: { n: 'alankey' }, username: 'alan' },
+ { id: 'alicekey', isOwner: false, publicKey: { n: 'alicekey' }, username: 'alice' },
+ ],
+ });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_ADD_USER', () => {
+ const state = {
+ members: [
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan', isOwner: true },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'alice', isOwner: false },
+ { publicKey: { n: 'dankey' }, id: 'dankey', username: 'dan', isOwner: false },
+ ],
+ };
+ const payload = {
+ payload: {
+ username: 'dany',
+ isOwner: true,
+ publicKey: { n: 'dankey' },
+ },
+ };
+ expect(reducer(state, { type: 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER', payload: payload })).toEqual({
+ members: [
+ { id: 'alankey', isOwner: true, publicKey: { n: 'alankey' }, username: 'alan' },
+ { id: 'alicekey', isOwner: false, publicKey: { n: 'alicekey' }, username: 'alice' },
+ { id: 'dankey', isOwner: true, publicKey: { n: 'dankey' }, username: 'dany' },
+ ],
+ });
+ });
+
+ it('should handle CREATE_USER', () => {
+ const state = {
+ members: [
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan', isOwner: true },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'alice', isOwner: false },
+ ],
+ };
+ const payload = {
+ username: 'dan',
+ publicKey: { n: 'danKey' },
+ };
+ expect(reducer(state, { type: 'CREATE_USER', payload: payload })).toEqual({
+ members: [
+ { id: 'alankey', isOwner: true, publicKey: { n: 'alankey' }, username: 'alan' },
+ { id: 'alicekey', isOwner: false, publicKey: { n: 'alicekey' }, username: 'alice' },
+ { id: 'danKey', publicKey: { n: 'danKey' }, username: 'dan' },
+ ],
+ });
+ });
+
+ it('should handle USER_ENTER', () => {
+ const state = {
+ members: [
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan', isOwner: true },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'alice', isOwner: false },
+ ],
+ };
+ const payload = {
+ users: [
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan', isOwner: true },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'alice', isOwner: false },
+ { publicKey: { n: 'dankey' }, id: 'dankey', username: 'dan', isOwner: false },
+ ],
+ isLocked: false,
+ id: 'test',
+ };
+ expect(reducer(state, { type: 'USER_ENTER', payload: payload })).toEqual({
+ id: 'test',
+ isLocked: false,
+ members: [
+ { id: 'alankey', isOwner: true, publicKey: { n: 'alankey' }, username: 'alan' },
+ { id: 'alicekey', isOwner: false, publicKey: { n: 'alicekey' }, username: 'alice' },
+ { id: 'dankey', isOwner: false, publicKey: { n: 'dankey' } },
+ ],
+ });
+ });
+
+ it('should handle TOGGLE_LOCK_ROOM', () => {
+ expect(reducer({ isLocked: true }, { type: 'TOGGLE_LOCK_ROOM' })).toEqual({ isLocked: false });
+ });
+
+ it('should handle RECEIVE_TOGGLE_LOCK_ROOM', () => {
+ expect(reducer({ isLocked: true }, { type: 'RECEIVE_TOGGLE_LOCK_ROOM', payload: { locked: false } })).toEqual({
+ isLocked: false,
+ });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
+ const state = {
+ members: [
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan', isOwner: true },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'alice', isOwner: false },
+ { publicKey: { n: 'dankey' }, id: 'dankey', username: 'dan', isOwner: false },
+ ],
+ };
+ const payload = {
+ newUsername: 'alicette',
+ id: 'alicekey',
+ };
+ expect(reducer(state, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload: payload })).toEqual({
+ members: [
+ { id: 'alankey', isOwner: true, publicKey: { n: 'alankey' }, username: 'alan' },
+ { id: 'alicekey', isOwner: false, publicKey: { n: 'alicekey' }, username: 'alicette' },
+ { id: 'dankey', isOwner: false, publicKey: { n: 'dankey' }, username: 'dan' },
+ ],
+ });
+ });
+
+ it('should handle RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
+ const state = {
+ members: [
+ { publicKey: { n: 'alankey' }, id: 'alankey', username: 'alan', isOwner: true },
+ { publicKey: { n: 'alicekey' }, id: 'alicekey', username: 'alice', isOwner: false },
+ { publicKey: { n: 'dankey' }, id: 'dankey', username: 'dan', isOwner: false },
+ ],
+ };
+ const payload = {
+ payload: {
+ newUsername: 'alicette',
+ id: 'alicekey',
+ },
+ };
+ expect(reducer(state, { type: 'RECEIVE_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload: payload })).toEqual({
+ members: [
+ { id: 'alankey', isOwner: true, publicKey: { n: 'alankey' }, username: 'alan' },
+ { id: 'alicekey', isOwner: false, publicKey: { n: 'alicekey' }, username: 'alicette' },
+ { id: 'dankey', isOwner: false, publicKey: { n: 'dankey' }, username: 'dan' },
+ ],
+ });
+ });
+});
diff --git a/client/src/reducers/user.test.js b/client/src/reducers/user.test.js
new file mode 100644
index 0000000..833439d
--- /dev/null
+++ b/client/src/reducers/user.test.js
@@ -0,0 +1,29 @@
+import reducer from './user';
+
+jest.mock('i18n', () => {
+ return {
+ getTranslations: jest.fn().mockReturnValue({ path: 'test' }),
+ };
+});
+
+describe('User reducer', () => {
+ it('should return the initial state', () => {
+ expect(reducer(undefined, {})).toEqual({ id: '', privateKey: {}, publicKey: {}, username: '' });
+ });
+
+ it('should handle CREATE_USER', () => {
+ const payload = { publicKey: { n: 'alicekey' }, username: 'alice' };
+ expect(reducer({}, { type: 'CREATE_USER', payload })).toEqual({
+ id: 'alicekey',
+ publicKey: { n: 'alicekey' },
+ username: 'alice',
+ });
+ });
+
+ it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
+ const payload = { newUsername: 'alice' };
+ expect(reducer({ username: 'polux' }, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload })).toEqual({
+ username: 'alice',
+ });
+ });
+});
diff --git a/client/src/serviceWorker.js b/client/src/serviceWorker.js
index f8c7e50..e0984dd 100644
--- a/client/src/serviceWorker.js
+++ b/client/src/serviceWorker.js
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
// This optional code is used to register a service worker.
// register() is not called by default.
diff --git a/client/src/setupTests.js b/client/src/setupTests.js
index 7a1fee7..e7d2497 100644
--- a/client/src/setupTests.js
+++ b/client/src/setupTests.js
@@ -1,4 +1,8 @@
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
+// this adds jest-dom's custom assertions
+import '@testing-library/jest-dom/extend-expect';
+import { enableFetchMocks } from 'jest-fetch-mock';
-configure({ adapter: new Adapter() });
\ No newline at end of file
+configure({ adapter: new Adapter() });
+enableFetchMocks();
diff --git a/client/src/store/index.js b/client/src/store/index.js
index 44ecc36..7198719 100644
--- a/client/src/store/index.js
+++ b/client/src/store/index.js
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
import { createStore, applyMiddleware, compose } from 'redux'
import reducers from 'reducers'
import thunk from 'redux-thunk'
diff --git a/client/src/stylesheets/postcss.config.js b/client/src/stylesheets/postcss.config.js
index ba493cd..9981c52 100644
--- a/client/src/stylesheets/postcss.config.js
+++ b/client/src/stylesheets/postcss.config.js
@@ -1,3 +1,5 @@
+/* istanbul ignore file */
+
module.exports = {
plugins: [
require('autoprefixer')({}), // eslint-disable-line
diff --git a/client/src/test/setup.js b/client/src/test/setup.js
index 88c8c3b..c4d2dea 100644
--- a/client/src/test/setup.js
+++ b/client/src/test/setup.js
@@ -1,4 +1,5 @@
-import Enzyme from 'enzyme'
-import Adapter from 'enzyme-adapter-react-16'
+/* istanbul ignore file */
+import Enzyme from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
-Enzyme.configure({ adapter: new Adapter() })
+Enzyme.configure({ adapter: new Adapter() });
diff --git a/client/yarn.lock b/client/yarn.lock
index df92a51..c4a299a 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -775,6 +775,14 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-typescript" "^7.3.2"
+"@babel/runtime-corejs3@^7.7.4":
+ version "7.9.6"
+ resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.9.6.tgz#67aded13fffbbc2cb93247388cf84d77a4be9a71"
+ integrity sha512-6toWAfaALQjt3KMZQc6fABqZwUDDuWzz+cAfPhqyEnzxvdWOAkjwPNxgF8xlmo7OWLsSjaKjsskpKHRLaMArOA==
+ dependencies:
+ core-js-pure "^3.0.0"
+ regenerator-runtime "^0.13.4"
+
"@babel/runtime@7.4.3", "@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4":
version "7.4.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.3.tgz#79888e452034223ad9609187a0ad1fe0d2ad4bdc"
@@ -789,6 +797,13 @@
dependencies:
regenerator-runtime "^0.13.2"
+"@babel/runtime@^7.7.4", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6":
+ version "7.9.6"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
+ integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.0":
version "7.4.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b"
@@ -925,6 +940,17 @@
jest-message-util "^24.7.1"
jest-mock "^24.7.0"
+"@jest/fake-timers@^25.1.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.5.0.tgz#46352e00533c024c90c2bc2ad9f2959f7f114185"
+ integrity sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ jest-message-util "^25.5.0"
+ jest-mock "^25.5.0"
+ jest-util "^25.5.0"
+ lolex "^5.0.0"
+
"@jest/reporters@^24.7.1":
version "24.7.1"
resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.7.1.tgz#38ac0b096cd691bbbe3051ddc25988d42e37773a"
@@ -1008,6 +1034,26 @@
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/yargs" "^12.0.9"
+"@jest/types@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
+ integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^1.1.1"
+ "@types/yargs" "^15.0.0"
+ chalk "^3.0.0"
+
+"@jest/types@^26.0.1":
+ version "26.0.1"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.0.1.tgz#b78333fbd113fa7aec8d39de24f88de8686dac67"
+ integrity sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^1.1.1"
+ "@types/yargs" "^15.0.0"
+ chalk "^4.0.0"
+
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -1021,6 +1067,41 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@peculiar/asn1-schema@^2.0.1", "@peculiar/asn1-schema@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.3.tgz#c6c097e842ebb8a07d198b68cd49f2cf9f3571b5"
+ integrity sha512-STqC+Tfx2dTiIGRmokjsKOeqsfhoV6WaBwFr7BVicSfHLAVSPrZXiugyD8AELrjQdJ9INWpL3N7YSJyU5a1ZwA==
+ dependencies:
+ "@types/asn1js" "^0.0.1"
+ asn1js "^2.0.26"
+ pvtsutils "^1.0.10"
+ tslib "^1.11.1"
+
+"@peculiar/json-schema@^1.1.10":
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.10.tgz#d772b4323c9a4b5352b5ad52dc821a07b0db4877"
+ integrity sha512-kbpnG9CkF1y6wwGkW7YtSA+yYK4X5uk4rAwsd1hxiaYE3Hkw2EsGlbGh/COkMLyFf+Fe830BoFiMSB3QnC/ItA==
+ dependencies:
+ tslib "^1.11.1"
+
+"@peculiar/webcrypto@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.1.1.tgz#4c7498e4861878e299ef058bce1208a4d063d0ff"
+ integrity sha512-Bu2XgOvzirnLcojZYs4KQ8hOLf2ETpa0NL6btQt5NgsAwctI6yVkzgYP+EcG7Mm579RBP+V0LM5rXyMlTVx23A==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.0.3"
+ "@peculiar/json-schema" "^1.1.10"
+ pvtsutils "^1.0.10"
+ tslib "^1.11.2"
+ webcrypto-core "^1.1.0"
+
+"@sinonjs/commons@^1.7.0":
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2"
+ integrity sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==
+ dependencies:
+ type-detect "4.0.8"
+
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1"
@@ -1126,6 +1207,48 @@
"@svgr/plugin-svgo" "^4.0.3"
loader-utils "^1.1.0"
+"@testing-library/dom@^7.2.2":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.3.0.tgz#f26edc23023414d25ed5b187d2db240f627f65de"
+ integrity sha512-F/gFzOGsX1ulldWMREiDkoBg4/kfKXhXi04iW9irwwwF/BLW9dSMLyK0zqnBjCTx76A8B584EuYBhNgWNOnUfA==
+ dependencies:
+ "@babel/runtime" "^7.9.6"
+ "@types/testing-library__dom" "^7.0.2"
+ aria-query "^4.0.2"
+ dom-accessibility-api "^0.4.3"
+ pretty-format "^26.0.1"
+
+"@testing-library/jest-dom@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.5.0.tgz#4707023e8f572021e8a84f65721303ff60828d88"
+ integrity sha512-7sWHrpxG4Yd8TmryI7Rtbx8Ff4mbs3ASye3oshQIuHvsCR+QHgr7rTR/PfeXvOmwUwR36wSTTAvrLKsPmr6VEQ==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+ "@types/testing-library__jest-dom" "^5.0.2"
+ chalk "^3.0.0"
+ css "^2.2.4"
+ css.escape "^1.5.1"
+ jest-diff "^25.1.0"
+ jest-matcher-utils "^25.1.0"
+ lodash "^4.17.15"
+ redent "^3.0.0"
+
+"@testing-library/react@^10.0.4":
+ version "10.0.4"
+ resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.4.tgz#8e0e299cd91acc626d81ed8489fdc13df864c31d"
+ integrity sha512-2e1B5debfuiIGbvUuiSXybskuh7ZTVJDDvG/IxlzLOY9Co/mKFj9hIklAe2nGZYcOUxFaiqWrRZ9vCVGzJfRlQ==
+ dependencies:
+ "@babel/runtime" "^7.9.6"
+ "@testing-library/dom" "^7.2.2"
+ "@types/testing-library__react" "^10.0.1"
+
+"@types/asn1js@^0.0.1":
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/@types/asn1js/-/asn1js-0.0.1.tgz#ef8b9f9708cb1632a1c3a9cd27717caabe793bc2"
+ integrity sha1-74uflwjLFjKhw6nNJ3F8qr55O8I=
+ dependencies:
+ "@types/pvutils" "*"
+
"@types/babel__core@^7.1.0":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.1.tgz#ce9a9e5d92b7031421e1d0d74ae59f572ba48be6"
@@ -1159,26 +1282,107 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/color-name@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
+ integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
+
+"@types/istanbul-lib-coverage@*":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
+ integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
+
"@types/istanbul-lib-coverage@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz#1eb8c033e98cf4e1a4cedcaf8bcafe8cb7591e85"
integrity sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==
+"@types/istanbul-lib-report@*":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
+ integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a"
+ integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+ "@types/istanbul-lib-report" "*"
+
+"@types/jest@*":
+ version "25.2.1"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.1.tgz#9544cd438607955381c1bdbdb97767a249297db5"
+ integrity sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA==
+ dependencies:
+ jest-diff "^25.2.1"
+ pretty-format "^25.2.1"
+
"@types/node@*":
version "11.13.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.6.tgz#37ec75690830acb0d74ce3c6c43caab787081e85"
integrity sha512-Xoo/EBzEe8HxTSwaZNLZjaW6M6tA/+GmD3/DZ6uo8qSaolE/9Oarko0oV1fVfrLqOz0tx0nXJB4rdD5c+vixLw==
+"@types/prop-types@*":
+ version "15.7.3"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+ integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
+
+"@types/pvutils@*":
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/@types/pvutils/-/pvutils-0.0.2.tgz#e21684962cfa58ac920fd576d90556032dc86009"
+ integrity sha512-CgQAm7pjyeF3Gnv78ty4RBVIfluB+Td+2DR8iPaU0prF18pkzptHHP+DoKPfpsJYknKsVZyVsJEu5AuGgAqQ5w==
+
"@types/q@^1.5.1":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
+"@types/react-dom@*":
+ version "16.9.7"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.7.tgz#60844d48ce252d7b2dccf0c7bb937130e27c0cd2"
+ integrity sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*":
+ version "16.9.34"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349"
+ integrity sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^2.2.0"
+
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+"@types/testing-library__dom@*", "@types/testing-library__dom@^7.0.2":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.0.2.tgz#2906f8a0dce58b0746c6ab606f786bd06fe6940e"
+ integrity sha512-8yu1gSwUEAwzg2OlPNbGq+ixhmSviGurBu1+ivxRKq1eRcwdjkmlwtPvr9VhuxTq2fNHBWN2po6Iem3Xt5A6rg==
+ dependencies:
+ pretty-format "^25.1.0"
+
+"@types/testing-library__jest-dom@^5.0.2":
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.0.3.tgz#8efef348eeedc62e7de21acbe455a779936417c4"
+ integrity sha512-NdbKc6yseg6uq4UJFwimPws0iwsGugVbPoOTP2EH+PJMJKiZsoSg5F2H3XYweOyytftCOuIMuXifBUrF9CSvaQ==
+ dependencies:
+ "@types/jest" "*"
+
+"@types/testing-library__react@^10.0.1":
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-10.0.1.tgz#92bb4a02394bf44428e35f1da2970ed77f803593"
+ integrity sha512-RbDwmActAckbujLZeVO/daSfdL1pnjVqas25UueOkAY5r7vriavWf0Zqg7ghXMHa8ycD/kLkv8QOj31LmSYwww==
+ dependencies:
+ "@types/react-dom" "*"
+ "@types/testing-library__dom" "*"
+ pretty-format "^25.1.0"
+
"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
@@ -1201,11 +1405,23 @@
"@types/unist" "*"
"@types/vfile-message" "*"
+"@types/yargs-parser@*":
+ version "15.0.0"
+ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
+ integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
+
"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
version "12.0.12"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
+"@types/yargs@^15.0.0":
+ version "15.0.4"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299"
+ integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==
+ dependencies:
+ "@types/yargs-parser" "*"
+
"@typescript-eslint/eslint-plugin@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.6.0.tgz#a5ff3128c692393fb16efa403ec7c8a5593dab0f"
@@ -1394,6 +1610,11 @@ abab@^2.0.0:
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
integrity sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==
+abab@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a"
+ integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==
+
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -1420,6 +1641,14 @@ acorn-globals@^4.1.0, acorn-globals@^4.3.0:
acorn "^6.0.1"
acorn-walk "^6.0.1"
+acorn-globals@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
+ integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==
+ dependencies:
+ acorn "^7.1.1"
+ acorn-walk "^7.1.1"
+
acorn-jsx@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
@@ -1430,6 +1659,11 @@ acorn-walk@^6.0.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==
+acorn-walk@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e"
+ integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==
+
acorn@^5.5.3:
version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
@@ -1440,6 +1674,11 @@ acorn@^6.0.1, acorn@^6.0.4, acorn@^6.0.5, acorn@^6.0.7:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
+acorn@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf"
+ integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==
+
address@1.0.3, address@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"
@@ -1526,6 +1765,11 @@ ansi-regex@^4.0.0, ansi-regex@^4.1.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+ansi-regex@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
+ integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1538,6 +1782,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
+ integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
+ dependencies:
+ "@types/color-name" "^1.1.1"
+ color-convert "^2.0.1"
+
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -1581,6 +1833,14 @@ aria-query@^3.0.0:
ast-types-flow "0.0.7"
commander "^2.11.0"
+aria-query@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.0.2.tgz#250687b4ccde1ab86d127da0432ae3552fc7b145"
+ integrity sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w==
+ dependencies:
+ "@babel/runtime" "^7.7.4"
+ "@babel/runtime-corejs3" "^7.7.4"
+
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -1709,6 +1969,13 @@ asn1@~0.2.3:
dependencies:
safer-buffer "~2.1.0"
+asn1js@^2.0.26:
+ version "2.0.26"
+ resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-2.0.26.tgz#0a6d435000f556a96c6012969d9704d981b71251"
+ integrity sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==
+ dependencies:
+ pvutils latest
+
assert-plus@1.0.0, assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
@@ -1768,7 +2035,7 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-atob@^2.1.1:
+atob@^2.1.1, atob@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
@@ -2109,6 +2376,13 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
+braces@^3.0.1:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -2119,6 +2393,11 @@ browser-process-hrtime@^0.1.2:
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4"
integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==
+browser-process-hrtime@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
+ integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
+
browser-resolve@^1.11.3:
version "1.11.3"
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
@@ -2411,6 +2690,22 @@ chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
+chalk@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+ integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chalk@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
+ integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -2588,12 +2883,19 @@ color-convert@^1.9.0, color-convert@^1.9.1:
dependencies:
color-name "1.1.3"
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-color-name@^1.0.0:
+color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
@@ -2807,6 +3109,11 @@ core-js-pure@3.0.1:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.0.1.tgz#37358fb0d024e6b86d443d794f4e37e949098cbe"
integrity sha512-mSxeQ6IghKW3MoyF4cz19GJ1cMm7761ON+WObSyLfTu/Jn3x7w4NwNFnrZxgl4MTSvYYepVLNuRtlB4loMwJ5g==
+core-js-pure@^3.0.0:
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
+ integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
+
core-js@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.1.tgz#1343182634298f7f38622f95e73f54e48ddf4738"
@@ -2886,6 +3193,14 @@ create-react-context@^0.2.2:
fbjs "^0.8.0"
gud "^1.0.0"
+cross-fetch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c"
+ integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==
+ dependencies:
+ node-fetch "2.6.0"
+ whatwg-fetch "3.0.0"
+
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -3030,6 +3345,21 @@ css-what@2.1, css-what@^2.1.2:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
+css.escape@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
+ integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
+
+css@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
+ integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
+ dependencies:
+ inherits "^2.0.3"
+ source-map "^0.6.1"
+ source-map-resolve "^0.5.2"
+ urix "^0.1.0"
+
cssdb@^4.3.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0"
@@ -3125,6 +3455,16 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@^0.3.4:
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.6.tgz#f85206cee04efa841f3c5982a74ba96ab20d65ad"
integrity sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==
+cssom@^0.4.4:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
+ integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
+
+cssom@~0.3.6:
+ version "0.3.8"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+ integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
+
cssstyle@^1.0.0, cssstyle@^1.1.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.2.2.tgz#427ea4d585b18624f6fdbf9de7a2a1a3ba713077"
@@ -3132,6 +3472,18 @@ cssstyle@^1.0.0, cssstyle@^1.1.1:
dependencies:
cssom "0.3.x"
+cssstyle@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
+ integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
+ dependencies:
+ cssom "~0.3.6"
+
+csstype@^2.2.0:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
+ integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
+
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@@ -3165,6 +3517,15 @@ data-urls@^1.0.0, data-urls@^1.1.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
+data-urls@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
+ integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==
+ dependencies:
+ abab "^2.0.3"
+ whatwg-mimetype "^2.3.0"
+ whatwg-url "^8.0.0"
+
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -3210,6 +3571,11 @@ decamelize@^2.0.0:
dependencies:
xregexp "4.0.0"
+decimal.js@^10.2.0:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
+ integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==
+
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@@ -3347,6 +3713,11 @@ diff-sequences@^24.3.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
+diff-sequences@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
+ integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
+
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -3411,6 +3782,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dom-accessibility-api@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c"
+ integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA==
+
dom-converter@^0.2:
version "0.2.0"
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@@ -3443,6 +3819,13 @@ domexception@^1.0.1:
dependencies:
webidl-conversions "^4.0.2"
+domexception@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
+ integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==
+ dependencies:
+ webidl-conversions "^5.0.0"
+
domhandler@^2.3.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
@@ -3721,6 +4104,18 @@ escodegen@^1.11.0, escodegen@^1.9.1:
optionalDependencies:
source-map "~0.6.1"
+escodegen@^1.14.1:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457"
+ integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==
+ dependencies:
+ esprima "^4.0.1"
+ estraverse "^4.2.0"
+ esutils "^2.0.2"
+ optionator "^0.8.1"
+ optionalDependencies:
+ source-map "~0.6.1"
+
eslint-config-react-app@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-4.0.0.tgz#1651f44b3830d863817af6ebed0916193aa870c3"
@@ -3894,7 +4289,7 @@ esprima@^3.1.3:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=
-esprima@^4.0.0:
+esprima@^4.0.0, esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
@@ -4214,6 +4609,13 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
finalhandler@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
@@ -4617,6 +5019,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
+graceful-fs@^4.2.4:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
+ integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@@ -4656,7 +5063,7 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-har-validator@~5.1.0:
+har-validator@~5.1.0, har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@@ -4693,6 +5100,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
has-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
@@ -4870,6 +5282,13 @@ html-encoding-sniffer@^1.0.2:
dependencies:
whatwg-encoding "^1.0.1"
+html-encoding-sniffer@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
+ integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==
+ dependencies:
+ whatwg-encoding "^1.0.5"
+
html-entities@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
@@ -5085,6 +5504,11 @@ indent-string@^2.1.0:
dependencies:
repeating "^2.0.0"
+indent-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
+ integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+
indexes-of@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
@@ -5380,6 +5804,11 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
is-obj@^1.0.0, is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@@ -5416,6 +5845,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
+is-potential-custom-element-name@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
+ integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
+
is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@@ -5676,6 +6110,16 @@ jest-diff@^24.7.0:
jest-get-type "^24.3.0"
pretty-format "^24.7.0"
+jest-diff@^25.1.0, jest-diff@^25.2.1, jest-diff@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
+ integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
+ dependencies:
+ chalk "^3.0.0"
+ diff-sequences "^25.2.6"
+ jest-get-type "^25.2.6"
+ pretty-format "^25.5.0"
+
jest-docblock@^24.3.0:
version "24.3.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd"
@@ -5703,6 +6147,16 @@ jest-environment-jsdom-fourteen@0.1.0:
jest-util "^24.5.0"
jsdom "^14.0.0"
+jest-environment-jsdom-sixteen@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom-sixteen/-/jest-environment-jsdom-sixteen-1.0.3.tgz#e222228fac537ef15cca5ad470b19b47d9690165"
+ integrity sha512-CwMqDUUfSl808uGPWXlNA1UFkWFgRmhHvyAjhCmCry6mYq4b/nn80MMN7tglqo5XgrANIs/w+mzINPzbZ4ZZrQ==
+ dependencies:
+ "@jest/fake-timers" "^25.1.0"
+ jest-mock "^25.1.0"
+ jest-util "^25.1.0"
+ jsdom "^16.2.1"
+
jest-environment-jsdom@^24.7.1:
version "24.7.1"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz#a40e004b4458ebeb8a98082df135fd501b9fbbd6"
@@ -5726,11 +6180,24 @@ jest-environment-node@^24.7.1:
jest-mock "^24.7.0"
jest-util "^24.7.1"
+jest-fetch-mock@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b"
+ integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==
+ dependencies:
+ cross-fetch "^3.0.4"
+ promise-polyfill "^8.1.3"
+
jest-get-type@^24.3.0:
version "24.3.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da"
integrity sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==
+jest-get-type@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
+ integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
+
jest-haste-map@^24.7.1:
version "24.7.1"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.7.1.tgz#772e215cd84080d4bbcb759cfb668ad649a21471"
@@ -5789,6 +6256,16 @@ jest-matcher-utils@^24.7.0:
jest-get-type "^24.3.0"
pretty-format "^24.7.0"
+jest-matcher-utils@^25.1.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz#fbc98a12d730e5d2453d7f1ed4a4d948e34b7867"
+ integrity sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==
+ dependencies:
+ chalk "^3.0.0"
+ jest-diff "^25.5.0"
+ jest-get-type "^25.2.6"
+ pretty-format "^25.5.0"
+
jest-message-util@^24.7.1:
version "24.7.1"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.7.1.tgz#f1dc3a6c195647096a99d0f1dadbc447ae547018"
@@ -5803,6 +6280,20 @@ jest-message-util@^24.7.1:
slash "^2.0.0"
stack-utils "^1.0.1"
+jest-message-util@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.5.0.tgz#ea11d93204cc7ae97456e1d8716251185b8880ea"
+ integrity sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@jest/types" "^25.5.0"
+ "@types/stack-utils" "^1.0.1"
+ chalk "^3.0.0"
+ graceful-fs "^4.2.4"
+ micromatch "^4.0.2"
+ slash "^3.0.0"
+ stack-utils "^1.0.1"
+
jest-mock@^24.5.0, jest-mock@^24.7.0:
version "24.7.0"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.7.0.tgz#e49ce7262c12d7f5897b0d8af77f6db8e538023b"
@@ -5810,6 +6301,13 @@ jest-mock@^24.5.0, jest-mock@^24.7.0:
dependencies:
"@jest/types" "^24.7.0"
+jest-mock@^25.1.0, jest-mock@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.5.0.tgz#a91a54dabd14e37ecd61665d6b6e06360a55387a"
+ integrity sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==
+ dependencies:
+ "@jest/types" "^25.5.0"
+
jest-pnp-resolver@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a"
@@ -5935,6 +6433,17 @@ jest-util@^24.5.0, jest-util@^24.7.1:
slash "^2.0.0"
source-map "^0.6.0"
+jest-util@^25.1.0, jest-util@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.5.0.tgz#31c63b5d6e901274d264a4fec849230aa3fa35b0"
+ integrity sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ chalk "^3.0.0"
+ graceful-fs "^4.2.4"
+ is-ci "^2.0.0"
+ make-dir "^3.0.0"
+
jest-validate@^24.7.0:
version "24.7.0"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.7.0.tgz#70007076f338528ee1b1c8a8258b1b0bb982508d"
@@ -6104,6 +6613,38 @@ jsdom@^14.0.0:
ws "^6.1.2"
xml-name-validator "^3.0.0"
+jsdom@^16.2.1:
+ version "16.2.2"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.2.2.tgz#76f2f7541646beb46a938f5dc476b88705bedf2b"
+ integrity sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==
+ dependencies:
+ abab "^2.0.3"
+ acorn "^7.1.1"
+ acorn-globals "^6.0.0"
+ cssom "^0.4.4"
+ cssstyle "^2.2.0"
+ data-urls "^2.0.0"
+ decimal.js "^10.2.0"
+ domexception "^2.0.1"
+ escodegen "^1.14.1"
+ html-encoding-sniffer "^2.0.1"
+ is-potential-custom-element-name "^1.0.0"
+ nwsapi "^2.2.0"
+ parse5 "5.1.1"
+ request "^2.88.2"
+ request-promise-native "^1.0.8"
+ saxes "^5.0.0"
+ symbol-tree "^3.2.4"
+ tough-cookie "^3.0.1"
+ w3c-hr-time "^1.0.2"
+ w3c-xmlserializer "^2.0.0"
+ webidl-conversions "^6.0.0"
+ whatwg-encoding "^1.0.5"
+ whatwg-mimetype "^2.3.0"
+ whatwg-url "^8.0.0"
+ ws "^7.2.3"
+ xml-name-validator "^3.0.0"
+
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@@ -6451,11 +6992,23 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==
+lodash@^4.17.15:
+ version "4.17.15"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+ integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+
loglevel@^1.4.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=
+lolex@^5.0.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
+ integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
+ dependencies:
+ "@sinonjs/commons" "^1.7.0"
+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -6499,6 +7052,13 @@ make-dir@^2.0.0, make-dir@^2.1.0:
pify "^4.0.1"
semver "^5.6.0"
+make-dir@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+ integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+ dependencies:
+ semver "^6.0.0"
+
makeerror@1.0.x:
version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@@ -6637,6 +7197,14 @@ micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
snapdragon "^0.8.1"
to-regex "^3.0.2"
+micromatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+ integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+ dependencies:
+ braces "^3.0.1"
+ picomatch "^2.0.5"
+
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -6677,6 +7245,11 @@ mimic-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+min-indent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256"
+ integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=
+
mini-css-extract-plugin@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0"
@@ -6896,6 +7469,11 @@ no-case@^2.2.0:
dependencies:
lower-case "^1.1.1"
+node-fetch@2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
+ integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
+
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@@ -7129,6 +7707,11 @@ nwsapi@^2.0.7, nwsapi@^2.1.3:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.3.tgz#25f3a5cec26c654f7376df6659cdf84b99df9558"
integrity sha512-RowAaJGEgYXEZfQ7tvvdtAQUKPyTR6T6wNu0fwlNsGQYr/h3yQc6oI8WnVZh3Y/Sylwc+dtAlvPqfFZjhTyk3A==
+nwsapi@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
+ integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
+
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@@ -7489,6 +8072,11 @@ parse5@5.1.0, parse5@^5.0.0:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
+parse5@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
+ integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
+
parse5@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
@@ -7613,6 +8201,11 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
+picomatch@^2.0.5:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
+ integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
+
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -8380,6 +8973,26 @@ pretty-format@^24.7.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
+pretty-format@^25.1.0, pretty-format@^25.2.1, pretty-format@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
+ integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ ansi-regex "^5.0.0"
+ ansi-styles "^4.0.0"
+ react-is "^16.12.0"
+
+pretty-format@^26.0.1:
+ version "26.0.1"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.0.1.tgz#a4fe54fe428ad2fd3413ca6bbd1ec8c2e277e197"
+ integrity sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==
+ dependencies:
+ "@jest/types" "^26.0.1"
+ ansi-regex "^5.0.0"
+ ansi-styles "^4.0.0"
+ react-is "^16.12.0"
+
private@^0.1.6:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -8405,6 +9018,11 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
+promise-polyfill@^8.1.3:
+ version "8.1.3"
+ resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116"
+ integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==
+
promise@8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.2.tgz#9dcd0672192c589477d56891271bdc27547ae9f0"
@@ -8527,6 +9145,18 @@ punycode@^1.2.4, punycode@^1.4.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
+pvtsutils@^1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.0.10.tgz#157d0fcb853f570d32e0f8788179f3057eacdf38"
+ integrity sha512-8ZKQcxnZKTn+fpDh7wL4yKax5fdl3UJzT8Jv49djZpB/dzPxacyN1Sez90b6YLdOmvIr9vaySJ5gw4aUA1EdSw==
+ dependencies:
+ tslib "^1.10.0"
+
+pvutils@latest:
+ version "1.0.17"
+ resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.0.17.tgz#ade3c74dfe7178944fe44806626bd2e249d996bf"
+ integrity sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==
+
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -8680,6 +9310,11 @@ react-feather@^1.1.6:
resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.1.6.tgz#2a547e3d5cd5e383d3da0128d593cbdb3c1b32f7"
integrity sha512-iCofWhTjX+vQwvDmg7o6vg0XrUg1c41yBDZG+l83nz1FiCsleJoUgd3O+kHpOeWMXuPrRIFfCixvcqyOLGOgIg==
+react-is@^16.12.0:
+ version "16.13.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
@@ -8940,6 +9575,14 @@ redent@^1.0.0:
indent-string "^2.1.0"
strip-indent "^1.0.1"
+redent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
+ integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
+ dependencies:
+ indent-string "^4.0.0"
+ strip-indent "^3.0.0"
+
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
@@ -8980,6 +9623,11 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
+regenerator-runtime@^0.13.4:
+ version "0.13.5"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
+ integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
+
regenerator-transform@^0.13.4:
version "0.13.4"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb"
@@ -9088,6 +9736,13 @@ request-promise-core@1.1.2:
dependencies:
lodash "^4.17.11"
+request-promise-core@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
+ integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==
+ dependencies:
+ lodash "^4.17.15"
+
request-promise-native@^1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59"
@@ -9097,6 +9752,15 @@ request-promise-native@^1.0.5:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
+request-promise-native@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36"
+ integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==
+ dependencies:
+ request-promise-core "1.1.3"
+ stealthy-require "^1.1.1"
+ tough-cookie "^2.3.3"
+
request@^2.87.0, request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
@@ -9123,6 +9787,32 @@ request@^2.87.0, request@^2.88.0:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
+request@^2.88.2:
+ version "2.88.2"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
+ integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.8.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.6"
+ extend "~3.0.2"
+ forever-agent "~0.6.1"
+ form-data "~2.3.2"
+ har-validator "~5.1.3"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.19"
+ oauth-sign "~0.9.0"
+ performance-now "^2.1.0"
+ qs "~6.5.2"
+ safe-buffer "^5.1.2"
+ tough-cookie "~2.5.0"
+ tunnel-agent "^0.6.0"
+ uuid "^3.3.2"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -9346,6 +10036,13 @@ saxes@^3.1.9:
dependencies:
xmlchars "^1.3.1"
+saxes@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
+ integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
+ dependencies:
+ xmlchars "^2.2.0"
+
scheduler@^0.13.6:
version "0.13.6"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889"
@@ -9578,6 +10275,11 @@ slash@^2.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
slice-ansi@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
@@ -9682,6 +10384,17 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
+source-map-resolve@^0.5.2:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
+ integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==
+ dependencies:
+ atob "^2.1.2"
+ decode-uri-component "^0.2.0"
+ resolve-url "^0.2.1"
+ source-map-url "^0.4.0"
+ urix "^0.1.0"
+
source-map-support@^0.5.6, source-map-support@~0.5.10:
version "0.5.12"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
@@ -9999,6 +10712,13 @@ strip-indent@^1.0.1:
dependencies:
get-stdin "^4.0.1"
+strip-indent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
+ integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
+ dependencies:
+ min-indent "^1.0.0"
+
strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -10040,6 +10760,13 @@ supports-color@^6.0.0, supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
+supports-color@^7.1.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
+ integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
+ dependencies:
+ has-flag "^4.0.0"
+
svgo@^1.0.0, svgo@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.2.2.tgz#0253d34eccf2aed4ad4f283e11ee75198f9d7316"
@@ -10070,6 +10797,11 @@ symbol-tree@^3.2.2:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
+symbol-tree@^3.2.4:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+ integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+
table@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2"
@@ -10247,6 +10979,13 @@ to-regex-range@^2.1.0:
is-number "^3.0.0"
repeat-string "^1.6.1"
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
to-regex@^3.0.1, to-regex@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
@@ -10264,7 +11003,7 @@ topo@3.x.x:
dependencies:
hoek "6.x.x"
-tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0:
+tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@@ -10272,6 +11011,15 @@ tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0:
psl "^1.1.28"
punycode "^2.1.1"
+tough-cookie@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
+ integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
+ dependencies:
+ ip-regex "^2.1.0"
+ psl "^1.1.28"
+ punycode "^2.1.1"
+
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
@@ -10287,6 +11035,13 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+tr46@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479"
+ integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==
+ dependencies:
+ punycode "^2.1.1"
+
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -10314,6 +11069,11 @@ ts-pnp@^1.0.0:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.2.tgz#be8e4bfce5d00f0f58e0666a82260c34a57af552"
integrity sha512-f5Knjh7XCyRIzoC/z1Su1yLLRrPrFCgtUAh/9fCSP6NKbATwpOL1+idQVXQokK9GRFURn/jYPGPfegIctwunoA==
+tslib@^1.10.0, tslib@^1.11.1, tslib@^1.11.2:
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
+ integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
+
tslib@^1.8.1, tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
@@ -10350,6 +11110,11 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
+type-detect@4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+ integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
+
type-is@~1.6.16:
version "1.6.16"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
@@ -10671,6 +11436,13 @@ w3c-hr-time@^1.0.1:
dependencies:
browser-process-hrtime "^0.1.2"
+w3c-hr-time@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
+ integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
+ dependencies:
+ browser-process-hrtime "^1.0.0"
+
w3c-xmlserializer@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794"
@@ -10680,6 +11452,13 @@ w3c-xmlserializer@^1.1.2:
webidl-conversions "^4.0.2"
xml-name-validator "^3.0.0"
+w3c-xmlserializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a"
+ integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==
+ dependencies:
+ xml-name-validator "^3.0.0"
+
walker@^1.0.7, walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
@@ -10715,6 +11494,17 @@ web-namespaces@^1.1.2:
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.2.tgz#c8dc267ab639505276bae19e129dbd6ae72b22b4"
integrity sha512-II+n2ms4mPxK+RnIxRPOw3zwF2jRscdJIUE9BfkKHm4FYEg9+biIoTMnaZF5MpemE3T+VhMLrhbyD4ilkPCSbg==
+webcrypto-core@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.1.1.tgz#c9cd26f8dea696d7b5f5c1b0598ff16e6bdcab7c"
+ integrity sha512-xK61sFRUyZdSAJG7+bJox36+Tnhxw1PaMbmrLLp30HNTJ4mffqsY2jUMlmGq6OOoej3WO/SsH5serzlzBMZ+jg==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.0.1"
+ "@peculiar/json-schema" "^1.1.10"
+ asn1js "^2.0.26"
+ pvtsutils "^1.0.10"
+ tslib "^1.11.2"
+
webcrypto-shim@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/webcrypto-shim/-/webcrypto-shim-0.1.4.tgz#b40ef59d042822269dd83c1462da1df4369834fb"
@@ -10725,6 +11515,16 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
+webidl-conversions@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
+ integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
+
+webidl-conversions@^6.0.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
+ integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
+
webpack-dev-middleware@^3.5.1:
version "3.6.2"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.2.tgz#f37a27ad7c09cd7dc67cd97655413abaa1f55942"
@@ -10874,6 +11674,15 @@ whatwg-url@^7.0.0:
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
+whatwg-url@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.0.0.tgz#37f256cb746398e19b107bd6ef820b4ae2d15871"
+ integrity sha512-41ou2Dugpij8/LPO5Pq64K5q++MnRCBpEHvQr26/mArEKTkCV5aoXIqyhuYtE0pkqScXwhf2JP57rkRTYM29lQ==
+ dependencies:
+ lodash.sortby "^4.7.0"
+ tr46 "^2.0.0"
+ webidl-conversions "^5.0.0"
+
which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
@@ -11093,6 +11902,11 @@ ws@^6.1.2:
dependencies:
async-limiter "~1.0.0"
+ws@^7.2.3:
+ version "7.2.5"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.5.tgz#abb1370d4626a5a9cd79d8de404aa18b3465d10d"
+ integrity sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==
+
ws@~6.1.0:
version "6.1.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
@@ -11115,6 +11929,11 @@ xmlchars@^1.3.1:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-1.3.1.tgz#1dda035f833dbb4f86a0c28eaa6ca769214793cf"
integrity sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw==
+xmlchars@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
+ integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
diff --git a/package.json b/package.json
index 88135ba..d22978b 100644
--- a/package.json
+++ b/package.json
@@ -19,9 +19,9 @@
"scripts": {
"build": "./build.sh",
"start": "cd server && CLIENT_DIST_DIRECTORY='../client/build' yarn start",
- "setup": "cd client && yarn && cd ../server && yarn",
+ "setup": "yarn && cd client && yarn && cd ../server && yarn",
"dev": "concurrently 'cd client && yarn start' 'cd server && yarn dev'",
- "test": "echo 'tests'"
+ "test": "concurrently 'cd client && yarn coverage' 'cd server && yarn test --watchAll=false'"
},
"devDependencies": {
"concurrently": "^4.1.0"