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

View File

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

View File

@ -1,14 +1,14 @@
# Api settings
TZ=UTC
REACT_APP_API_HOST=localhost
REACT_APP_API_PROTOCOL=http
REACT_APP_API_PORT=3001
REACT_APP_COMMIT_SHA=some_sha
VITE_API_HOST=localhost
VITE_API_PROTOCOL=http
VITE_API_PORT=3001
VITE_COMMIT_SHA=some_sha
# To display darkwire version
REACT_APP_COMMIT_SHA=some_sha
VITE_COMMIT_SHA=some_sha
# 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
.DS_Store
dist
coverage
# Logs
logs
*.log
.env*
!.env.example
build/
*sublime*
npm-debug.log*
yarn-debug.log*
yarn-error.log*
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",
"version": "2.0.0-beta.12",
"main": "index.js",
"type": "module",
"contributors": [
{
"name": "Daniel Seripap"
@ -12,44 +13,55 @@
],
"license": "MIT",
"dependencies": {
"autosize": "^4.0.2",
"bootstrap": "^4.3.1",
"clipboard": "^2.0.4",
"feather-icons": "^4.21.0",
"jquery": "^3.5.0",
"js-cookie": "^2.2.0",
"lodash": "^4.17.20",
"moment": "^2.24.0",
"node-sass": "^4.13.1",
"popper.js": "^1.15.0",
"randomcolor": "^0.5.4",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-feather": "^1.1.6",
"react-linkify": "^0.2.2",
"react-modal": "^3.8.1",
"react-redux": "^7.0.3",
"react-router-dom": "^5.0.0",
"react-scripts": "3.0.0",
"bootstrap": "^4.6.2",
"classnames": "^2.3.2",
"clipboard": "^2.0.11",
"jquery": "3",
"js-cookie": "^3.0.1",
"moment": "^2.29.4",
"nanoid": "^4.0.0",
"randomcolor": "^0.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-linkify": "^1.0.0-alpha",
"react-modal": "^3.16.1",
"react-redux": "^8.0.5",
"react-router": "^6.4.4",
"react-router-dom": "^6.4.4",
"react-simple-dropdown": "^3.2.3",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0",
"sanitize-html": "^1.20.1",
"shortid": "^2.2.14",
"socket.io-client": "^2.2.0",
"tinycon": "^0.6.8",
"webcrypto-shim": "^0.1.4"
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"sanitize-html": "^2.7.3",
"socket.io-client": "^4.5.4",
"tinycon": "^0.6.8"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jest-environment-jsdom-sixteen",
"coverage": "react-scripts test --env=jest-environment-jsdom-sixteen --coverage --watchAll=false",
"eject": "react-scripts eject",
"lint": "eslint src"
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "TZ=UTC vitest",
"lint": "eslint src",
"coverage": "TZ=UTC vitest --coverage --watch=false"
},
"eslintConfig": {
"extends": "react-app"
"devDependencies": {
"@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": {
"production": [
@ -62,18 +74,5 @@
"last 1 firefox 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 { describe, it, expect, vi } from 'vitest';
describe('App actions', () => {
it('should create an action to scroll to bottom', () => {
expect(actions.setScrolledToBottom('test')).toEqual({
@ -22,7 +24,7 @@ describe('App actions', () => {
});
it('should create an action to clear activities', () => {
const mockDispatch = jest.fn();
const mockDispatch = vi.fn();
actions.clearActivities()(mockDispatch);
@ -31,7 +33,7 @@ describe('App actions', () => {
});
});
it('should create all actions', () => {
const mockDispatch = jest.fn();
const mockDispatch = vi.fn();
const actionsResults = [
[actions.toggleWindowFocus('test'), 'TOGGLE_WINDOW_FOCUS'],

View File

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

View File

@ -1,21 +1,23 @@
import * as actions from './encrypted_messages';
import { getSocket } from 'utils/socket';
import { prepare as prepareMessage, process as processMessage } from 'utils/message';
import { getSocket } from '@/utils/socket';
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 {
prepare: jest
prepare: vi
.fn()
.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 {
getSocket: jest.fn().mockImplementation(() => ({
getSocket: vi.fn().mockImplementation(() => ({
emit: mockEmit,
})),
};
@ -23,9 +25,9 @@ jest.mock('utils/socket', () => {
describe('Encrypted messages actions', () => {
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(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 () => {
const mockDispatch = jest.fn();
const mockDispatch = vi.fn();
await actions.receiveEncryptedMessage({ payload: 'encrypted' })(
mockDispatch,
jest.fn().mockReturnValue({ state: {} }),
vi.fn().mockReturnValue({ 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) => {
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) {
case 'TOGGLE_LOCK_ROOM':
return sendToggleLockRoom(dispatch, getState);

View File

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

View File

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

View File

@ -1,15 +1,17 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { render, fireEvent } from '@testing-library/react';
import About from '.';
import fetchMock from 'jest-fetch-mock';
import { describe, it, expect, vi, afterEach } from 'vitest';
jest.useFakeTimers();
vi.useFakeTimers();
// Mock Api generator
jest.mock('../../api/generator', () => {
return path => {
vi.mock('@/api/generator', () => {
return {
default: path => {
return `http://fakedomain/${path}`;
},
};
});
@ -43,7 +45,7 @@ describe('About component', () => {
fireEvent.change(getByPlaceholderText('Room ID'), { target: { value: 'newRoomName' } });
jest.runAllTimers();
vi.runAllTimers();
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>
<div
class="base"
class="_base_4f26aa"
>
<div
class="links"
class="_links_4f26aa"
>
<div>
<a

View File

@ -1,7 +1,9 @@
/* eslint-disable */
import React, { Component } from 'react';
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';
class About extends Component {
@ -63,11 +65,8 @@ class About extends Component {
<h4>Version</h4>
<p>
Commit SHA:{' '}
<a
target="_blank"
href={`https://github.com/darkwire/darkwire.io/commit/${process.env.REACT_APP_COMMIT_SHA}`}
>
{process.env.REACT_APP_COMMIT_SHA}
<a target="_blank" href={`https://github.com/darkwire/darkwire.io/commit/${COMMIT_SHA}`}>
{COMMIT_SHA}
</a>
</p>
</section>

View File

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

View File

@ -1,19 +1,20 @@
import React from '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 = {
typePlaceholder: 'inputplaceholder',
};
// 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
jest.mock('../../utils/dom');
vi.mock('@/utils/dom');
describe('Chat component', () => {
afterEach(() => {
@ -39,7 +40,7 @@ describe('Chat component', () => {
});
it('can send message', () => {
const sendEncryptedMessage = jest.fn();
const sendEncryptedMessage = vi.fn();
render(
<Chat
@ -78,7 +79,7 @@ describe('Chat component', () => {
});
it("shouldn't send message with Shift+enter", () => {
const sendEncryptedMessage = jest.fn();
const sendEncryptedMessage = vi.fn();
render(
<Chat
@ -115,9 +116,9 @@ describe('Chat component', () => {
});
it('should send commands', () => {
const sendEncryptedMessage = jest.fn();
const showNotice = jest.fn();
const clearActivities = jest.fn();
const sendEncryptedMessage = vi.fn();
const showNotice = vi.fn();
const clearActivities = vi.fn();
render(
<Chat
@ -210,14 +211,14 @@ describe('Chat component', () => {
// Enable touch support
dom.hasTouchSupport = true;
jest.mock('../../utils/dom', () => {
vi.mock('@/utils/dom', () => {
return {
getSelectedText: jest.fn(),
getSelectedText: vi.fn(),
hasTouchSupport: true,
};
});
const sendEncryptedMessage = jest.fn();
const sendEncryptedMessage = vi.fn();
const { getByTitle } = render(
<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>
<form
class="chat-preflight-container"
@ -13,7 +13,7 @@ exports[`Chat component should display 1`] = `
class="input-controls"
>
<div
class="styles icon file-transfer btn btn-link"
class="_styles_374fdd icon file-transfer btn btn-link"
>
<input
id="fileInput"

View File

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

View File

@ -1,6 +1,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import Connecting from '.';
import { test, expect } from 'vitest';
test('Connecting component is displaying', async () => {
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`] = `
<DocumentFragment>

View File

@ -1,9 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import uuid from 'uuid';
import { nanoid } from 'nanoid';
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 = [
'png',
@ -24,8 +27,6 @@ const VALID_FILE_TYPES = [
'css',
];
const MAX_FILE_SIZE = process.env.REACT_APP_MAX_FILE_SIZE || 4;
/**
* Encode the given file to binary string
* @param {File} file
@ -86,7 +87,7 @@ export const FileTransfer = ({ sendEncryptedMessage }) => {
return false;
}
const fileId = uuid.v4();
const fileId = nanoid();
const fileData = {
id: fileId,
file,
@ -129,7 +130,7 @@ export const FileTransfer = ({ sendEncryptedMessage }) => {
}
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} />
<label htmlFor="fileInput">
<File color="#fff" />

View File

@ -1,9 +1,10 @@
import React from 'react';
import { render, screen, fireEvent, createEvent } from '@testing-library/react';
import FileTransfer from '.';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// 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', () => {
const { File } = window;
@ -68,7 +69,7 @@ describe('FileTransfer tests', () => {
});
it('Try to send unsupported file', async () => {
window.alert = jest.fn();
window.alert = vi.fn();
render(<FileTransfer sendEncryptedMessage={() => {}} />);
@ -86,7 +87,7 @@ describe('FileTransfer tests', () => {
});
it('Try to send too big file', async () => {
window.alert = jest.fn();
window.alert = vi.fn();
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>
<div
class="styles icon file-transfer btn btn-link"
class="_styles_374fdd icon file-transfer btn btn-link"
>
<input
id="fileInput"

View File

@ -1,12 +1,13 @@
import React from 'react';
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 zoomableImage = React.useRef(null);

View File

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

View File

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

View File

@ -1,18 +1,21 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import ActivityList from './ActivityList';
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();
jest.useFakeTimers();
vi.useFakeTimers();
describe('ActivityList component', () => {
it('should display', () => {
const { asFragment } = render(
<Provider store={store}>
<ActivityList openModal={jest.fn()} activities={[]} />
<ActivityList openModal={vi.fn()} activities={[]} />
</Provider>,
);
@ -39,7 +42,7 @@ describe('ActivityList component', () => {
];
const { asFragment } = render(
<Provider store={store}>
<ActivityList openModal={jest.fn()} activities={activities} />
<ActivityList openModal={vi.fn()} activities={activities} />
</Provider>,
);
@ -47,7 +50,7 @@ describe('ActivityList component', () => {
});
it('should show About modal', async () => {
const mockOpenModal = jest.fn();
const mockOpenModal = vi.fn();
const { getByText } = render(
<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'));
jest.runAllTimers();
vi.runAllTimers();
expect(mockOpenModal.mock.calls[0][0]).toBe('About');
jest.runAllTimers();
vi.runAllTimers();
});
it('should focus chat', () => {
const { getByTestId } = render(
<Provider store={store}>
<ActivityList openModal={jest.fn()} activities={[]} />
<ActivityList openModal={vi.fn()} activities={[]} />
</Provider>,
);
fireEvent.click(getByTestId('main-div'));
jest.runAllTimers();
vi.runAllTimers();
});
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
.fn()
.mockReturnValueOnce({ top: 0 })
.mockReturnValueOnce({ top: 261 });
Element.prototype.getBoundingClientRect = vi.fn().mockReturnValueOnce({ top: 0 }).mockReturnValueOnce({ top: 261 });
jest.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(42);
const mockScrollTop = jest.spyOn(Element.prototype, 'scrollTop', 'set');
vi.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(42);
const mockScrollTop = vi.spyOn(Element.prototype, 'scrollTop', 'set');
const { rerender, getByTestId } = render(
<Provider store={store}>
<ActivityList openModal={jest.fn()} activities={[]} />
<ActivityList openModal={vi.fn()} activities={[]} />
</Provider>,
);
rerender(
<Provider store={store}>
<ActivityList
openModal={jest.fn()}
openModal={vi.fn()}
activities={[
{
type: 'TEXT_MESSAGE',
@ -105,7 +105,7 @@ describe('ActivityList component', () => {
</Provider>,
);
jest.runAllTimers();
vi.runAllTimers();
expect(mockScrollTop).toHaveBeenCalledTimes(2);
expect(mockScrollTop).toHaveBeenLastCalledWith(42);
@ -115,7 +115,7 @@ describe('ActivityList component', () => {
rerender(
<Provider store={store}>
<ActivityList
openModal={jest.fn()}
openModal={vi.fn()}
activities={[
{
type: 'TEXT_MESSAGE',

View File

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

View File

@ -1,27 +1,34 @@
import React from 'react';
import { render } from '@testing-library/react';
import Home from './Home';
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();
jest.mock('react-modal'); // Cant load modal without root app element
jest.mock('utils/socket', () => {
vi.mock('react-modal'); // Cant load modal without root app element
vi.mock('@/RoomLink');
vi.mock('@/components/Nav');
vi.mock('@/utils/socket', () => {
// Avoid exception
return {
connect: jest.fn().mockImplementation(() => {
connect: vi.fn().mockImplementation(() => {
return {
on: jest.fn(),
emit: jest.fn(),
on: vi.fn(),
emit: vi.fn(),
};
}),
};
}); //
jest.mock('utils/crypto', () => {
vi.mock('../../utils/crypto', () => {
// Need window.crytpo.subtle
return jest.fn().mockImplementation(() => {
return {
default: vi.fn().mockImplementation(() => {
return {
createEncryptDecryptKeys: () => {
return {
@ -33,7 +40,8 @@ jest.mock('utils/crypto', () => {
return 'exportedkey';
},
};
});
}),
};
});
test('Home component is displaying', async () => {

View File

@ -1,8 +1,9 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { notify, beep } from 'utils/notifications';
import Tinycon from 'tinycon';
import { toggleNotificationAllowed, toggleNotificationEnabled } from 'actions';
import { notify, beep } from '@/utils/notifications';
import { toggleNotificationAllowed, toggleNotificationEnabled } from '@/actions';
const mapStateToProps = state => {
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>
<div>
<div
@ -30,7 +30,7 @@ exports[`Activity component should display CHANGE_USERNAME 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display NOTICE 1`] = `
exports[`Activity component > should display NOTICE 1`] = `
<DocumentFragment>
<div>
<div
@ -44,7 +44,7 @@ exports[`Activity component should display NOTICE 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display RECEIVE_FILE 1`] = `
exports[`Activity component > should display RECEIVE_FILE 1`] = `
<DocumentFragment>
<div>
<span>
@ -71,7 +71,7 @@ exports[`Activity component should display RECEIVE_FILE 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display RECEIVE_FILE with image 1`] = `
exports[`Activity component > should display RECEIVE_FILE with image 1`] = `
<DocumentFragment>
<div>
<span>
@ -103,7 +103,7 @@ exports[`Activity component should display RECEIVE_FILE with image 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display SEND_FILE 1`] = `
exports[`Activity component > should display SEND_FILE 1`] = `
<DocumentFragment>
<div>
<div
@ -128,7 +128,7 @@ exports[`Activity component should display SEND_FILE 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display TEXT_MESSAGE 1`] = `
exports[`Activity component > should display TEXT_MESSAGE 1`] = `
<DocumentFragment>
<div>
<div
@ -148,18 +148,14 @@ exports[`Activity component should display TEXT_MESSAGE 1`] = `
</div>
<div
class="chat"
>
<span
class="Linkify"
>
Hi!
</span>
</div>
</div>
</DocumentFragment>
`;
exports[`Activity component should display TOGGLE_LOCK_ROOM 1`] = `
exports[`Activity component > should display TOGGLE_LOCK_ROOM 1`] = `
<DocumentFragment>
<div>
<div
@ -181,7 +177,7 @@ exports[`Activity component should display TOGGLE_LOCK_ROOM 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display TOGGLE_LOCK_ROOM 2`] = `
exports[`Activity component > should display TOGGLE_LOCK_ROOM 2`] = `
<DocumentFragment>
<div>
<div
@ -203,7 +199,7 @@ exports[`Activity component should display TOGGLE_LOCK_ROOM 2`] = `
</DocumentFragment>
`;
exports[`Activity component should display USER_ACTION 1`] = `
exports[`Activity component > should display USER_ACTION 1`] = `
<DocumentFragment>
<div>
<div
@ -224,7 +220,7 @@ exports[`Activity component should display USER_ACTION 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display USER_ENTER 1`] = `
exports[`Activity component > should display USER_ENTER 1`] = `
<DocumentFragment>
<div>
<div
@ -246,7 +242,7 @@ exports[`Activity component should display USER_ENTER 1`] = `
</DocumentFragment>
`;
exports[`Activity component should display USER_EXIT 1`] = `
exports[`Activity component > should display USER_EXIT 1`] = `
<DocumentFragment>
<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>
<div
class="main-chat"
@ -14,7 +14,7 @@ exports[`ActivityList component should display 1`] = `
>
<li>
<p
class="tos"
class="_tos_0b54d3"
>
<button
class="btn btn-link"
@ -40,7 +40,7 @@ exports[`ActivityList component should display 1`] = `
class="input-controls"
>
<div
class="styles icon file-transfer btn btn-link"
class="_styles_374fdd icon file-transfer btn btn-link"
>
<input
id="fileInput"
@ -78,7 +78,7 @@ exports[`ActivityList component should display 1`] = `
</DocumentFragment>
`;
exports[`ActivityList component should display with activities 1`] = `
exports[`ActivityList component > should display with activities 1`] = `
<DocumentFragment>
<div
class="main-chat"
@ -92,7 +92,7 @@ exports[`ActivityList component should display with activities 1`] = `
>
<li>
<p
class="tos"
class="_tos_0b54d3"
>
<button
class="btn btn-link"
@ -122,12 +122,8 @@ exports[`ActivityList component should display with activities 1`] = `
</div>
<div
class="chat"
>
<span
class="Linkify"
>
Hi!
</span>
</div>
</div>
</li>
@ -196,7 +192,7 @@ exports[`ActivityList component should display with activities 1`] = `
class="input-controls"
>
<div
class="styles icon file-transfer btn btn-link"
class="_styles_374fdd icon file-transfer btn btn-link"
>
<input
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>
<div
class="styles h-100"
class="_styles_0b54d3 h-100"
>
<div
class="nav-container"
@ -38,7 +38,7 @@ exports[`Connected Home component should display 1`] = `
/>
<line
x1="12"
x2="12"
x2="12.01"
y1="16"
y2="16"
/>
@ -58,11 +58,11 @@ exports[`Connected Home component should display 1`] = `
<img
alt="Darkwire"
class="logo"
src="logo.png"
src="/src/img/logo.png"
/>
<button
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-toggle="tooltip"
title="Copied"
@ -279,7 +279,7 @@ exports[`Connected Home component should display 1`] = `
/>
<line
x1="12"
x2="12"
x2="12.01"
y1="8"
y2="8"
/>

View File

@ -1,5 +1,6 @@
import Home from './Home';
import { connect } from 'react-redux';
import { useLoaderData } from 'react-router-dom';
import {
receiveEncryptedMessage,
createUser,
@ -14,7 +15,9 @@ import {
sendUnencryptedMessage,
sendEncryptedMessage,
setLanguage,
} from 'actions';
} from '@/actions';
import Home from './Home';
import WithNewMessageNotification from './WithNewMessageNotification';
const mapStateToProps = state => {
@ -57,4 +60,11 @@ const mapDispatchToProps = {
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,55 +1,65 @@
import React from 'react';
import { render } from '@testing-library/react';
import ConnectedHome from '.';
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 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();
jest.useFakeTimers();
vi.useFakeTimers();
// We don't test activity list here
jest.mock('./ActivityList', () => {
return jest.fn().mockReturnValue(null);
});
vi.mock('react-modal'); // Cant load modal without root app element
jest.mock('react-modal'); // Cant load modal without root app element
jest.mock('utils/socket', () => {
vi.mock('nanoid', () => {
// Avoid exception
return {
connect: jest.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(() => {
nanoid: vi.fn().mockImplementation(() => {
return 'shortidgenerated';
}),
};
});
jest.mock('utils/crypto', () => {
vi.mock('tinycon', () => {
return {
default: { setBubble: vi.fn() },
};
});
// We don't test activity list here
vi.mock('./ActivityList', () => {
return { default: vi.fn().mockReturnValue(null) };
});
vi.mock('@/utils/socket', () => {
// Avoid exception
return {
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 jest.fn().mockImplementation(() => {
return {
default: vi.fn().mockImplementation(() => {
return {
createEncryptDecryptKeys: () => {
return {
@ -61,28 +71,23 @@ jest.mock('utils/crypto', () => {
return { n: 'exportedKey' };
},
};
});
}),
};
});
jest.mock('utils/message', () => {
vi.mock('@/utils/message', () => {
return {
process: jest.fn(async (payload, state) => ({
process: vi.fn(async (payload, state) => ({
...payload,
payload: { payload: 'text', username: 'sender', text: 'new message' },
})),
};
});
jest.mock('utils/notifications', () => {
vi.mock('@/utils/notifications', () => {
return {
notify: jest.fn(),
beep: { play: jest.fn() },
};
});
jest.mock('tinycon', () => {
return {
setBubble: jest.fn(),
notify: vi.fn(),
beep: { play: vi.fn() },
};
});
@ -143,7 +148,7 @@ describe('Connected Home component', () => {
});
it('should send notifications', async () => {
Modal.prototype.getSnapshotBeforeUpdate = jest.fn().mockReturnValue(null);
Modal.prototype.getSnapshotBeforeUpdate = vi.fn().mockReturnValue(null);
const { rerender } = render(
<Provider store={store}>
<ConnectedHome match={{ params: { roomId: 'roomTest' } }} userId="testUserId" roomId={'testId'} />

View File

@ -1,6 +1,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import Message from '.';
import { test, expect } from 'vitest';
test('Message component is displaying', async () => {
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`] = `
<DocumentFragment>
@ -20,12 +20,8 @@ exports[`Message component is displaying 1`] = `
</div>
<div
class="chat"
>
<span
class="Linkify"
>
we come in peace
</span>
</div>
</div>
</DocumentFragment>

View File

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

View File

@ -1,19 +1,21 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import Nav from '.';
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);
});
const mockCollapse = jest.fn().mockImplementation(param => {
const mockCollapse = vi.fn().mockImplementation(param => {
// console.log('collapse', param);
});
jest.mock('jquery', () => {
return jest.fn().mockImplementation(param => {
// console.log('$', param);
vi.mock('jquery', () => {
return {
default: vi.fn().mockImplementation(param => {
if (typeof param === 'function') {
param();
}
@ -21,18 +23,19 @@ jest.mock('jquery', () => {
tooltip: mockTooltip,
collapse: mockCollapse,
};
});
}),
};
});
jest.mock('shortid', () => {
vi.mock('nanoid', () => {
return {
generate() {
nanoid: () => {
return 'fakeid';
},
};
});
jest.useFakeTimers();
vi.useFakeTimers();
const mockTranslations = {
newRoomButton: 'new room',
@ -101,9 +104,9 @@ test('Nav component is displaying with another configuration and can rerender',
});
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(
<Nav
@ -128,14 +131,14 @@ test('Can copy room url', async () => {
expect(mockTooltip).toHaveBeenLastCalledWith('show');
// Wait tooltip closing
jest.runAllTimers();
vi.runAllTimers();
expect(mock$).toHaveBeenCalledTimes(18);
expect(mockTooltip).toHaveBeenLastCalledWith('hide');
});
test('Can lock/unlock room is room owner only', async () => {
const toggleLockRoom = jest.fn();
const toggleLockRoom = vi.fn();
const { rerender, getByTitle } = render(
<Nav
@ -231,7 +234,7 @@ test('Can show user list', async () => {
});
test('Can open settings', async () => {
const openModal = jest.fn();
const openModal = vi.fn();
// Test with one user owner and me
const { getByText } = render(
@ -255,7 +258,7 @@ test('Can open settings', async () => {
});
test('Can open About', async () => {
const openModal = jest.fn();
const openModal = vi.fn();
// Test with one user owner and me
const { getByText } = render(
@ -279,7 +282,7 @@ 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
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`] = `
<DocumentFragment>
@ -11,11 +11,11 @@ exports[`Nav component is displaying 1`] = `
<img
alt="Darkwire"
class="logo"
src="logo.png"
src="/src/img/logo.png"
/>
<button
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-toggle="tooltip"
>
@ -227,7 +227,7 @@ exports[`Nav component is displaying 1`] = `
/>
<line
x1="12"
x2="12"
x2="12.01"
y1="8"
y2="8"
/>
@ -253,11 +253,11 @@ exports[`Nav component is displaying with another configuration and can rerender
<img
alt="Darkwire"
class="logo"
src="logo.png"
src="/src/img/logo.png"
/>
<button
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-toggle="tooltip"
>
@ -515,7 +515,7 @@ exports[`Nav component is displaying with another configuration and can rerender
/>
<line
x1="12"
x2="12"
x2="12.01"
y1="8"
y2="8"
/>

View File

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

View File

@ -1,6 +1,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import Notice from '.';
import { test, expect } from 'vitest';
test('Notice component is displaying', async () => {
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`] = `
<DocumentFragment>

View File

@ -2,24 +2,24 @@ import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import RoomLink from '.';
import mock$ from 'jquery';
import { describe, it, expect, vi, afterEach } from 'vitest';
const mockTooltip = jest.fn().mockImplementation(param => {
// console.log('tooltip', param);
});
const mockTooltip = vi.fn().mockImplementation(param => {});
jest.mock('jquery', () => {
return jest.fn().mockImplementation(param => {
// console.log('$', param);
vi.mock('jquery', () => {
return {
default: vi.fn().mockImplementation(param => {
if (typeof param === 'function') {
param();
}
return {
tooltip: mockTooltip,
};
});
}),
};
});
jest.useFakeTimers();
vi.useFakeTimers();
const mockTranslations = {
copyButtonTooltip: 'copyButton',
@ -47,11 +47,11 @@ describe('RoomLink', () => {
it('should copy link', async () => {
// Mock execCommand for paste
document.execCommand = jest.fn(() => true);
document.execCommand = vi.fn(() => true);
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(mock$).toHaveBeenCalledTimes(4);
@ -59,7 +59,7 @@ describe('RoomLink', () => {
expect(mockTooltip).toHaveBeenLastCalledWith('show');
// Wait for tooltip to close
jest.runAllTimers();
vi.runAllTimers();
expect(mock$).toHaveBeenCalledTimes(6);
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>
<form>
<div
@ -14,14 +14,14 @@ exports[`RoomLink should display 1`] = `
id="room-url"
readonly=""
type="text"
value="http://localhost/roomId"
value="http://localhost:3000/roomId"
/>
<div
class="input-group-append"
>
<button
class="copy-room btn btn-secondary"
data-clipboard-text="http://localhost/roomId"
data-clipboard-text="http://localhost:3000/roomId"
data-placement="bottom"
data-toggle="tooltip"
title="copyButton"

View File

@ -1,6 +1,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import RoomLocked from '.';
import { test, expect } from 'vitest';
test('RoomLocked component should display', () => {
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`] = `
<DocumentFragment>

View File

@ -1,7 +1,9 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import configureStore from 'store';
import { describe, it, expect, vi } from 'vitest';
import configureStore from '@/store';
import Settings from '.';
@ -11,9 +13,24 @@ const mockTranslations = {
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', () => {
it('should display', async () => {
@ -24,7 +41,7 @@ describe('Settings component', () => {
toggleSoundEnabled={() => {}}
notificationIsEnabled={true}
toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()}
toggleNotificationAllowed={vi.fn()}
roomId="roomId"
setLanguage={() => {}}
translations={{}}
@ -42,7 +59,7 @@ describe('Settings component', () => {
notificationIsEnabled={true}
notificationIsAllowed={false}
toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()}
toggleNotificationAllowed={vi.fn()}
roomId="roomId"
setLanguage={() => {}}
translations={{}}
@ -54,7 +71,7 @@ describe('Settings component', () => {
});
it('should toggle sound', async () => {
const toggleSound = jest.fn();
const toggleSound = vi.fn();
const { getByText } = render(
<Provider store={store}>
<Settings
@ -63,7 +80,7 @@ describe('Settings component', () => {
notificationIsEnabled={true}
notificationIsAllowed={true}
toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()}
toggleNotificationAllowed={vi.fn()}
roomId="roomId"
setLanguage={() => {}}
translations={{}}
@ -79,10 +96,10 @@ describe('Settings component', () => {
it('should toggle notifications', async () => {
global.Notification = {
requestPermission: jest.fn().mockResolvedValue('granted'),
requestPermission: vi.fn().mockResolvedValue('granted'),
};
const toggleNotifications = jest.fn();
const toggleNotifications = vi.fn();
const { getByText } = render(
<Provider store={store}>
<Settings
@ -91,7 +108,7 @@ describe('Settings component', () => {
notificationIsEnabled={true}
notificationIsAllowed={true}
toggleNotificationEnabled={toggleNotifications}
toggleNotificationAllowed={jest.fn()}
toggleNotificationAllowed={vi.fn()}
roomId="roomId"
setLanguage={() => {}}
translations={{}}
@ -101,7 +118,7 @@ describe('Settings component', () => {
fireEvent.click(getByText('Desktop Notification'));
jest.runAllTimers();
vi.runAllTimers();
delete global.Notification;
@ -110,11 +127,11 @@ describe('Settings component', () => {
it('should not toggle notifications', async () => {
global.Notification = {
requestPermission: jest.fn().mockResolvedValue('denied'),
requestPermission: vi.fn().mockResolvedValue('denied'),
};
const toggleNotifications = jest.fn();
const toggleAllowed = jest.fn();
const toggleNotifications = vi.fn();
const toggleAllowed = vi.fn();
const { getByText } = render(
<Provider store={store}>
<Settings
@ -133,7 +150,7 @@ describe('Settings component', () => {
fireEvent.click(getByText('Desktop Notification'));
jest.runAllTimers();
vi.runAllTimers();
delete global.Notification;
@ -142,7 +159,7 @@ describe('Settings component', () => {
});
it('should change lang', async () => {
const changeLang = jest.fn();
const changeLang = vi.fn();
const { getByDisplayValue } = render(
<Provider store={store}>
@ -151,7 +168,7 @@ describe('Settings component', () => {
toggleSoundEnabled={() => {}}
notificationIsEnabled={true}
toggleNotificationEnabled={() => {}}
toggleNotificationAllowed={jest.fn()}
toggleNotificationAllowed={vi.fn()}
roomId="roomId"
setLanguage={changeLang}
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>
<div
class="styles"
class="_styles_23b490"
>
<section>
<h4>
@ -127,6 +127,11 @@ exports[`Settings component should display 1`] = `
>
Türkçe
</option>
<option
value="ko"
>
한국어
</option>
</select>
</div>
</section>
@ -192,10 +197,10 @@ exports[`Settings component should display 1`] = `
</DocumentFragment>
`;
exports[`Settings component should display 2`] = `
exports[`Settings component > should display 2`] = `
<DocumentFragment>
<div
class="styles"
class="_styles_23b490"
>
<section>
<h4>
@ -313,6 +318,11 @@ exports[`Settings component should display 2`] = `
>
Türkçe
</option>
<option
value="ko"
>
한국어
</option>
</select>
</div>
</section>

View File

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

View File

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

View File

@ -1,10 +1,11 @@
import React from 'react';
import { render } from '@testing-library/react';
import { test, expect, vi } from 'vitest';
import T from './T';
// To avoid missing provider
jest.mock('components/T');
vi.mock('components/T');
test('T component is displaying', async () => {
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`] = `
<DocumentFragment>

View File

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

View File

@ -1,5 +1,6 @@
import React from 'react';
import { render } from '@testing-library/react';
import { test, expect } from 'vitest';
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`] = `
<DocumentFragment>

View File

@ -1,8 +1,11 @@
import React from 'react';
import { render } from '@testing-library/react';
import { test, expect, vi } from 'vitest';
import Welcome from '.';
vi.mock('@/components/RoomLink');
test('Welcome component is displaying', async () => {
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 PropTypes from 'prop-types';
import RoomLink from 'components/RoomLink';
import RoomLink from '@/components/RoomLink';
class Welcome extends Component {
constructor(props) {

View File

@ -1,2 +1,5 @@
/* 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', () => {
expect(getTranslations('en').welcomeHeader).toBe('Welcome to Darkwire v2.0');

View File

@ -34,11 +34,11 @@ const languagesMap = {
export function getTranslations(language = '') {
const [lang, variant] = language.split('-');
if (languagesMap.hasOwnProperty(`${lang}${variant}`)) {
if (Object.prototype.hasOwnProperty.call(languagesMap, `${lang}${variant}`)) {
return languagesMap[`${lang}${variant}`];
}
if (languagesMap.hasOwnProperty(lang)) {
if (Object.prototype.hasOwnProperty.call(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':
const newUserId = action.payload.payload.id;
const haveUser = action.payload.state.room.members.find(m => m.id === newUserId);
if (haveUser) {
if (action.payload.state.room.members.find(m => m.id === action.payload.payload.id)) {
return state;
}
@ -88,7 +85,7 @@ const activities = (state = initialState, action) => {
items: [
...state.items,
{
userId: newUserId,
userId: action.payload.payload.id,
type: 'USER_ENTER',
username: action.payload.payload.username,
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,10 @@
import { describe, it, expect, vi } from 'vitest';
import reducer from './user';
jest.mock('i18n', () => {
vi.mock('@/i18n', () => {
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 { Redirect } from 'react-router';
import { Provider } from 'react-redux';
import configureStore from 'store';
import configureStore from './store';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import shortId from 'shortid';
import Home from 'components/Home';

View File

@ -20,9 +20,9 @@ const isLocalhost = Boolean(
);
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.
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) {
// 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
@ -31,7 +31,7 @@ export function register(config) {
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
const swUrl = `${import.meta.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// 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 Adapter from 'enzyme-adapter-react-16';
// this adds jest-dom's custom assertions
import '@testing-library/jest-dom/extend-expect';
import { enableFetchMocks } from 'jest-fetch-mock';
import { expect, afterEach, vi } from 'vitest';
import { cleanup } from '@testing-library/react';
import matchers from '@testing-library/jest-dom/extend-expect';
import createFetchMock from 'vitest-fetch-mock';
configure({ adapter: new Adapter() });
enableFetchMocks();
const fetchMock = createFetchMock(vi);
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 */
import { createStore, applyMiddleware, compose } from 'redux';
import reducers from 'reducers';
import thunk from 'redux-thunk';
import reducers from '@/reducers';
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];
@ -12,14 +13,5 @@ const middlewares = applyMiddleware(...enabledMiddlewares);
export default function configureStore(preloadedState) {
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;
}

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 = () => {
let text = '';
if (typeof window.getSelection !== 'undefined') {

View File

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

View File

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

View File

@ -2,22 +2,19 @@ import Crypto from './crypto';
const crypto = new Crypto();
export const process = (payload, state) =>
new Promise(async (resolve, reject) => {
export const process = async (payload, state) => {
const privateKeyJson = state.user.privateKey;
const privateKey = await crypto.importEncryptDecryptKey(privateKeyJson, 'jwk', ['decrypt', 'unwrapKey']);
let sessionKey;
let signingKey;
const iv = await crypto.convertStringToArrayBufferView(payload.iv);
const signature = await crypto.convertStringToArrayBufferView(payload.signature);
const payloadBuffer = await crypto.convertStringToArrayBufferView(payload.payload);
await new Promise(resolvePayload => {
// We try to decrypt all sessions and signin keys to get the one encrypted for self
const [sessionKey, signingKey] = await new Promise(resolvePayload => {
payload.keys.forEach(async key => {
try {
sessionKey = await crypto.unwrapKey(
const sessionKey = await crypto.unwrapKey(
'jwk',
key.sessionKey,
privateKey,
@ -30,7 +27,7 @@ export const process = (payload, state) =>
['decrypt'],
);
signingKey = await crypto.unwrapKey(
const signingKey = await crypto.unwrapKey(
'jwk',
key.signingKey,
privateKey,
@ -42,7 +39,7 @@ export const process = (payload, state) =>
true,
['verify'],
);
resolvePayload();
resolvePayload([sessionKey, signingKey]);
} catch (e) {} // eslint-disable-line
});
});
@ -50,19 +47,17 @@ export const process = (payload, state) =>
const verified = await crypto.verifyPayload(signature, payloadBuffer, signingKey);
if (!verified) {
reject();
return;
throw new Error("Can't verify message");
}
const decryptedPayload = await crypto.decryptMessage(payloadBuffer, sessionKey, iv);
const payloadJson = JSON.parse(crypto.convertArrayBufferViewToString(new Uint8Array(decryptedPayload)));
resolve(payloadJson);
});
return payloadJson;
};
export const prepare = (payload, state) =>
new Promise(async resolve => {
export const prepare = async (payload, state) => {
const myUsername = state.user.username;
const myId = state.user.id;
@ -101,7 +96,7 @@ export const prepare = (payload, state) =>
const ivString = await crypto.convertArrayBufferViewToString(new Uint8Array(iv));
const signatureString = await crypto.convertArrayBufferViewToString(new Uint8Array(signature));
resolve({
return {
toSend: {
payload: payloadString,
signature: signatureString,
@ -109,5 +104,5 @@ export const prepare = (payload, state) =>
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 = {
body: message,
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 };

View File

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

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