Update all dependencies

This commit is contained in:
Jeremie Pardou-Piquemal 2022-12-06 21:41:31 +01:00 committed by Jérémie Pardou-Piquemal
parent d0d36d690c
commit 0057292553
119 changed files with 4948 additions and 16856 deletions

View File

@ -4,7 +4,7 @@
jobs: jobs:
test-job: test-job:
docker: docker:
- image: "circleci/node:lts" - image: "cimg/node:lts"
working_directory: ~/repo working_directory: ~/repo
@ -33,7 +33,7 @@ jobs:
command: yarn test command: yarn test
environment: environment:
TZ: UTC TZ: UTC
REACT_APP_COMMIT_SHA: some_sha VITE_COMMIT_SHA: some_sha
- store_artifacts: # For coverage report - store_artifacts: # For coverage report
path: client/coverage path: client/coverage

View File

@ -10,10 +10,10 @@ fi
echo "building client..." echo "building client..."
cd client cd client
yarn --production=false yarn --production=false
REACT_APP_COMMIT_SHA=$SOURCE_VERSION \ VITE_COMMIT_SHA=$SOURCE_VERSION \
REACT_APP_API_HOST=$api_host \ VITE_API_HOST=$api_host \
REACT_APP_API_PROTOCOL=$API_PROTOCOL \ VITE_API_PROTOCOL=$API_PROTOCOL \
REACT_APP_API_PORT=$API_PORT \ VITE_API_PORT=$API_PORT \
yarn build yarn build
cd ../ cd ../

View File

@ -1,14 +1,14 @@
# Api settings # Api settings
TZ=UTC TZ=UTC
REACT_APP_API_HOST=localhost VITE_API_HOST=localhost
REACT_APP_API_PROTOCOL=http VITE_API_PROTOCOL=http
REACT_APP_API_PORT=3001 VITE_API_PORT=3001
REACT_APP_COMMIT_SHA=some_sha VITE_COMMIT_SHA=some_sha
# To display darkwire version # To display darkwire version
REACT_APP_COMMIT_SHA=some_sha VITE_COMMIT_SHA=some_sha
# Set max transferable file size in MB # Set max transferable file size in MB
REACT_APP_MAX_FILE_SIZE=4 VITE_MAX_FILE_SIZE=4

View File

@ -1,7 +0,0 @@
{
"extends": ["plugin:prettier/recommended"],
"parser": "babel-eslint",
"rules": {
"prettier/prettier": "error"
}
}

10
client/.eslintrc.cjs Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
root: true,
env: {
browser: true,
node: true,
},
};

32
client/.gitignore vendored
View File

@ -1,9 +1,25 @@
node_modules # Logs
.DS_Store logs
dist
coverage
*.log *.log
.env* npm-debug.log*
!.env.example yarn-debug.log*
build/ yarn-error.log*
*sublime* pnpm-debug.log*
lerna-debug.log*
node_modules
dist/
dist-ssr
*.local
coverage
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1 +0,0 @@
module.exports = {};

17
client/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en" class="h-100">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<meta name="robots" content="index,nofollow" />
<meta name="googlebot" content="index,nofollow" />
<meta name="description" content="darkwire.io is the simplest way to chat with encryption online." />
<title>Darkwire.io - instant encrypted web chat</title>
</head>
<body class="h-100">
<div id="root" class="h-100"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -6,3 +6,4 @@
} }
} }
} }

View File

@ -2,6 +2,7 @@
"name": "darkwire-client", "name": "darkwire-client",
"version": "2.0.0-beta.12", "version": "2.0.0-beta.12",
"main": "index.js", "main": "index.js",
"type": "module",
"contributors": [ "contributors": [
{ {
"name": "Daniel Seripap" "name": "Daniel Seripap"
@ -12,44 +13,55 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"autosize": "^4.0.2", "bootstrap": "^4.6.2",
"bootstrap": "^4.3.1", "classnames": "^2.3.2",
"clipboard": "^2.0.4", "clipboard": "^2.0.11",
"feather-icons": "^4.21.0", "jquery": "3",
"jquery": "^3.5.0", "js-cookie": "^3.0.1",
"js-cookie": "^2.2.0", "moment": "^2.29.4",
"lodash": "^4.17.20", "nanoid": "^4.0.0",
"moment": "^2.24.0", "randomcolor": "^0.6.2",
"node-sass": "^4.13.1", "react": "^18.2.0",
"popper.js": "^1.15.0", "react-dom": "^18.2.0",
"randomcolor": "^0.5.4", "react-feather": "^2.0.10",
"react": "^16.8.6", "react-linkify": "^1.0.0-alpha",
"react-dom": "^16.8.6", "react-modal": "^3.16.1",
"react-feather": "^1.1.6", "react-redux": "^8.0.5",
"react-linkify": "^0.2.2", "react-router": "^6.4.4",
"react-modal": "^3.8.1", "react-router-dom": "^6.4.4",
"react-redux": "^7.0.3",
"react-router-dom": "^5.0.0",
"react-scripts": "3.0.0",
"react-simple-dropdown": "^3.2.3", "react-simple-dropdown": "^3.2.3",
"redux": "^4.0.1", "redux": "^4.2.0",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.4.2",
"sanitize-html": "^1.20.1", "sanitize-html": "^2.7.3",
"shortid": "^2.2.14", "socket.io-client": "^4.5.4",
"socket.io-client": "^2.2.0", "tinycon": "^0.6.8"
"tinycon": "^0.6.8",
"webcrypto-shim": "^0.1.4"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "dev": "vite",
"build": "react-scripts build", "build": "tsc && vite build",
"test": "react-scripts test --env=jest-environment-jsdom-sixteen", "preview": "vite preview",
"coverage": "react-scripts test --env=jest-environment-jsdom-sixteen --coverage --watchAll=false", "test": "TZ=UTC vitest",
"eject": "react-scripts eject", "lint": "eslint src",
"lint": "eslint src" "coverage": "TZ=UTC vitest --coverage --watch=false"
}, },
"eslintConfig": { "devDependencies": {
"extends": "react-app" "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@typescript-eslint/eslint-plugin": "^5.46.0",
"@typescript-eslint/parser": "^5.46.0",
"@vitejs/plugin-react": "^3.0.0",
"@vitest/coverage-istanbul": "^0.25.7",
"eslint": "^8.29.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-fetch-mock": "^3.0.3",
"prettier": "^2.0.5",
"sass": "^1.56.2",
"typescript": "^4.9.4",
"vitest": "^0.25.7",
"vitest-fetch-mock": "^0.2.1"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
@ -62,18 +74,5 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"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",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"jest-environment-jsdom-sixteen": "^1.0.3",
"jest-fetch-mock": "^3.0.3",
"prettier": "^2.0.5"
} }
} }

View File

@ -1,42 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="h-100">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<meta name="robots" content="index,nofollow" />
<meta name="googlebot" content="index,nofollow" />
<meta name="description" content="darkwire.io is the simplest way to chat with encryption online." />
<title>darkwire.io - instant encrypted web chat</title>
</head>
<body class="h-100">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="h-100"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -1,5 +1,7 @@
import * as actions from './app'; import * as actions from './app';
import { describe, it, expect, vi } from 'vitest';
describe('App actions', () => { describe('App actions', () => {
it('should create an action to scroll to bottom', () => { it('should create an action to scroll to bottom', () => {
expect(actions.setScrolledToBottom('test')).toEqual({ expect(actions.setScrolledToBottom('test')).toEqual({
@ -22,7 +24,7 @@ describe('App actions', () => {
}); });
it('should create an action to clear activities', () => { it('should create an action to clear activities', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
actions.clearActivities()(mockDispatch); actions.clearActivities()(mockDispatch);
@ -31,7 +33,7 @@ describe('App actions', () => {
}); });
}); });
it('should create all actions', () => { it('should create all actions', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
const actionsResults = [ const actionsResults = [
[actions.toggleWindowFocus('test'), 'TOGGLE_WINDOW_FOCUS'], [actions.toggleWindowFocus('test'), 'TOGGLE_WINDOW_FOCUS'],

View File

@ -1,5 +1,5 @@
import { getSocket } from 'utils/socket'; import { getSocket } from '@/utils/socket';
import { prepare as prepareMessage, process as processMessage } from 'utils/message'; import { prepare as prepareMessage, process as processMessage } from '@/utils/message';
export const sendEncryptedMessage = payload => async (dispatch, getState) => { export const sendEncryptedMessage = payload => async (dispatch, getState) => {
const state = getState(); const state = getState();

View File

@ -1,21 +1,23 @@
import * as actions from './encrypted_messages'; import * as actions from './encrypted_messages';
import { getSocket } from 'utils/socket'; import { getSocket } from '@/utils/socket';
import { prepare as prepareMessage, process as processMessage } from 'utils/message'; import { prepare as prepareMessage, process as processMessage } from '@/utils/message';
jest.mock('utils/message', () => { import { describe, it, expect, vi } from 'vitest';
vi.mock('@/utils/message', () => {
return { return {
prepare: jest prepare: vi
.fn() .fn()
.mockResolvedValue({ original: { type: 'messageType', payload: 'test' }, toSend: 'encryptedpayload' }), .mockResolvedValue({ original: { type: 'messageType', payload: 'test' }, toSend: 'encryptedpayload' }),
process: jest.fn().mockResolvedValue({ type: 'messageType', payload: 'test' }), process: vi.fn().mockResolvedValue({ type: 'messageType', payload: 'test' }),
}; };
}); });
const mockEmit = jest.fn(); const mockEmit = vi.fn();
jest.mock('utils/socket', () => { vi.mock('@/utils/socket', () => {
return { return {
getSocket: jest.fn().mockImplementation(() => ({ getSocket: vi.fn().mockImplementation(() => ({
emit: mockEmit, emit: mockEmit,
})), })),
}; };
@ -23,9 +25,9 @@ jest.mock('utils/socket', () => {
describe('Encrypted messages actions', () => { describe('Encrypted messages actions', () => {
it('should create an action to send message', async () => { it('should create an action to send message', async () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
await actions.sendEncryptedMessage({ payload: 'payload' })(mockDispatch, jest.fn().mockReturnValue({ state: {} })); await actions.sendEncryptedMessage({ payload: 'payload' })(mockDispatch, vi.fn().mockReturnValue({ state: {} }));
expect(prepareMessage).toHaveBeenLastCalledWith({ payload: 'payload' }, { state: {} }); expect(prepareMessage).toHaveBeenLastCalledWith({ payload: 'payload' }, { state: {} });
expect(mockDispatch).toHaveBeenLastCalledWith({ payload: 'test', type: 'SEND_ENCRYPTED_MESSAGE_messageType' }); expect(mockDispatch).toHaveBeenLastCalledWith({ payload: 'test', type: 'SEND_ENCRYPTED_MESSAGE_messageType' });
@ -33,11 +35,11 @@ describe('Encrypted messages actions', () => {
}); });
it('should create an action to receive message', async () => { it('should create an action to receive message', async () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
await actions.receiveEncryptedMessage({ payload: 'encrypted' })( await actions.receiveEncryptedMessage({ payload: 'encrypted' })(
mockDispatch, mockDispatch,
jest.fn().mockReturnValue({ state: {} }), vi.fn().mockReturnValue({ state: {} }),
); );
expect(processMessage).toHaveBeenLastCalledWith({ payload: 'encrypted' }, { state: {} }); expect(processMessage).toHaveBeenLastCalledWith({ payload: 'encrypted' }, { state: {} });

View File

@ -1,4 +1,4 @@
import { getSocket } from 'utils/socket'; import { getSocket } from '@/utils/socket';
const receiveUserEnter = (payload, dispatch) => { const receiveUserEnter = (payload, dispatch) => {
dispatch({ type: 'USER_ENTER', payload }); dispatch({ type: 'USER_ENTER', payload });
@ -70,7 +70,7 @@ const sendToggleLockRoom = (dispatch, getState) => {
}); });
}; };
export const sendUnencryptedMessage = (type, payload) => async (dispatch, getState) => { export const sendUnencryptedMessage = type => async (dispatch, getState) => {
switch (type) { switch (type) {
case 'TOGGLE_LOCK_ROOM': case 'TOGGLE_LOCK_ROOM':
return sendToggleLockRoom(dispatch, getState); return sendToggleLockRoom(dispatch, getState);

View File

@ -1,33 +1,34 @@
import * as actions from './unencrypted_messages'; import * as actions from './unencrypted_messages';
import { getSocket } from 'utils/socket';
const mockEmit = jest.fn((_type, _null, callback) => { import { describe, it, expect, vi } from 'vitest';
const mockEmit = vi.fn((_type, _null, callback) => {
callback({ isLocked: true }); callback({ isLocked: true });
}); });
jest.mock('utils/socket', () => { vi.mock('@/utils/socket', () => {
return { return {
getSocket: jest.fn().mockImplementation(() => ({ getSocket: vi.fn().mockImplementation(() => ({
emit: mockEmit, emit: mockEmit,
})), })),
}; };
}); });
describe('Reveice unencrypted message actions', () => { describe('Receive unencrypted message actions', () => {
it('should create no action', () => { it('should create no action', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
actions.receiveUnencryptedMessage('FAKE')(mockDispatch, jest.fn().mockReturnValue({})); actions.receiveUnencryptedMessage('FAKE')(mockDispatch, vi.fn().mockReturnValue({}));
expect(mockDispatch).not.toHaveBeenCalled(); expect(mockDispatch).not.toHaveBeenCalled();
}); });
it('should create user enter action', () => { it('should create user enter action', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
actions.receiveUnencryptedMessage('USER_ENTER', 'test')(mockDispatch, jest.fn().mockReturnValue({ state: {} })); actions.receiveUnencryptedMessage('USER_ENTER', 'test')(mockDispatch, vi.fn().mockReturnValue({ state: {} }));
expect(mockDispatch).toHaveBeenLastCalledWith({ type: 'USER_ENTER', payload: 'test' }); expect(mockDispatch).toHaveBeenLastCalledWith({ type: 'USER_ENTER', payload: 'test' });
}); });
it('should create user exit action', () => { it('should create user exit action', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
const state = { const state = {
room: { room: {
members: [ members: [
@ -37,7 +38,7 @@ describe('Reveice unencrypted message actions', () => {
], ],
}, },
}; };
const mockGetState = jest.fn().mockReturnValue(state); const mockGetState = vi.fn().mockReturnValue(state);
const payload1 = [ const payload1 = [
{ publicKey: { n: 'alankey' } }, { publicKey: { n: 'alankey' } },
{ publicKey: { n: 'dankey' } }, { publicKey: { n: 'dankey' } },
@ -62,7 +63,7 @@ describe('Reveice unencrypted message actions', () => {
}); });
it('should create receive toggle lock room action', () => { it('should create receive toggle lock room action', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
const state = { const state = {
room: { room: {
members: [ members: [
@ -71,7 +72,7 @@ describe('Reveice unencrypted message actions', () => {
], ],
}, },
}; };
const mockGetState = jest.fn().mockReturnValue(state); const mockGetState = vi.fn().mockReturnValue(state);
const payload = { publicKey: { n: 'alankey' } }; const payload = { publicKey: { n: 'alankey' } };
actions.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload)(mockDispatch, mockGetState); actions.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload)(mockDispatch, mockGetState);
@ -82,14 +83,14 @@ describe('Reveice unencrypted message actions', () => {
}); });
it('should create receive toggle lock room action', () => { it('should create receive toggle lock room action', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
const state = { const state = {
user: { user: {
username: 'alan', username: 'alan',
id: 'idalan', id: 'idalan',
}, },
}; };
const mockGetState = jest.fn().mockReturnValue(state); const mockGetState = vi.fn().mockReturnValue(state);
actions.sendUnencryptedMessage('TOGGLE_LOCK_ROOM')(mockDispatch, mockGetState); actions.sendUnencryptedMessage('TOGGLE_LOCK_ROOM')(mockDispatch, mockGetState);
expect(mockDispatch).toHaveBeenLastCalledWith({ expect(mockDispatch).toHaveBeenLastCalledWith({
@ -101,20 +102,20 @@ describe('Reveice unencrypted message actions', () => {
describe('Send unencrypted message actions', () => { describe('Send unencrypted message actions', () => {
it('should create no action', () => { it('should create no action', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
actions.sendUnencryptedMessage('FAKE')(mockDispatch, jest.fn().mockReturnValue({})); actions.sendUnencryptedMessage('FAKE')(mockDispatch, vi.fn().mockReturnValue({}));
expect(mockDispatch).not.toHaveBeenCalled(); expect(mockDispatch).not.toHaveBeenCalled();
}); });
it('should create toggle lock room action', () => { it('should create toggle lock room action', () => {
const mockDispatch = jest.fn(); const mockDispatch = vi.fn();
const state = { const state = {
user: { user: {
username: 'alan', username: 'alan',
id: 'idalan', id: 'idalan',
}, },
}; };
const mockGetState = jest.fn().mockReturnValue(state); const mockGetState = vi.fn().mockReturnValue(state);
actions.sendUnencryptedMessage('TOGGLE_LOCK_ROOM')(mockDispatch, mockGetState); actions.sendUnencryptedMessage('TOGGLE_LOCK_ROOM')(mockDispatch, mockGetState);
expect(mockDispatch).toHaveBeenLastCalledWith({ expect(mockDispatch).toHaveBeenLastCalledWith({

View File

@ -3,21 +3,21 @@ let host;
let protocol; let protocol;
let port; let port;
switch (process.env.NODE_ENV) { switch (import.meta.env.NODE_ENV) {
case 'staging': case 'staging':
host = process.env.REACT_APP_API_HOST; host = import.meta.env.VITE_API_HOST;
protocol = process.env.REACT_APP_API_PROTOCOL || 'https'; protocol = import.meta.env.VITE_API_PROTOCOL || 'https';
port = process.env.REACT_APP_API_PORT || 443; port = import.meta.env.VITE_API_PORT || 443;
break; break;
case 'production': case 'production':
host = process.env.REACT_APP_API_HOST; host = import.meta.env.VITE_API_HOST;
protocol = process.env.REACT_APP_API_PROTOCOL || 'https'; protocol = import.meta.env.VITE_API_PROTOCOL || 'https';
port = process.env.REACT_APP_API_PORT || 443; port = import.meta.env.VITE_API_PORT || 443;
break; break;
default: default:
host = process.env.REACT_APP_API_HOST || 'localhost'; host = import.meta.env.VITE_API_HOST || 'localhost';
protocol = process.env.REACT_APP_API_PROTOCOL || 'http'; protocol = import.meta.env.VITE_API_PROTOCOL || 'http';
port = process.env.REACT_APP_API_PORT || 3001; port = import.meta.env.VITE_API_PORT || 3001;
} }
export default { export default {

View File

@ -1,15 +1,17 @@
import React from 'react'; import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react'; import { render, fireEvent } from '@testing-library/react';
import About from '.'; import About from '.';
import fetchMock from 'jest-fetch-mock'; import { describe, it, expect, vi, afterEach } from 'vitest';
jest.useFakeTimers(); vi.useFakeTimers();
// Mock Api generator // Mock Api generator
jest.mock('../../api/generator', () => { vi.mock('@/api/generator', () => {
return path => { return {
return `http://fakedomain/${path}`; default: path => {
return `http://fakedomain/${path}`;
},
}; };
}); });
@ -43,7 +45,7 @@ describe('About component', () => {
fireEvent.change(getByPlaceholderText('Room ID'), { target: { value: 'newRoomName' } }); fireEvent.change(getByPlaceholderText('Room ID'), { target: { value: 'newRoomName' } });
jest.runAllTimers(); vi.runAllTimers();
fireEvent.click(getByText('Submit')); fireEvent.click(getByText('Submit'));

View File

@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`About component should display 1`] = ` exports[`About component > should display 1`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="base" class="_base_4f26aa"
> >
<div <div
class="links" class="_links_4f26aa"
> >
<div> <div>
<a <a

View File

@ -1,7 +1,9 @@
/* eslint-disable */ /* eslint-disable */
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import apiUrlGenerator from '../../api/generator';
import { COMMIT_SHA } from '@/config/env';
import apiUrlGenerator from '@/api/generator';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
class About extends Component { class About extends Component {
@ -63,11 +65,8 @@ class About extends Component {
<h4>Version</h4> <h4>Version</h4>
<p> <p>
Commit SHA:{' '} Commit SHA:{' '}
<a <a target="_blank" href={`https://github.com/darkwire/darkwire.io/commit/${COMMIT_SHA}`}>
target="_blank" {COMMIT_SHA}
href={`https://github.com/darkwire/darkwire.io/commit/${process.env.REACT_APP_COMMIT_SHA}`}
>
{process.env.REACT_APP_COMMIT_SHA}
</a> </a>
</p> </p>
</section> </section>

View File

@ -1,12 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html';
import FileTransfer from 'components/FileTransfer';
import { CornerDownRight } from 'react-feather'; import { CornerDownRight } from 'react-feather';
import { getSelectedText, hasTouchSupport } from '../../utils/dom';
// Disable for now import { getSelectedText, hasTouchSupport } from '@/utils/dom';
// import autosize from 'autosize'
import FileTransfer from '@/components/FileTransfer';
export class Chat extends Component { export class Chat extends Component {
constructor(props) { constructor(props) {
@ -21,14 +20,14 @@ export class Chat extends Component {
{ {
command: 'nick', command: 'nick',
description: 'Changes nickname.', description: 'Changes nickname.',
paramaters: ['{username}'], parameters: ['{username}'],
usage: '/nick {username}', usage: '/nick {username}',
scope: 'global', scope: 'global',
action: params => { action: params => {
// eslint-disable-line // eslint-disable-line
let newUsername = params.join(' ') || ''; // eslint-disable-line let newUsername = params.join(' ') || ''; // eslint-disable-line
// Remove things that arent digits or chars // Remove things that aren't digits or chars
newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-'); newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-');
const errors = []; const errors = [];

View File

@ -1,19 +1,20 @@
import React from 'react'; import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { Chat } from './Chat'; import { Chat } from '@/components/Chat/Chat';
import * as dom from 'utils/dom'; import * as dom from '@/utils/dom';
const translations = { const translations = {
typePlaceholder: 'inputplaceholder', typePlaceholder: 'inputplaceholder',
}; };
// Fake date // Fake date
jest.spyOn(global.Date, 'now').mockImplementation(() => new Date('2020-03-14T11:01:58.135Z').valueOf()); vi.spyOn(global.Date, 'now').mockImplementation(() => new Date('2020-03-14T11:01:58.135Z').valueOf());
// To change touch support // To change touch support
jest.mock('../../utils/dom'); vi.mock('@/utils/dom');
describe('Chat component', () => { describe('Chat component', () => {
afterEach(() => { afterEach(() => {
@ -39,7 +40,7 @@ describe('Chat component', () => {
}); });
it('can send message', () => { it('can send message', () => {
const sendEncryptedMessage = jest.fn(); const sendEncryptedMessage = vi.fn();
render( render(
<Chat <Chat
@ -78,7 +79,7 @@ describe('Chat component', () => {
}); });
it("shouldn't send message with Shift+enter", () => { it("shouldn't send message with Shift+enter", () => {
const sendEncryptedMessage = jest.fn(); const sendEncryptedMessage = vi.fn();
render( render(
<Chat <Chat
@ -115,9 +116,9 @@ describe('Chat component', () => {
}); });
it('should send commands', () => { it('should send commands', () => {
const sendEncryptedMessage = jest.fn(); const sendEncryptedMessage = vi.fn();
const showNotice = jest.fn(); const showNotice = vi.fn();
const clearActivities = jest.fn(); const clearActivities = vi.fn();
render( render(
<Chat <Chat
@ -210,14 +211,14 @@ describe('Chat component', () => {
// Enable touch support // Enable touch support
dom.hasTouchSupport = true; dom.hasTouchSupport = true;
jest.mock('../../utils/dom', () => { vi.mock('@/utils/dom', () => {
return { return {
getSelectedText: jest.fn(), getSelectedText: vi.fn(),
hasTouchSupport: true, hasTouchSupport: true,
}; };
}); });
const sendEncryptedMessage = jest.fn(); const sendEncryptedMessage = vi.fn();
const { getByTitle } = render( const { getByTitle } = render(
<Chat <Chat

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Chat component should display 1`] = ` exports[`Chat component > should display 1`] = `
<DocumentFragment> <DocumentFragment>
<form <form
class="chat-preflight-container" class="chat-preflight-container"
@ -13,7 +13,7 @@ exports[`Chat component should display 1`] = `
class="input-controls" class="input-controls"
> >
<div <div
class="styles icon file-transfer btn btn-link" class="_styles_374fdd icon file-transfer btn btn-link"
> >
<input <input
id="fileInput" id="fileInput"

View File

@ -1,6 +1,6 @@
import Chat from './Chat'; import Chat from './Chat';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { clearActivities, showNotice, sendEncryptedMessage } from '../../actions'; import { clearActivities, showNotice, sendEncryptedMessage } from '@/actions';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
username: state.user.username, username: state.user.username,

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import Connecting from '.'; import Connecting from '.';
import { test, expect } from 'vitest';
test('Connecting component is displaying', async () => { test('Connecting component is displaying', async () => {
const { asFragment } = render(<Connecting />); const { asFragment } = render(<Connecting />);

View File

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Connecting component is displaying 1`] = ` exports[`Connecting component is displaying 1`] = `
<DocumentFragment> <DocumentFragment>

View File

@ -1,9 +1,12 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import uuid from 'uuid'; import { nanoid } from 'nanoid';
import { File } from 'react-feather'; import { File } from 'react-feather';
import { sanitize } from 'utils';
import { styles } from './styles.module.scss'; import { MAX_FILE_SIZE } from '@/config/env';
import { sanitize } from '@/utils';
import classes from './styles.module.scss';
const VALID_FILE_TYPES = [ const VALID_FILE_TYPES = [
'png', 'png',
@ -24,8 +27,6 @@ const VALID_FILE_TYPES = [
'css', 'css',
]; ];
const MAX_FILE_SIZE = process.env.REACT_APP_MAX_FILE_SIZE || 4;
/** /**
* Encode the given file to binary string * Encode the given file to binary string
* @param {File} file * @param {File} file
@ -86,7 +87,7 @@ export const FileTransfer = ({ sendEncryptedMessage }) => {
return false; return false;
} }
const fileId = uuid.v4(); const fileId = nanoid();
const fileData = { const fileData = {
id: fileId, id: fileId,
file, file,
@ -129,7 +130,7 @@ export const FileTransfer = ({ sendEncryptedMessage }) => {
} }
return ( return (
<div className={`${styles} icon file-transfer btn btn-link`}> <div className={`${classes.styles} icon file-transfer btn btn-link`}>
<input placeholder="Choose a file..." type="file" name="fileUploader" id="fileInput" ref={fileInput} /> <input placeholder="Choose a file..." type="file" name="fileUploader" id="fileInput" ref={fileInput} />
<label htmlFor="fileInput"> <label htmlFor="fileInput">
<File color="#fff" /> <File color="#fff" />

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import { render, screen, fireEvent, createEvent } from '@testing-library/react'; import { render, screen, fireEvent, createEvent } from '@testing-library/react';
import FileTransfer from '.'; import FileTransfer from '.';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// Fake date // Fake date
jest.spyOn(global.Date, 'now').mockImplementation(() => new Date('2020-03-14T11:01:58.135Z').valueOf()); vi.spyOn(global.Date, 'now').mockImplementation(() => new Date('2020-03-14T11:01:58.135Z').valueOf());
describe('FileTransfer tests', () => { describe('FileTransfer tests', () => {
const { File } = window; const { File } = window;
@ -68,7 +69,7 @@ describe('FileTransfer tests', () => {
}); });
it('Try to send unsupported file', async () => { it('Try to send unsupported file', async () => {
window.alert = jest.fn(); window.alert = vi.fn();
render(<FileTransfer sendEncryptedMessage={() => {}} />); render(<FileTransfer sendEncryptedMessage={() => {}} />);
@ -86,7 +87,7 @@ describe('FileTransfer tests', () => {
}); });
it('Try to send too big file', async () => { it('Try to send too big file', async () => {
window.alert = jest.fn(); window.alert = vi.fn();
render(<FileTransfer sendEncryptedMessage={() => {}} />); render(<FileTransfer sendEncryptedMessage={() => {}} />);

View File

@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`FileTransfer tests FileTransfer component is displaying 1`] = ` exports[`FileTransfer tests > FileTransfer component is displaying 1`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="styles icon file-transfer btn btn-link" class="_styles_374fdd icon file-transfer btn btn-link"
> >
<input <input
id="fileInput" id="fileInput"

View File

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Message from 'components/Message';
import Username from 'components/Username';
import Notice from 'components/Notice';
import Zoom from 'utils/ImageZoom';
import { getObjectUrl } from 'utils/file';
import T from 'components/T'; import Zoom from '@/utils/ImageZoom';
import { getObjectUrl } from '@/utils/file';
import Message from '@/components/Message';
import Username from '@/components/Username';
import Notice from '@/components/Notice';
import T from '@/components/T';
const FileDisplay = ({ activity: { fileType, encodedFile, fileName, username }, scrollToBottom }) => { const FileDisplay = ({ activity: { fileType, encodedFile, fileName, username }, scrollToBottom }) => {
const zoomableImage = React.useRef(null); const zoomableImage = React.useRef(null);

View File

@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import { render, fireEvent } from '@testing-library/react'; import { render, fireEvent } from '@testing-library/react';
import Activity from './Activity'; import { describe, it, expect, vi } from 'vitest';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import configureStore from 'store';
import Activity from './Activity';
import configureStore from '@/store';
const store = configureStore(); const store = configureStore();
//jest.mock('components/T'); // Need store
describe('Activity component', () => { describe('Activity component', () => {
it('should display', () => { it('should display', () => {
const activity = { const activity = {
@ -15,7 +15,7 @@ describe('Activity component', () => {
}; };
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -31,7 +31,7 @@ describe('Activity component', () => {
}; };
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -45,7 +45,7 @@ describe('Activity component', () => {
}; };
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -59,7 +59,7 @@ describe('Activity component', () => {
}; };
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -74,7 +74,7 @@ describe('Activity component', () => {
}; };
const { asFragment, rerender } = render( const { asFragment, rerender } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -84,7 +84,7 @@ describe('Activity component', () => {
rerender( rerender(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -98,7 +98,7 @@ describe('Activity component', () => {
}; };
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -113,7 +113,7 @@ describe('Activity component', () => {
}; };
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -128,7 +128,7 @@ describe('Activity component', () => {
}; };
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -143,11 +143,11 @@ describe('Activity component', () => {
encodedFile: 'dGV4dGZpbGU=', encodedFile: 'dGV4dGZpbGU=',
fileType: 'text/plain', fileType: 'text/plain',
}; };
global.URL.createObjectURL = jest.fn(data => `url:${data}`); global.URL.createObjectURL = vi.fn(data => `url:${data}`);
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );
@ -155,9 +155,9 @@ describe('Activity component', () => {
}); });
it('should display RECEIVE_FILE with image', () => { it('should display RECEIVE_FILE with image', () => {
global.URL.createObjectURL = jest.fn(data => `url:${data}`); global.URL.createObjectURL = vi.fn(data => `url:${data}`);
const mockScrollToBottom = jest.fn(); const mockScrollToBottom = vi.fn();
const activity = { const activity = {
type: 'RECEIVE_FILE', type: 'RECEIVE_FILE',
@ -181,7 +181,7 @@ describe('Activity component', () => {
}); });
it('should display SEND_FILE', () => { it('should display SEND_FILE', () => {
global.URL.createObjectURL = jest.fn(data => `url:${data}`); global.URL.createObjectURL = vi.fn(data => `url:${data}`);
const activity = { const activity = {
type: 'SEND_FILE', type: 'SEND_FILE',
username: 'alice', username: 'alice',
@ -192,7 +192,7 @@ describe('Activity component', () => {
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<Activity activity={activity} scrollToBottom={jest.fn()} /> <Activity activity={activity} scrollToBottom={vi.fn()} />
</Provider>, </Provider>,
); );

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ChatInput from 'components/Chat';
import Activity from './Activity';
import T from 'components/T';
import { defer } from 'lodash'; import { defer } from 'lodash';
import ChatInput from '@/components/Chat';
import T from '@/components/T';
import Activity from './Activity';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
const ActivityList = ({ activities, openModal }) => { const ActivityList = ({ activities, openModal }) => {

View File

@ -1,18 +1,21 @@
import React from 'react'; import React from 'react';
import { render, fireEvent } from '@testing-library/react'; import { render, fireEvent } from '@testing-library/react';
import ActivityList from './ActivityList';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import configureStore from 'store'; import { describe, it, expect, vi } from 'vitest';
import configureStore from '@/store';
import ActivityList from './ActivityList';
const store = configureStore(); const store = configureStore();
jest.useFakeTimers(); vi.useFakeTimers();
describe('ActivityList component', () => { describe('ActivityList component', () => {
it('should display', () => { it('should display', () => {
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<ActivityList openModal={jest.fn()} activities={[]} /> <ActivityList openModal={vi.fn()} activities={[]} />
</Provider>, </Provider>,
); );
@ -39,7 +42,7 @@ describe('ActivityList component', () => {
]; ];
const { asFragment } = render( const { asFragment } = render(
<Provider store={store}> <Provider store={store}>
<ActivityList openModal={jest.fn()} activities={activities} /> <ActivityList openModal={vi.fn()} activities={activities} />
</Provider>, </Provider>,
); );
@ -47,7 +50,7 @@ describe('ActivityList component', () => {
}); });
it('should show About modal', async () => { it('should show About modal', async () => {
const mockOpenModal = jest.fn(); const mockOpenModal = vi.fn();
const { getByText } = render( const { getByText } = render(
<Provider store={store}> <Provider store={store}>
@ -56,43 +59,40 @@ describe('ActivityList component', () => {
); );
fireEvent.click(getByText('By using Darkwire, you are agreeing to our Acceptable Use Policy and Terms of Service')); fireEvent.click(getByText('By using Darkwire, you are agreeing to our Acceptable Use Policy and Terms of Service'));
jest.runAllTimers(); vi.runAllTimers();
expect(mockOpenModal.mock.calls[0][0]).toBe('About'); expect(mockOpenModal.mock.calls[0][0]).toBe('About');
jest.runAllTimers(); vi.runAllTimers();
}); });
it('should focus chat', () => { it('should focus chat', () => {
const { getByTestId } = render( const { getByTestId } = render(
<Provider store={store}> <Provider store={store}>
<ActivityList openModal={jest.fn()} activities={[]} /> <ActivityList openModal={vi.fn()} activities={[]} />
</Provider>, </Provider>,
); );
fireEvent.click(getByTestId('main-div')); fireEvent.click(getByTestId('main-div'));
jest.runAllTimers(); vi.runAllTimers();
}); });
it('should scroll to bottom on new message if not scrolled', () => { it('should scroll to bottom on new message if not scrolled', () => {
jest.spyOn(Element.prototype, 'clientHeight', 'get').mockReturnValueOnce(400).mockReturnValueOnce(200); vi.spyOn(Element.prototype, 'clientHeight', 'get').mockReturnValueOnce(400).mockReturnValueOnce(200);
Element.prototype.getBoundingClientRect = jest Element.prototype.getBoundingClientRect = vi.fn().mockReturnValueOnce({ top: 0 }).mockReturnValueOnce({ top: 261 });
.fn()
.mockReturnValueOnce({ top: 0 })
.mockReturnValueOnce({ top: 261 });
jest.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(42); vi.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(42);
const mockScrollTop = jest.spyOn(Element.prototype, 'scrollTop', 'set'); const mockScrollTop = vi.spyOn(Element.prototype, 'scrollTop', 'set');
const { rerender, getByTestId } = render( const { rerender, getByTestId } = render(
<Provider store={store}> <Provider store={store}>
<ActivityList openModal={jest.fn()} activities={[]} /> <ActivityList openModal={vi.fn()} activities={[]} />
</Provider>, </Provider>,
); );
rerender( rerender(
<Provider store={store}> <Provider store={store}>
<ActivityList <ActivityList
openModal={jest.fn()} openModal={vi.fn()}
activities={[ activities={[
{ {
type: 'TEXT_MESSAGE', type: 'TEXT_MESSAGE',
@ -105,7 +105,7 @@ describe('ActivityList component', () => {
</Provider>, </Provider>,
); );
jest.runAllTimers(); vi.runAllTimers();
expect(mockScrollTop).toHaveBeenCalledTimes(2); expect(mockScrollTop).toHaveBeenCalledTimes(2);
expect(mockScrollTop).toHaveBeenLastCalledWith(42); expect(mockScrollTop).toHaveBeenLastCalledWith(42);
@ -115,7 +115,7 @@ describe('ActivityList component', () => {
rerender( rerender(
<Provider store={store}> <Provider store={store}>
<ActivityList <ActivityList
openModal={jest.fn()} openModal={vi.fn()}
activities={[ activities={[
{ {
type: 'TEXT_MESSAGE', type: 'TEXT_MESSAGE',

View File

@ -1,17 +1,20 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Crypto from 'utils/crypto';
import { connect as connectSocket } from 'utils/socket';
import Nav from 'components/Nav';
import shortId from 'shortid';
import Connecting from 'components/Connecting';
import Modal from 'react-modal'; import Modal from 'react-modal';
import About from 'components/About'; import PropTypes from 'prop-types';
import Settings from 'components/Settings'; import { nanoid } from 'nanoid';
import Welcome from 'components/Welcome';
import RoomLocked from 'components/RoomLocked';
import { X, AlertCircle } from 'react-feather'; import { X, AlertCircle } from 'react-feather';
import classNames from 'classnames'; import classNames from 'classnames';
import Crypto from '@/utils/crypto';
import { connect as connectSocket } from '@/utils/socket';
import Nav from '@/components/Nav';
import Connecting from '@/components/Connecting';
import About from '@/components/About';
import Settings from '@/components/Settings';
import Welcome from '@/components/Welcome';
import RoomLocked from '@/components/RoomLocked';
import ActivityList from './ActivityList'; import ActivityList from './ActivityList';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
@ -22,11 +25,9 @@ Modal.setAppElement('#root');
class Home extends Component { class Home extends Component {
async componentWillMount() { async componentWillMount() {
const roomId = encodeURI(this.props.match.params.roomId);
const user = await this.createUser(); const user = await this.createUser();
const socket = connectSocket(roomId); const socket = connectSocket(this.props.socketId);
this.socket = socket; this.socket = socket;
@ -150,7 +151,7 @@ class Home extends Component {
createUser() { createUser() {
return new Promise(async resolve => { return new Promise(async resolve => {
const username = shortId.generate(); const username = nanoid();
const encryptDecryptKeys = await crypto.createEncryptDecryptKeys(); const encryptDecryptKeys = await crypto.createEncryptDecryptKeys();
const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey); const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey);
@ -237,7 +238,7 @@ Home.propTypes = {
username: PropTypes.string.isRequired, username: PropTypes.string.isRequired,
publicKey: PropTypes.object.isRequired, publicKey: PropTypes.object.isRequired,
members: PropTypes.array.isRequired, members: PropTypes.array.isRequired,
match: PropTypes.object.isRequired, socketId: PropTypes.object.isRequired,
roomId: PropTypes.string.isRequired, roomId: PropTypes.string.isRequired,
roomLocked: PropTypes.bool.isRequired, roomLocked: PropTypes.bool.isRequired,
modalComponent: PropTypes.string, modalComponent: PropTypes.string,

View File

@ -1,39 +1,47 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react';
import Home from './Home';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import configureStore from 'store'; import { render } from '@testing-library/react';
import { test, expect, vi } from 'vitest';
import Home from './Home';
import configureStore from '@/store';
const store = configureStore(); const store = configureStore();
jest.mock('react-modal'); // Cant load modal without root app element vi.mock('react-modal'); // Cant load modal without root app element
jest.mock('utils/socket', () => {
vi.mock('@/RoomLink');
vi.mock('@/components/Nav');
vi.mock('@/utils/socket', () => {
// Avoid exception // Avoid exception
return { return {
connect: jest.fn().mockImplementation(() => { connect: vi.fn().mockImplementation(() => {
return { return {
on: jest.fn(), on: vi.fn(),
emit: jest.fn(), emit: vi.fn(),
}; };
}), }),
}; };
}); // }); //
jest.mock('utils/crypto', () => { vi.mock('../../utils/crypto', () => {
// Need window.crytpo.subtle // Need window.crytpo.subtle
return jest.fn().mockImplementation(() => { return {
return { default: vi.fn().mockImplementation(() => {
createEncryptDecryptKeys: () => { return {
return { createEncryptDecryptKeys: () => {
privateKey: 'private', return {
publicKey: 'public', privateKey: 'private',
}; publicKey: 'public',
}, };
exportKey: () => { },
return 'exportedkey'; exportKey: () => {
}, return 'exportedkey';
}; },
}); };
}),
};
}); });
test('Home component is displaying', async () => { test('Home component is displaying', async () => {

View File

@ -1,8 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { notify, beep } from 'utils/notifications';
import Tinycon from 'tinycon'; import Tinycon from 'tinycon';
import { toggleNotificationAllowed, toggleNotificationEnabled } from 'actions';
import { notify, beep } from '@/utils/notifications';
import { toggleNotificationAllowed, toggleNotificationEnabled } from '@/actions';
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {

View File

@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Activity component should display 1`] = `<DocumentFragment />`; exports[`Activity component > should display 1`] = `<DocumentFragment />`;
exports[`Activity component should display CHANGE_USERNAME 1`] = ` exports[`Activity component > should display CHANGE_USERNAME 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -30,7 +30,7 @@ exports[`Activity component should display CHANGE_USERNAME 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display NOTICE 1`] = ` exports[`Activity component > should display NOTICE 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -44,7 +44,7 @@ exports[`Activity component should display NOTICE 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display RECEIVE_FILE 1`] = ` exports[`Activity component > should display RECEIVE_FILE 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<span> <span>
@ -71,7 +71,7 @@ exports[`Activity component should display RECEIVE_FILE 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display RECEIVE_FILE with image 1`] = ` exports[`Activity component > should display RECEIVE_FILE with image 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<span> <span>
@ -103,7 +103,7 @@ exports[`Activity component should display RECEIVE_FILE with image 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display SEND_FILE 1`] = ` exports[`Activity component > should display SEND_FILE 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -128,7 +128,7 @@ exports[`Activity component should display SEND_FILE 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display TEXT_MESSAGE 1`] = ` exports[`Activity component > should display TEXT_MESSAGE 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -149,17 +149,13 @@ exports[`Activity component should display TEXT_MESSAGE 1`] = `
<div <div
class="chat" class="chat"
> >
<span Hi!
class="Linkify"
>
Hi!
</span>
</div> </div>
</div> </div>
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display TOGGLE_LOCK_ROOM 1`] = ` exports[`Activity component > should display TOGGLE_LOCK_ROOM 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -181,7 +177,7 @@ exports[`Activity component should display TOGGLE_LOCK_ROOM 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display TOGGLE_LOCK_ROOM 2`] = ` exports[`Activity component > should display TOGGLE_LOCK_ROOM 2`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -203,7 +199,7 @@ exports[`Activity component should display TOGGLE_LOCK_ROOM 2`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display USER_ACTION 1`] = ` exports[`Activity component > should display USER_ACTION 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -224,7 +220,7 @@ exports[`Activity component should display USER_ACTION 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display USER_ENTER 1`] = ` exports[`Activity component > should display USER_ENTER 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div
@ -246,7 +242,7 @@ exports[`Activity component should display USER_ENTER 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Activity component should display USER_EXIT 1`] = ` exports[`Activity component > should display USER_EXIT 1`] = `
<DocumentFragment> <DocumentFragment>
<div> <div>
<div <div

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`ActivityList component should display 1`] = ` exports[`ActivityList component > should display 1`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="main-chat" class="main-chat"
@ -14,7 +14,7 @@ exports[`ActivityList component should display 1`] = `
> >
<li> <li>
<p <p
class="tos" class="_tos_0b54d3"
> >
<button <button
class="btn btn-link" class="btn btn-link"
@ -40,7 +40,7 @@ exports[`ActivityList component should display 1`] = `
class="input-controls" class="input-controls"
> >
<div <div
class="styles icon file-transfer btn btn-link" class="_styles_374fdd icon file-transfer btn btn-link"
> >
<input <input
id="fileInput" id="fileInput"
@ -78,7 +78,7 @@ exports[`ActivityList component should display 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`ActivityList component should display with activities 1`] = ` exports[`ActivityList component > should display with activities 1`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="main-chat" class="main-chat"
@ -92,7 +92,7 @@ exports[`ActivityList component should display with activities 1`] = `
> >
<li> <li>
<p <p
class="tos" class="_tos_0b54d3"
> >
<button <button
class="btn btn-link" class="btn btn-link"
@ -123,11 +123,7 @@ exports[`ActivityList component should display with activities 1`] = `
<div <div
class="chat" class="chat"
> >
<span Hi!
class="Linkify"
>
Hi!
</span>
</div> </div>
</div> </div>
</li> </li>
@ -196,7 +192,7 @@ exports[`ActivityList component should display with activities 1`] = `
class="input-controls" class="input-controls"
> >
<div <div
class="styles icon file-transfer btn btn-link" class="_styles_374fdd icon file-transfer btn btn-link"
> >
<input <input
id="fileInput" id="fileInput"

View File

@ -1,365 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Home component is displaying 1`] = `
<DocumentFragment>
<div
class="styles h-100"
>
<div
class="nav-container"
>
<div
class="alert-banner"
>
<span
class="icon"
>
<svg
fill="none"
height="15"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="15"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<line
x1="12"
x2="12"
y1="8"
y2="12"
/>
<line
x1="12"
x2="12"
y1="16"
y2="16"
/>
</svg>
</span>
<span>
Disconnected
</span>
</div>
<nav
class="navbar navbar-expand-md navbar-dark"
>
<div
class="meta"
>
<img
alt="Darkwire"
class="logo"
src="logo.png"
/>
<button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost/testId"
data-placement="bottom"
data-toggle="tooltip"
>
/testId
</button>
<span
class="lock-room-container"
>
<button
class="lock-room btn btn-link btn-plain"
data-placement="bottom"
data-toggle="tooltip"
title="You must be the owner to lock or unlock the room"
>
<svg
class="muted"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<rect
height="11"
rx="2"
ry="2"
width="18"
x="3"
y="11"
/>
<path
d="M7 11V7a5 5 0 0 1 9.9-1"
/>
</svg>
</button>
</span>
<div
class="dropdown members-dropdown"
>
<a
class="dropdown__trigger "
>
<button
class="btn btn-link btn-plain members-action"
title="Users"
>
<svg
class="users-icon"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"
/>
<circle
cx="9"
cy="7"
r="4"
/>
<path
d="M23 21v-2a4 4 0 0 0-3-3.87"
/>
<path
d="M16 3.13a4 4 0 0 1 0 7.75"
/>
</svg>
</button>
<span>
0
</span>
</a>
<div
class="dropdown__content "
>
<ul
class="plain"
/>
</div>
</div>
</div>
<button
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
class="navbar-toggler"
data-target="#navbarSupportedContent"
data-toggle="collapse"
type="button"
>
<span
class="navbar-toggler-icon"
/>
</button>
<div
class="collapse navbar-collapse"
id="navbarSupportedContent"
>
<ul
class="navbar-nav ml-auto"
>
<li
class="nav-item"
>
<button
class="btn btn-plain nav-link"
target="blank"
>
<svg
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<line
x1="12"
x2="12"
y1="8"
y2="16"
/>
<line
x1="8"
x2="16"
y1="12"
y2="12"
/>
</svg>
<span />
</button>
</li>
<li
class=" nav-item"
>
<button
class="btn btn-plain nav-link"
>
<svg
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="3"
/>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
<span />
</button>
</li>
<li
class="nav-item"
>
<button
class="btn btn-plain nav-link"
>
<svg
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<line
x1="12"
x2="12"
y1="16"
y2="12"
/>
<line
x1="12"
x2="12"
y1="8"
y2="8"
/>
</svg>
<span />
</button>
</li>
</ul>
</div>
</nav>
</div>
<div
class="main-chat"
>
<div
class="message-stream h-100"
data-testid="main-div"
>
<ul
class="plain"
>
<li>
<p
class="tos"
>
<button
class="btn btn-link"
>
By using Darkwire, you are agreeing to our Acceptable Use Policy and Terms of Service
</button>
</p>
</li>
</ul>
</div>
<div
class="chat-container"
>
<form
class="chat-preflight-container"
>
<textarea
class="chat"
placeholder="Type here"
rows="1"
/>
<div
class="input-controls"
>
<div
class="styles icon file-transfer btn btn-link"
>
<input
id="fileInput"
name="fileUploader"
placeholder="Choose a file..."
type="file"
/>
<label
for="fileInput"
>
<svg
fill="none"
height="24"
stroke="#fff"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"
/>
<polyline
points="13 2 13 9 20 9"
/>
</svg>
</label>
</div>
</div>
</form>
</div>
</div>
</div>
</DocumentFragment>
`;

View File

@ -0,0 +1,128 @@
// Vitest Snapshot v1
exports[`Home component is displaying 1`] = `
<DocumentFragment>
<div
class="_styles_0b54d3 h-100"
>
<div
class="nav-container"
>
<div
class="alert-banner"
>
<span
class="icon"
>
<svg
fill="none"
height="15"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="15"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<line
x1="12"
x2="12"
y1="8"
y2="12"
/>
<line
x1="12"
x2="12.01"
y1="16"
y2="16"
/>
</svg>
</span>
<span>
Disconnected
</span>
</div>
</div>
<div
class="main-chat"
>
<div
class="message-stream h-100"
data-testid="main-div"
>
<ul
class="plain"
>
<li>
<p
class="_tos_0b54d3"
>
<button
class="btn btn-link"
>
By using Darkwire, you are agreeing to our Acceptable Use Policy and Terms of Service
</button>
</p>
</li>
</ul>
</div>
<div
class="chat-container"
>
<form
class="chat-preflight-container"
>
<textarea
class="chat"
placeholder="Type here"
rows="1"
/>
<div
class="input-controls"
>
<div
class="_styles_374fdd icon file-transfer btn btn-link"
>
<input
id="fileInput"
name="fileUploader"
placeholder="Choose a file..."
type="file"
/>
<label
for="fileInput"
>
<svg
fill="none"
height="24"
stroke="#fff"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"
/>
<polyline
points="13 2 13 9 20 9"
/>
</svg>
</label>
</div>
</div>
</form>
</div>
</div>
</div>
</DocumentFragment>
`;

View File

@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Connected Home component should display 1`] = ` exports[`Connected Home component > should display 1`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="styles h-100" class="_styles_0b54d3 h-100"
> >
<div <div
class="nav-container" class="nav-container"
@ -38,7 +38,7 @@ exports[`Connected Home component should display 1`] = `
/> />
<line <line
x1="12" x1="12"
x2="12" x2="12.01"
y1="16" y1="16"
y2="16" y2="16"
/> />
@ -58,11 +58,11 @@ exports[`Connected Home component should display 1`] = `
<img <img
alt="Darkwire" alt="Darkwire"
class="logo" class="logo"
src="logo.png" src="/src/img/logo.png"
/> />
<button <button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis" class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost/" data-clipboard-text="http://localhost:3000/"
data-placement="bottom" data-placement="bottom"
data-toggle="tooltip" data-toggle="tooltip"
title="Copied" title="Copied"
@ -218,7 +218,7 @@ exports[`Connected Home component should display 1`] = `
</button> </button>
</li> </li>
<li <li
class=" nav-item" class="nav-item"
> >
<button <button
class="btn btn-plain nav-link" class="btn btn-plain nav-link"
@ -279,7 +279,7 @@ exports[`Connected Home component should display 1`] = `
/> />
<line <line
x1="12" x1="12"
x2="12" x2="12.01"
y1="8" y1="8"
y2="8" y2="8"
/> />

View File

@ -1,5 +1,6 @@
import Home from './Home';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { useLoaderData } from 'react-router-dom';
import { import {
receiveEncryptedMessage, receiveEncryptedMessage,
createUser, createUser,
@ -14,7 +15,9 @@ import {
sendUnencryptedMessage, sendUnencryptedMessage,
sendEncryptedMessage, sendEncryptedMessage,
setLanguage, setLanguage,
} from 'actions'; } from '@/actions';
import Home from './Home';
import WithNewMessageNotification from './WithNewMessageNotification'; import WithNewMessageNotification from './WithNewMessageNotification';
const mapStateToProps = state => { const mapStateToProps = state => {
@ -57,4 +60,11 @@ const mapDispatchToProps = {
setLanguage, setLanguage,
}; };
export default WithNewMessageNotification(connect(mapStateToProps, mapDispatchToProps)(Home)); export const ConnectedHome = WithNewMessageNotification(connect(mapStateToProps, mapDispatchToProps)(Home));
const HomeWithParams = ({ ...props }) => {
const socketId = useLoaderData();
return <ConnectedHome socketId={socketId} {...props} />;
};
export default HomeWithParams;

View File

@ -1,88 +1,93 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import ConnectedHome from '.';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import configureStore from 'store';
import { toggleWindowFocus, toggleNotificationEnabled, toggleSoundEnabled } from 'actions/app';
import { receiveEncryptedMessage } from 'actions/encrypted_messages';
import { notify, beep } from 'utils/notifications';
import Tinycon from 'tinycon'; import Tinycon from 'tinycon';
import Modal from 'react-modal'; import Modal from 'react-modal';
import { expect, vi, describe, beforeEach, afterEach, it } from 'vitest';
import configureStore from '@/store';
import { toggleWindowFocus, toggleNotificationEnabled, toggleSoundEnabled } from '@/actions/app';
import { receiveEncryptedMessage } from '@/actions/encrypted_messages';
import { notify, beep } from '@/utils/notifications';
import { ConnectedHome } from './';
const store = configureStore(); const store = configureStore();
jest.useFakeTimers(); vi.useFakeTimers();
// We don't test activity list here vi.mock('react-modal'); // Cant load modal without root app element
jest.mock('./ActivityList', () => {
return jest.fn().mockReturnValue(null);
});
jest.mock('react-modal'); // Cant load modal without root app element vi.mock('nanoid', () => {
jest.mock('utils/socket', () => {
// Avoid exception // Avoid exception
return { return {
connect: jest.fn().mockImplementation(() => { nanoid: vi.fn().mockImplementation(() => {
return {
on: jest.fn(),
emit: jest.fn(),
};
}),
getSocket: jest.fn().mockImplementation(() => {
return {
on: jest.fn(),
emit: jest.fn(),
};
}),
};
});
jest.mock('shortid', () => {
// Avoid exception
return {
generate: jest.fn().mockImplementation(() => {
return 'shortidgenerated'; return 'shortidgenerated';
}), }),
}; };
}); });
jest.mock('utils/crypto', () => { vi.mock('tinycon', () => {
// Need window.crytpo.subtle return {
return jest.fn().mockImplementation(() => { default: { setBubble: vi.fn() },
return { };
createEncryptDecryptKeys: () => {
return {
privateKey: { n: 'private' },
publicKey: { n: 'public' },
};
},
exportKey: () => {
return { n: 'exportedKey' };
},
};
});
}); });
jest.mock('utils/message', () => { // We don't test activity list here
vi.mock('./ActivityList', () => {
return { default: vi.fn().mockReturnValue(null) };
});
vi.mock('@/utils/socket', () => {
// Avoid exception
return { return {
process: jest.fn(async (payload, state) => ({ connect: vi.fn().mockImplementation(() => {
return {
on: vi.fn(),
emit: vi.fn(),
};
}),
getSocket: vi.fn().mockImplementation(() => {
return {
on: vi.fn(),
emit: vi.fn(),
};
}),
};
});
vi.mock('@/utils/crypto', () => {
// Need window.crytpo.subtle
return {
default: vi.fn().mockImplementation(() => {
return {
createEncryptDecryptKeys: () => {
return {
privateKey: { n: 'private' },
publicKey: { n: 'public' },
};
},
exportKey: () => {
return { n: 'exportedKey' };
},
};
}),
};
});
vi.mock('@/utils/message', () => {
return {
process: vi.fn(async (payload, state) => ({
...payload, ...payload,
payload: { payload: 'text', username: 'sender', text: 'new message' }, payload: { payload: 'text', username: 'sender', text: 'new message' },
})), })),
}; };
}); });
jest.mock('utils/notifications', () => { vi.mock('@/utils/notifications', () => {
return { return {
notify: jest.fn(), notify: vi.fn(),
beep: { play: jest.fn() }, beep: { play: vi.fn() },
};
});
jest.mock('tinycon', () => {
return {
setBubble: jest.fn(),
}; };
}); });
@ -143,7 +148,7 @@ describe('Connected Home component', () => {
}); });
it('should send notifications', async () => { it('should send notifications', async () => {
Modal.prototype.getSnapshotBeforeUpdate = jest.fn().mockReturnValue(null); Modal.prototype.getSnapshotBeforeUpdate = vi.fn().mockReturnValue(null);
const { rerender } = render( const { rerender } = render(
<Provider store={store}> <Provider store={store}>
<ConnectedHome match={{ params: { roomId: 'roomTest' } }} userId="testUserId" roomId={'testId'} /> <ConnectedHome match={{ params: { roomId: 'roomTest' } }} userId="testUserId" roomId={'testId'} />

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import Message from '.'; import Message from '.';
import { test, expect } from 'vitest';
test('Message component is displaying', async () => { test('Message component is displaying', async () => {
const { asFragment } = render(<Message sender={'linus'} timestamp={1588794269074} message={'we come in peace'} />); const { asFragment } = render(<Message sender={'linus'} timestamp={1588794269074} message={'we come in peace'} />);

View File

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Message component is displaying 1`] = ` exports[`Message component is displaying 1`] = `
<DocumentFragment> <DocumentFragment>
@ -21,11 +21,7 @@ exports[`Message component is displaying 1`] = `
<div <div
class="chat" class="chat"
> >
<span we come in peace
class="Linkify"
>
we come in peace
</span>
</div> </div>
</div> </div>
</DocumentFragment> </DocumentFragment>

View File

@ -1,9 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Username from 'components/Username';
import moment from 'moment'; import moment from 'moment';
import Linkify from 'react-linkify'; import Linkify from 'react-linkify';
import Username from '@/components/Username';
class Message extends Component { class Message extends Component {
render() { render() {
const msg = decodeURI(this.props.message); const msg = decodeURI(this.props.message);

View File

@ -1,38 +1,41 @@
import React from 'react'; import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react'; import { render, fireEvent, waitFor } from '@testing-library/react';
import Nav from '.';
import mock$ from 'jquery'; import mock$ from 'jquery';
import { test, expect, vi } from 'vitest';
const mockTooltip = jest.fn().mockImplementation(param => { import Nav from '.';
const mockTooltip = vi.fn().mockImplementation(param => {
// console.log('tooltip', param); // console.log('tooltip', param);
}); });
const mockCollapse = jest.fn().mockImplementation(param => { const mockCollapse = vi.fn().mockImplementation(param => {
// console.log('collapse', param); // console.log('collapse', param);
}); });
jest.mock('jquery', () => { vi.mock('jquery', () => {
return jest.fn().mockImplementation(param => { return {
// console.log('$', param); default: vi.fn().mockImplementation(param => {
if (typeof param === 'function') { if (typeof param === 'function') {
param(); param();
} }
return { return {
tooltip: mockTooltip, tooltip: mockTooltip,
collapse: mockCollapse, collapse: mockCollapse,
}; };
}); }),
};
}); });
jest.mock('shortid', () => { vi.mock('nanoid', () => {
return { return {
generate() { nanoid: () => {
return 'fakeid'; return 'fakeid';
}, },
}; };
}); });
jest.useFakeTimers(); vi.useFakeTimers();
const mockTranslations = { const mockTranslations = {
newRoomButton: 'new room', newRoomButton: 'new room',
@ -101,9 +104,9 @@ test('Nav component is displaying with another configuration and can rerender',
}); });
test('Can copy room url', async () => { test('Can copy room url', async () => {
document.execCommand = jest.fn(() => true); document.execCommand = vi.fn(() => true);
const toggleLockRoom = jest.fn(); const toggleLockRoom = vi.fn();
const { getByText } = render( const { getByText } = render(
<Nav <Nav
@ -128,14 +131,14 @@ test('Can copy room url', async () => {
expect(mockTooltip).toHaveBeenLastCalledWith('show'); expect(mockTooltip).toHaveBeenLastCalledWith('show');
// Wait tooltip closing // Wait tooltip closing
jest.runAllTimers(); vi.runAllTimers();
expect(mock$).toHaveBeenCalledTimes(18); expect(mock$).toHaveBeenCalledTimes(18);
expect(mockTooltip).toHaveBeenLastCalledWith('hide'); expect(mockTooltip).toHaveBeenLastCalledWith('hide');
}); });
test('Can lock/unlock room is room owner only', async () => { test('Can lock/unlock room is room owner only', async () => {
const toggleLockRoom = jest.fn(); const toggleLockRoom = vi.fn();
const { rerender, getByTitle } = render( const { rerender, getByTitle } = render(
<Nav <Nav
@ -231,7 +234,7 @@ test('Can show user list', async () => {
}); });
test('Can open settings', async () => { test('Can open settings', async () => {
const openModal = jest.fn(); const openModal = vi.fn();
// Test with one user owner and me // Test with one user owner and me
const { getByText } = render( const { getByText } = render(
@ -255,7 +258,7 @@ test('Can open settings', async () => {
}); });
test('Can open About', async () => { test('Can open About', async () => {
const openModal = jest.fn(); const openModal = vi.fn();
// Test with one user owner and me // Test with one user owner and me
const { getByText } = render( const { getByText } = render(
@ -279,7 +282,7 @@ test('Can open About', async () => {
}); });
test('Can open About', async () => { test('Can open About', async () => {
window.open = jest.fn(); window.open = vi.fn();
// Test with one user owner and me // Test with one user owner and me
const { getByText } = render( const { getByText } = render(

View File

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Nav component is displaying 1`] = ` exports[`Nav component is displaying 1`] = `
<DocumentFragment> <DocumentFragment>
@ -11,11 +11,11 @@ exports[`Nav component is displaying 1`] = `
<img <img
alt="Darkwire" alt="Darkwire"
class="logo" class="logo"
src="logo.png" src="/src/img/logo.png"
/> />
<button <button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis" class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost/testRoom" data-clipboard-text="http://localhost:3000/testRoom"
data-placement="bottom" data-placement="bottom"
data-toggle="tooltip" data-toggle="tooltip"
> >
@ -168,7 +168,7 @@ exports[`Nav component is displaying 1`] = `
</button> </button>
</li> </li>
<li <li
class=" nav-item" class="nav-item"
> >
<button <button
class="btn btn-plain nav-link" class="btn btn-plain nav-link"
@ -227,7 +227,7 @@ exports[`Nav component is displaying 1`] = `
/> />
<line <line
x1="12" x1="12"
x2="12" x2="12.01"
y1="8" y1="8"
y2="8" y2="8"
/> />
@ -253,11 +253,11 @@ exports[`Nav component is displaying with another configuration and can rerender
<img <img
alt="Darkwire" alt="Darkwire"
class="logo" class="logo"
src="logo.png" src="/src/img/logo.png"
/> />
<button <button
class="btn btn-plain btn-link clipboard-trigger room-id ellipsis" class="btn btn-plain btn-link clipboard-trigger room-id ellipsis"
data-clipboard-text="http://localhost/testRoom_2" data-clipboard-text="http://localhost:3000/testRoom_2"
data-placement="bottom" data-placement="bottom"
data-toggle="tooltip" data-toggle="tooltip"
> >
@ -456,7 +456,7 @@ exports[`Nav component is displaying with another configuration and can rerender
</button> </button>
</li> </li>
<li <li
class=" nav-item" class="nav-item"
> >
<button <button
class="btn btn-plain nav-link" class="btn btn-plain nav-link"
@ -515,7 +515,7 @@ exports[`Nav component is displaying with another configuration and can rerender
/> />
<line <line
x1="12" x1="12"
x2="12" x2="12.01"
y1="8" y1="8"
y2="8" y2="8"
/> />

View File

@ -1,13 +1,14 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import shortId from 'shortid'; import { nanoid } from 'nanoid';
import { Info, Settings, PlusCircle, User, Users, Lock, Unlock, Star } from 'react-feather'; 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 Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
import Username from 'components/Username';
import Clipboard from 'clipboard'; import Clipboard from 'clipboard';
import $ from 'jquery'; import $ from 'jquery';
import logoImg from '@/img/logo.png';
import Username from '@/components/Username';
class Nav extends Component { class Nav extends Component {
componentDidMount() { componentDidMount() {
const clip = new Clipboard('.clipboard-trigger'); const clip = new Clipboard('.clipboard-trigger');
@ -38,7 +39,7 @@ class Nav extends Component {
newRoom() { newRoom() {
$('.navbar-collapse').collapse('hide'); $('.navbar-collapse').collapse('hide');
window.open(`/${shortId.generate()}`); window.open(`/${nanoid()}`);
} }
handleSettingsClick() { handleSettingsClick() {
@ -138,10 +139,7 @@ class Nav extends Component {
<PlusCircle /> <span>{this.props.translations.newRoomButton}</span> <PlusCircle /> <span>{this.props.translations.newRoomButton}</span>
</button> </button>
</li> </li>
<li <li className="nav-item">
className="
nav-item"
>
<button onClick={this.handleSettingsClick.bind(this)} className="btn btn-plain nav-link"> <button onClick={this.handleSettingsClick.bind(this)} className="btn btn-plain nav-link">
<Settings /> <span>{this.props.translations.settingsButton}</span> <Settings /> <span>{this.props.translations.settingsButton}</span>
</button> </button>

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import Notice from '.'; import Notice from '.';
import { test, expect } from 'vitest';
test('Notice component is displaying', async () => { test('Notice component is displaying', async () => {
const { asFragment } = render( const { asFragment } = render(

View File

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Notice component is displaying 1`] = ` exports[`Notice component is displaying 1`] = `
<DocumentFragment> <DocumentFragment>

View File

@ -2,24 +2,24 @@ import React from 'react';
import { render, fireEvent } from '@testing-library/react'; import { render, fireEvent } from '@testing-library/react';
import RoomLink from '.'; import RoomLink from '.';
import mock$ from 'jquery'; import mock$ from 'jquery';
import { describe, it, expect, vi, afterEach } from 'vitest';
const mockTooltip = jest.fn().mockImplementation(param => { const mockTooltip = vi.fn().mockImplementation(param => {});
// console.log('tooltip', param);
vi.mock('jquery', () => {
return {
default: vi.fn().mockImplementation(param => {
if (typeof param === 'function') {
param();
}
return {
tooltip: mockTooltip,
};
}),
};
}); });
jest.mock('jquery', () => { vi.useFakeTimers();
return jest.fn().mockImplementation(param => {
// console.log('$', param);
if (typeof param === 'function') {
param();
}
return {
tooltip: mockTooltip,
};
});
});
jest.useFakeTimers();
const mockTranslations = { const mockTranslations = {
copyButtonTooltip: 'copyButton', copyButtonTooltip: 'copyButton',
@ -47,11 +47,11 @@ describe('RoomLink', () => {
it('should copy link', async () => { it('should copy link', async () => {
// Mock execCommand for paste // Mock execCommand for paste
document.execCommand = jest.fn(() => true); document.execCommand = vi.fn(() => true);
const { getByTitle } = render(<RoomLink roomId="roomId" translations={mockTranslations} />); const { getByTitle } = render(<RoomLink roomId="roomId" translations={mockTranslations} />);
fireEvent.click(getByTitle(mockTranslations.copyButtonTooltip)); await fireEvent.click(getByTitle(mockTranslations.copyButtonTooltip));
expect(document.execCommand).toHaveBeenLastCalledWith('copy'); expect(document.execCommand).toHaveBeenLastCalledWith('copy');
expect(mock$).toHaveBeenCalledTimes(4); expect(mock$).toHaveBeenCalledTimes(4);
@ -59,7 +59,7 @@ describe('RoomLink', () => {
expect(mockTooltip).toHaveBeenLastCalledWith('show'); expect(mockTooltip).toHaveBeenLastCalledWith('show');
// Wait for tooltip to close // Wait for tooltip to close
jest.runAllTimers(); vi.runAllTimers();
expect(mock$).toHaveBeenCalledTimes(6); expect(mock$).toHaveBeenCalledTimes(6);
expect(mock$).toHaveBeenLastCalledWith('.copy-room'); expect(mock$).toHaveBeenLastCalledWith('.copy-room');

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`RoomLink should display 1`] = ` exports[`RoomLink > should display 1`] = `
<DocumentFragment> <DocumentFragment>
<form> <form>
<div <div
@ -14,14 +14,14 @@ exports[`RoomLink should display 1`] = `
id="room-url" id="room-url"
readonly="" readonly=""
type="text" type="text"
value="http://localhost/roomId" value="http://localhost:3000/roomId"
/> />
<div <div
class="input-group-append" class="input-group-append"
> >
<button <button
class="copy-room btn btn-secondary" class="copy-room btn btn-secondary"
data-clipboard-text="http://localhost/roomId" data-clipboard-text="http://localhost:3000/roomId"
data-placement="bottom" data-placement="bottom"
data-toggle="tooltip" data-toggle="tooltip"
title="copyButton" title="copyButton"

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import RoomLocked from '.'; import RoomLocked from '.';
import { test, expect } from 'vitest';
test('RoomLocked component should display', () => { test('RoomLocked component should display', () => {
const { asFragment } = render(<RoomLocked modalContent={'test'} />); const { asFragment } = render(<RoomLocked modalContent={'test'} />);

View File

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`RoomLocked component should display 1`] = ` exports[`RoomLocked component should display 1`] = `
<DocumentFragment> <DocumentFragment>

View File

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react'; import { render, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import configureStore from 'store'; import { describe, it, expect, vi } from 'vitest';
import configureStore from '@/store';
import Settings from '.'; import Settings from '.';
@ -11,9 +13,24 @@ const mockTranslations = {
sound: 'soundCheck', sound: 'soundCheck',
}; };
jest.useFakeTimers(); vi.useFakeTimers();
jest.mock('components/RoomLink'); vi.mock('@/components/RoomLink');
const mockTooltip = vi.fn().mockImplementation(param => {});
vi.mock('jquery', () => {
return {
default: vi.fn().mockImplementation(param => {
if (typeof param === 'function') {
param();
}
return {
tooltip: mockTooltip,
};
}),
};
});
describe('Settings component', () => { describe('Settings component', () => {
it('should display', async () => { it('should display', async () => {
@ -24,7 +41,7 @@ describe('Settings component', () => {
toggleSoundEnabled={() => {}} toggleSoundEnabled={() => {}}
notificationIsEnabled={true} notificationIsEnabled={true}
toggleNotificationEnabled={() => {}} toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()} toggleNotificationAllowed={vi.fn()}
roomId="roomId" roomId="roomId"
setLanguage={() => {}} setLanguage={() => {}}
translations={{}} translations={{}}
@ -42,7 +59,7 @@ describe('Settings component', () => {
notificationIsEnabled={true} notificationIsEnabled={true}
notificationIsAllowed={false} notificationIsAllowed={false}
toggleNotificationEnabled={() => {}} toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()} toggleNotificationAllowed={vi.fn()}
roomId="roomId" roomId="roomId"
setLanguage={() => {}} setLanguage={() => {}}
translations={{}} translations={{}}
@ -54,7 +71,7 @@ describe('Settings component', () => {
}); });
it('should toggle sound', async () => { it('should toggle sound', async () => {
const toggleSound = jest.fn(); const toggleSound = vi.fn();
const { getByText } = render( const { getByText } = render(
<Provider store={store}> <Provider store={store}>
<Settings <Settings
@ -63,7 +80,7 @@ describe('Settings component', () => {
notificationIsEnabled={true} notificationIsEnabled={true}
notificationIsAllowed={true} notificationIsAllowed={true}
toggleNotificationEnabled={() => {}} toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()} toggleNotificationAllowed={vi.fn()}
roomId="roomId" roomId="roomId"
setLanguage={() => {}} setLanguage={() => {}}
translations={{}} translations={{}}
@ -79,10 +96,10 @@ describe('Settings component', () => {
it('should toggle notifications', async () => { it('should toggle notifications', async () => {
global.Notification = { global.Notification = {
requestPermission: jest.fn().mockResolvedValue('granted'), requestPermission: vi.fn().mockResolvedValue('granted'),
}; };
const toggleNotifications = jest.fn(); const toggleNotifications = vi.fn();
const { getByText } = render( const { getByText } = render(
<Provider store={store}> <Provider store={store}>
<Settings <Settings
@ -91,7 +108,7 @@ describe('Settings component', () => {
notificationIsEnabled={true} notificationIsEnabled={true}
notificationIsAllowed={true} notificationIsAllowed={true}
toggleNotificationEnabled={toggleNotifications} toggleNotificationEnabled={toggleNotifications}
toggleNotificationAllowed={jest.fn()} toggleNotificationAllowed={vi.fn()}
roomId="roomId" roomId="roomId"
setLanguage={() => {}} setLanguage={() => {}}
translations={{}} translations={{}}
@ -101,7 +118,7 @@ describe('Settings component', () => {
fireEvent.click(getByText('Desktop Notification')); fireEvent.click(getByText('Desktop Notification'));
jest.runAllTimers(); vi.runAllTimers();
delete global.Notification; delete global.Notification;
@ -110,11 +127,11 @@ describe('Settings component', () => {
it('should not toggle notifications', async () => { it('should not toggle notifications', async () => {
global.Notification = { global.Notification = {
requestPermission: jest.fn().mockResolvedValue('denied'), requestPermission: vi.fn().mockResolvedValue('denied'),
}; };
const toggleNotifications = jest.fn(); const toggleNotifications = vi.fn();
const toggleAllowed = jest.fn(); const toggleAllowed = vi.fn();
const { getByText } = render( const { getByText } = render(
<Provider store={store}> <Provider store={store}>
<Settings <Settings
@ -133,7 +150,7 @@ describe('Settings component', () => {
fireEvent.click(getByText('Desktop Notification')); fireEvent.click(getByText('Desktop Notification'));
jest.runAllTimers(); vi.runAllTimers();
delete global.Notification; delete global.Notification;
@ -142,7 +159,7 @@ describe('Settings component', () => {
}); });
it('should change lang', async () => { it('should change lang', async () => {
const changeLang = jest.fn(); const changeLang = vi.fn();
const { getByDisplayValue } = render( const { getByDisplayValue } = render(
<Provider store={store}> <Provider store={store}>
@ -151,7 +168,7 @@ describe('Settings component', () => {
toggleSoundEnabled={() => {}} toggleSoundEnabled={() => {}}
notificationIsEnabled={true} notificationIsEnabled={true}
toggleNotificationEnabled={() => {}} toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()} toggleNotificationAllowed={vi.fn()}
roomId="roomId" roomId="roomId"
setLanguage={changeLang} setLanguage={changeLang}
translations={{}} translations={{}}

View File

@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Settings component should display 1`] = ` exports[`Settings component > should display 1`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="styles" class="_styles_23b490"
> >
<section> <section>
<h4> <h4>
@ -127,6 +127,11 @@ exports[`Settings component should display 1`] = `
> >
Türkçe Türkçe
</option> </option>
<option
value="ko"
>
한국어
</option>
</select> </select>
</div> </div>
</section> </section>
@ -192,10 +197,10 @@ exports[`Settings component should display 1`] = `
</DocumentFragment> </DocumentFragment>
`; `;
exports[`Settings component should display 2`] = ` exports[`Settings component > should display 2`] = `
<DocumentFragment> <DocumentFragment>
<div <div
class="styles" class="_styles_23b490"
> >
<section> <section>
<h4> <h4>
@ -313,6 +318,11 @@ exports[`Settings component should display 2`] = `
> >
Türkçe Türkçe
</option> </option>
<option
value="ko"
>
한국어
</option>
</select> </select>
</div> </div>
</section> </section>

View File

@ -1,9 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import RoomLink from 'components/RoomLink';
import { styles } from './styles.module.scss';
import Cookie from 'js-cookie'; import Cookie from 'js-cookie';
import T from 'components/T';
import RoomLink from '@/components/RoomLink';
import T from '@/components/T';
import classes from './styles.module.scss';
class Settings extends Component { class Settings extends Component {
handleSoundToggle() { handleSoundToggle() {
@ -30,7 +32,7 @@ class Settings extends Component {
render() { render() {
return ( return (
<div className={styles}> <div className={classes.styles}>
<section> <section>
<h4> <h4>
<T path="newMessageNotification" /> <T path="newMessageNotification" />

View File

@ -1,8 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { getTranslations } from 'i18n';
import _ from 'lodash'; import _ from 'lodash';
import { getTranslations } from '@/i18n';
const regex = /{(.*?)}/g; const regex = /{(.*?)}/g;
class T extends Component { class T extends Component {

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { test, expect, vi } from 'vitest';
import T from './T'; import T from './T';
// To avoid missing provider // To avoid missing provider
jest.mock('components/T'); vi.mock('components/T');
test('T component is displaying', async () => { test('T component is displaying', async () => {
const { asFragment, rerender } = render(<T path="welcomeHeader" language="en" />); const { asFragment, rerender } = render(<T path="welcomeHeader" language="en" />);

View File

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`T component is displaying 1`] = ` exports[`T component is displaying 1`] = `
<DocumentFragment> <DocumentFragment>

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import T from 'components/T/T'; import T from './T';
export default connect((state, ownProps) => ({ export default connect((state, ownProps) => ({
language: state.app.language, language: state.app.language,

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { test, expect } from 'vitest';
import Username from '.'; import Username from '.';

View File

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Vitest Snapshot v1
exports[`Username component is displaying 1`] = ` exports[`Username component is displaying 1`] = `
<DocumentFragment> <DocumentFragment>

View File

@ -1,8 +1,11 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { test, expect, vi } from 'vitest';
import Welcome from '.'; import Welcome from '.';
vi.mock('@/components/RoomLink');
test('Welcome component is displaying', async () => { test('Welcome component is displaying', async () => {
const { asFragment } = render(<Welcome roomId="roomtest" close={() => {}} translations={{}} />); const { asFragment } = render(<Welcome roomId="roomtest" close={() => {}} translations={{}} />);

View File

@ -1,106 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Welcome component is displaying 1`] = `
<DocumentFragment>
<div>
<div>
v2.0 is a complete rewrite and includes several new features. Here are some highlights:
<ul
class="native"
>
<li>
Support on all modern browsers (Chrome, Firefox, Safari, Safari iOS, Android)
</li>
<li>
Slash commands (/nick, /me, /clear)
</li>
<li>
Room owners can lock the room, preventing anyone else from joining
</li>
<li>
Front-end rewritten in React.js and Redux
</li>
<li>
Send files up to 4 MB
</li>
</ul>
<div>
You can learn more
<a
href="https://github.com/darkwire/darkwire.io"
rel="noopener noreferrer"
target="_blank"
>
here
</a>
.
</div>
</div>
<br />
<p
class="mb-2"
>
Others can join this room using the following URL:
</p>
<form>
<div
class="form-group"
>
<div
class="input-group"
>
<input
class="form-control"
id="room-url"
readonly=""
type="text"
value="http://localhost/roomtest"
/>
<div
class="input-group-append"
>
<button
class="copy-room btn btn-secondary"
data-clipboard-text="http://localhost/roomtest"
data-placement="bottom"
data-toggle="tooltip"
type="button"
>
<svg
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<rect
height="13"
rx="2"
ry="2"
width="13"
x="9"
y="9"
/>
<path
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
/>
</svg>
</button>
</div>
</div>
</div>
</form>
<div
class="react-modal-footer"
>
<button
class="btn btn-primary btn-lg"
/>
</div>
</div>
</DocumentFragment>
`;

View File

@ -0,0 +1,54 @@
// Vitest Snapshot v1
exports[`Welcome component is displaying 1`] = `
<DocumentFragment>
<div>
<div>
v2.0 is a complete rewrite and includes several new features. Here are some highlights:
<ul
class="native"
>
<li>
Support on all modern browsers (Chrome, Firefox, Safari, Safari iOS, Android)
</li>
<li>
Slash commands (/nick, /me, /clear)
</li>
<li>
Room owners can lock the room, preventing anyone else from joining
</li>
<li>
Front-end rewritten in React.js and Redux
</li>
<li>
Send files up to 4 MB
</li>
</ul>
<div>
You can learn more
<a
href="https://github.com/darkwire/darkwire.io"
rel="noopener noreferrer"
target="_blank"
>
here
</a>
.
</div>
</div>
<br />
<p
class="mb-2"
>
Others can join this room using the following URL:
</p>
<div
class="react-modal-footer"
>
<button
class="btn btn-primary btn-lg"
/>
</div>
</div>
</DocumentFragment>
`;

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import RoomLink from 'components/RoomLink'; import RoomLink from '@/components/RoomLink';
class Welcome extends Component { class Welcome extends Component {
constructor(props) { constructor(props) {

View File

@ -1,2 +1,5 @@
/* istanbul ignore file */ /* istanbul ignore file */
export default process.env.NODE_ENV; export const MAX_FILE_SIZE = import.meta.VITE_MAX_FILE_SIZE || 4;
export const COMMIT_SHA = import.meta.env.VITE_COMMIT_SHA;
export default import.meta.env.NODE_ENV;

View File

@ -1,4 +1,6 @@
import { getTranslations } from './index.js'; import { getTranslations } from './';
import { test, expect } from 'vitest';
test('Get translation', () => { test('Get translation', () => {
expect(getTranslations('en').welcomeHeader).toBe('Welcome to Darkwire v2.0'); expect(getTranslations('en').welcomeHeader).toBe('Welcome to Darkwire v2.0');

View File

@ -34,11 +34,11 @@ const languagesMap = {
export function getTranslations(language = '') { export function getTranslations(language = '') {
const [lang, variant] = language.split('-'); const [lang, variant] = language.split('-');
if (languagesMap.hasOwnProperty(`${lang}${variant}`)) { if (Object.prototype.hasOwnProperty.call(languagesMap, `${lang}${variant}`)) {
return languagesMap[`${lang}${variant}`]; return languagesMap[`${lang}${variant}`];
} }
if (languagesMap.hasOwnProperty(lang)) { if (Object.prototype.hasOwnProperty.call(languagesMap, `${lang}`)) {
return languagesMap[lang]; return languagesMap[lang];
} }

View File

@ -1,13 +0,0 @@
/* istanbul ignore file */
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Root from './root';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<Root />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@ -1,5 +0,0 @@
describe('Timezones', () => {
it('should always be UTC', () => {
expect(new Date().getTimezoneOffset()).toBe(0);
});
});

52
client/src/main.tsx Normal file
View File

@ -0,0 +1,52 @@
import React from 'react';
import { Provider } from 'react-redux';
import { createRoot } from 'react-dom/client';
import { nanoid } from 'nanoid';
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'react-simple-dropdown/styles/Dropdown.css';
import './stylesheets/app.sass';
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
import configureStore from '@/store/';
import Home from '@/components/Home/';
import { hasTouchSupport } from '@/utils/dom';
const store = configureStore();
const router = createBrowserRouter([
{
path: '/',
element: <Navigate to={`/${nanoid()}`} replace />,
},
{
path: '/:roomId',
element: <Home />,
loader({ params }) {
return encodeURI(params.roomId ? params.roomId : '');
},
},
]);
const Main = () => {
React.useEffect(() => {
if (hasTouchSupport) {
document.body.classList.add('touch');
}
}, []);
return (
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
};
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<Main />
</React.StrictMode>,
);

View File

@ -76,10 +76,7 @@ const activities = (state = initialState, action) => {
], ],
}; };
case 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER': case 'RECEIVE_ENCRYPTED_MESSAGE_ADD_USER':
const newUserId = action.payload.payload.id; if (action.payload.state.room.members.find(m => m.id === action.payload.payload.id)) {
const haveUser = action.payload.state.room.members.find(m => m.id === newUserId);
if (haveUser) {
return state; return state;
} }
@ -88,7 +85,7 @@ const activities = (state = initialState, action) => {
items: [ items: [
...state.items, ...state.items,
{ {
userId: newUserId, userId: action.payload.payload.id,
type: 'USER_ENTER', type: 'USER_ENTER',
username: action.payload.payload.username, username: action.payload.payload.username,
}, },

View File

@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest';
import reducer from './activities'; import reducer from './activities';
describe('Activities reducer', () => { describe('Activities reducer', () => {

View File

@ -1,5 +1,6 @@
import Cookie from 'js-cookie'; import Cookie from 'js-cookie';
import { getTranslations } from 'i18n';
import { getTranslations } from '@/i18n';
const language = Cookie.get('language') || navigator.language || 'en'; const language = Cookie.get('language') || navigator.language || 'en';

View File

@ -1,9 +1,11 @@
import reducer from './app'; import { describe, it, expect, vi } from 'vitest';
import { getTranslations } from 'i18n';
jest.mock('i18n', () => { import reducer from './app';
import { getTranslations } from '@/i18n';
vi.mock('@/i18n', () => {
return { return {
getTranslations: jest.fn().mockReturnValue({ path: 'test' }), getTranslations: vi.fn().mockReturnValue({ path: 'test' }),
}; };
}); });

View File

@ -1,5 +1,6 @@
/* istanbul ignore file */ /* istanbul ignore file */
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import app from './app'; import app from './app';
import activities from './activities'; import activities from './activities';
import user from './user'; import user from './user';

View File

@ -1,3 +1,4 @@
/* eslint-disable no-case-declarations */
import _ from 'lodash'; import _ from 'lodash';
const initialState = { const initialState = {

View File

@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest';
import reducer from './room'; import reducer from './room';
describe('Room reducer', () => { describe('Room reducer', () => {

View File

@ -1,8 +1,10 @@
import { describe, it, expect, vi } from 'vitest';
import reducer from './user'; import reducer from './user';
jest.mock('i18n', () => { vi.mock('@/i18n', () => {
return { return {
getTranslations: jest.fn().mockReturnValue({ path: 'test' }), getTranslations: vi.fn().mockReturnValue({ path: 'test' }),
}; };
}); });

View File

@ -5,7 +5,7 @@ import 'bootstrap/dist/js/bootstrap.bundle.min.js';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Redirect } from 'react-router'; import { Redirect } from 'react-router';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import configureStore from 'store'; import configureStore from './store';
import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { BrowserRouter, Route, Switch } from 'react-router-dom';
import shortId from 'shortid'; import shortId from 'shortid';
import Home from 'components/Home'; import Home from 'components/Home';

View File

@ -20,9 +20,9 @@ const isLocalhost = Boolean(
); );
export function register(config) { export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (import.meta.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); const publicUrl = new URL(import.meta.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin // Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to // from what our page is served on. This might happen if a CDN is used to
@ -31,7 +31,7 @@ export function register(config) {
} }
window.addEventListener('load', () => { window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${import.meta.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) { if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not. // This is running on localhost. Let's check if a service worker still exists or not.

View File

@ -1,8 +1,14 @@
import { configure } from 'enzyme'; import { expect, afterEach, vi } from 'vitest';
import Adapter from 'enzyme-adapter-react-16'; import { cleanup } from '@testing-library/react';
// this adds jest-dom's custom assertions import matchers from '@testing-library/jest-dom/extend-expect';
import '@testing-library/jest-dom/extend-expect'; import createFetchMock from 'vitest-fetch-mock';
import { enableFetchMocks } from 'jest-fetch-mock';
configure({ adapter: new Adapter() }); const fetchMock = createFetchMock(vi);
enableFetchMocks(); fetchMock.enableMocks();
expect.extend(matchers);
// runs a cleanup after each test case (e.g. clearing jsdom)
afterEach(() => {
cleanup();
});

View File

@ -1,10 +1,11 @@
/* istanbul ignore file */ /* istanbul ignore file */
import { createStore, applyMiddleware, compose } from 'redux'; import { createStore, applyMiddleware, compose } from 'redux';
import reducers from 'reducers';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import reducers from '@/reducers';
const composeEnhancers = const composeEnhancers =
process.env.NODE_ENV === 'production' ? compose : window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; import.meta.env.NODE_ENV === 'production' ? compose : window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const enabledMiddlewares = [thunk]; const enabledMiddlewares = [thunk];
@ -12,14 +13,5 @@ const middlewares = applyMiddleware(...enabledMiddlewares);
export default function configureStore(preloadedState) { export default function configureStore(preloadedState) {
const store = createStore(reducers, preloadedState, composeEnhancers(middlewares)); const store = createStore(reducers, preloadedState, composeEnhancers(middlewares));
if (module.hot) {
module.hot.accept('../reducers', () => {
// eslint-disable-next-line global-require
const nextRootReducer = require('../reducers/index');
store.replaceReducer(nextRootReducer);
});
}
return store; return store;
} }

View File

@ -1,5 +0,0 @@
/* istanbul ignore file */
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

View File

@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
export const getSelectedText = () => { export const getSelectedText = () => {
let text = ''; let text = '';
if (typeof window.getSelection !== 'undefined') { if (typeof window.getSelection !== 'undefined') {

View File

@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
export const getObjectUrl = (encodedFile, fileType) => { export const getObjectUrl = (encodedFile, fileType) => {
const b64 = unescape(encodedFile); const b64 = unescape(encodedFile);
const sliceSize = 1024; const sliceSize = 1024;

View File

@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
export function sanitize(str) { export function sanitize(str) {
return str.replace(/[^A-Za-z0-9._]/g, '-').replace(/[<>]/gi, ''); return str.replace(/[^A-Za-z0-9._]/g, '-').replace(/[<>]/gi, '');
} }

View File

@ -2,112 +2,107 @@ import Crypto from './crypto';
const crypto = new Crypto(); const crypto = new Crypto();
export const process = (payload, state) => export const process = async (payload, state) => {
new Promise(async (resolve, reject) => { const privateKeyJson = state.user.privateKey;
const privateKeyJson = state.user.privateKey; const privateKey = await crypto.importEncryptDecryptKey(privateKeyJson, 'jwk', ['decrypt', 'unwrapKey']);
const privateKey = await crypto.importEncryptDecryptKey(privateKeyJson, 'jwk', ['decrypt', 'unwrapKey']);
let sessionKey; const iv = await crypto.convertStringToArrayBufferView(payload.iv);
let signingKey; const signature = await crypto.convertStringToArrayBufferView(payload.signature);
const payloadBuffer = await crypto.convertStringToArrayBufferView(payload.payload);
const iv = await crypto.convertStringToArrayBufferView(payload.iv); // We try to decrypt all sessions and signin keys to get the one encrypted for self
const signature = await crypto.convertStringToArrayBufferView(payload.signature); const [sessionKey, signingKey] = await new Promise(resolvePayload => {
const payloadBuffer = await crypto.convertStringToArrayBufferView(payload.payload); payload.keys.forEach(async key => {
try {
const sessionKey = await crypto.unwrapKey(
'jwk',
key.sessionKey,
privateKey,
{
name: 'RSA-OAEP',
hash: { name: 'SHA-1' },
},
{ name: 'AES-CBC' },
true,
['decrypt'],
);
await new Promise(resolvePayload => { const signingKey = await crypto.unwrapKey(
payload.keys.forEach(async key => { 'jwk',
try { key.signingKey,
sessionKey = await crypto.unwrapKey( privateKey,
'jwk', {
key.sessionKey, name: 'RSA-OAEP',
privateKey, hash: { name: 'SHA-1' },
{ },
name: 'RSA-OAEP', { name: 'HMAC', hash: { name: 'SHA-256' } },
hash: { name: 'SHA-1' }, true,
}, ['verify'],
{ name: 'AES-CBC' }, );
true, resolvePayload([sessionKey, signingKey]);
['decrypt'], } catch (e) {} // eslint-disable-line
);
signingKey = await crypto.unwrapKey(
'jwk',
key.signingKey,
privateKey,
{
name: 'RSA-OAEP',
hash: { name: 'SHA-1' },
},
{ name: 'HMAC', hash: { name: 'SHA-256' } },
true,
['verify'],
);
resolvePayload();
} catch (e) {} // eslint-disable-line
});
});
const verified = await crypto.verifyPayload(signature, payloadBuffer, signingKey);
if (!verified) {
reject();
return;
}
const decryptedPayload = await crypto.decryptMessage(payloadBuffer, sessionKey, iv);
const payloadJson = JSON.parse(crypto.convertArrayBufferViewToString(new Uint8Array(decryptedPayload)));
resolve(payloadJson);
});
export const prepare = (payload, state) =>
new Promise(async resolve => {
const myUsername = state.user.username;
const myId = state.user.id;
const sessionKey = await crypto.createSecretKey();
const signingKey = await crypto.createSigningKey();
const iv = await crypto.crypto.getRandomValues(new Uint8Array(16));
const jsonToSend = {
...payload,
payload: {
...payload.payload,
sender: myId,
username: myUsername,
text: encodeURI(payload.payload.text),
},
};
const payloadBuffer = crypto.convertStringToArrayBufferView(JSON.stringify(jsonToSend));
const encryptedPayload = await crypto.encryptMessage(payloadBuffer, sessionKey, iv);
const payloadString = await crypto.convertArrayBufferViewToString(new Uint8Array(encryptedPayload));
const signature = await crypto.signMessage(encryptedPayload, signingKey);
const encryptedKeys = await Promise.all(
state.room.members.map(async member => {
const key = await crypto.importEncryptDecryptKey(member.publicKey);
const enc = await Promise.all([crypto.wrapKey(sessionKey, key), crypto.wrapKey(signingKey, key)]);
return {
sessionKey: enc[0],
signingKey: enc[1],
};
}),
);
const ivString = await crypto.convertArrayBufferViewToString(new Uint8Array(iv));
const signatureString = await crypto.convertArrayBufferViewToString(new Uint8Array(signature));
resolve({
toSend: {
payload: payloadString,
signature: signatureString,
iv: ivString,
keys: encryptedKeys,
},
original: jsonToSend,
}); });
}); });
const verified = await crypto.verifyPayload(signature, payloadBuffer, signingKey);
if (!verified) {
throw new Error("Can't verify message");
}
const decryptedPayload = await crypto.decryptMessage(payloadBuffer, sessionKey, iv);
const payloadJson = JSON.parse(crypto.convertArrayBufferViewToString(new Uint8Array(decryptedPayload)));
return payloadJson;
};
export const prepare = async (payload, state) => {
const myUsername = state.user.username;
const myId = state.user.id;
const sessionKey = await crypto.createSecretKey();
const signingKey = await crypto.createSigningKey();
const iv = await crypto.crypto.getRandomValues(new Uint8Array(16));
const jsonToSend = {
...payload,
payload: {
...payload.payload,
sender: myId,
username: myUsername,
text: encodeURI(payload.payload.text),
},
};
const payloadBuffer = crypto.convertStringToArrayBufferView(JSON.stringify(jsonToSend));
const encryptedPayload = await crypto.encryptMessage(payloadBuffer, sessionKey, iv);
const payloadString = await crypto.convertArrayBufferViewToString(new Uint8Array(encryptedPayload));
const signature = await crypto.signMessage(encryptedPayload, signingKey);
const encryptedKeys = await Promise.all(
state.room.members.map(async member => {
const key = await crypto.importEncryptDecryptKey(member.publicKey);
const enc = await Promise.all([crypto.wrapKey(sessionKey, key), crypto.wrapKey(signingKey, key)]);
return {
sessionKey: enc[0],
signingKey: enc[1],
};
}),
);
const ivString = await crypto.convertArrayBufferViewToString(new Uint8Array(iv));
const signatureString = await crypto.convertArrayBufferViewToString(new Uint8Array(signature));
return {
toSend: {
payload: payloadString,
signature: signatureString,
iv: ivString,
keys: encryptedKeys,
},
original: jsonToSend,
};
};

View File

@ -1,6 +1,6 @@
import beepFile from 'audio/beep.mp3'; import beepFile from '@/audio/beep.mp3';
const showNotification = (title, message, avatarUrl) => { const showNotification = (title, message) => {
const notifBody = { const notifBody = {
body: message, body: message,
tag: 'darkwire', tag: 'darkwire',
@ -44,6 +44,21 @@ export const notify = (title, content = '') => {
} }
}; };
export const beep = (window.Audio && new window.Audio(beepFile)) || { play: () => {} }; let theBeep;
export const beep = {
async play() {
if (window.Audio) {
if (theBeep === undefined) {
theBeep = new window.Audio(beepFile);
}
try {
await theBeep.play();
} catch (e) {
console.log("Can't play sound.");
}
}
},
};
export default { notify, beep }; export default { notify, beep };

View File

@ -1,5 +1,5 @@
import socketIO from 'socket.io-client'; import socketIO from 'socket.io-client';
import generateUrl from '../api/generator'; import generateUrl from '@/api/generator';
let socket; let socket;

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