Skip to content

Commit

Permalink
Video 10586 synk pr (#733)
Browse files Browse the repository at this point in the history
  • Loading branch information
olipyskoty committed Jul 22, 2022
1 parent 6e481aa commit ffea3e1
Show file tree
Hide file tree
Showing 31 changed files with 38,503 additions and 35,814 deletions.
6 changes: 6 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module.exports = {
roots: ['<rootDir>/src', '<rootDir>/server'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
"^.+\\.(css)$": "<rootDir>/jest.transform.js"
},
transformIgnorePatterns: ["/node_modules/(?!swiper|swiper/react|ssr-window|dom7)"],
testEnvironment: 'jsdom',
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
snapshotSerializers: ['enzyme-to-json/serializer'],
Expand All @@ -13,5 +16,8 @@ module.exports = {
coveragePathIgnorePatterns: ['node_modules', 'src/icons'],
moduleNameMapper: {
'.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': '<rootDir>/src/__mocks__/fileMock.ts',
"swiper/react": "<rootDir>/node_modules/swiper/react/swiper-react.js",
"swiper/css": "<rootDir>/node_modules/swiper/swiper.min.css",
"swiper/css/pagination": "<rootDir>/node_modules/swiper/modules/autoplay/pagination.min.css"
},
};
6 changes: 6 additions & 0 deletions jest.transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const path = require("path")

// Requried to fix Swiper CSS imports during jest executions, it transforms imports into filenames
module.exports = {
process: (_src, filename) => `module.exports = ${JSON.stringify(path.basename(filename))};`
}
74,035 changes: 38,341 additions & 35,694 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.60",
"@twilio-labs/plugin-rtc": "^0.8.2",
"@twilio/conversations": "^1.2.3",
"@twilio/conversations": "^2.1.0",
"@twilio/video-processors": "^1.0.1",
"@twilio/video-room-monitor": "^1.0.0",
"@types/d3-timer": "^1.0.9",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.11",
"@types/fscreen": "^1.0.1",
"@types/jest": "^25.1.0",
"@types/jest": "^27.5.2",
"@types/linkify-it": "^3.0.0",
"@types/lodash.throttle": "^4.1.6",
"@types/node": "^12.12.26",
Expand All @@ -27,7 +27,7 @@
"cross-env": "^7.0.2",
"d3-timer": "^1.0.10",
"express": "^4.17.1",
"firebase": "^7.24.0",
"firebase": "^9.9.0",
"firebase-admin": "^9.5.0",
"fscreen": "^1.0.2",
"husky": "^3.1.0",
Expand All @@ -39,7 +39,7 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "4.0.3",
"react-scripts": "5.0.0",
"rimraf": "3.0.2",
"strip-color": "^0.1.0",
"swiper": "^8.1.5",
Expand Down Expand Up @@ -73,7 +73,7 @@
"puppeteer": "^5.3.1",
"react-test-renderer": "^16.12.0",
"start-server-and-test": "^1.10.8",
"ts-jest": "^26.5.1"
"ts-jest": "^27.0.1"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
Expand All @@ -83,15 +83,15 @@
},
"scripts": {
"postinstall": "rimraf public/virtualbackground && copyfiles -f node_modules/@twilio/video-processors/dist/build/* public/virtualbackground",
"start": "concurrently npm:server npm:dev",
"start": "npx cross-env REACT_APP_VERSION=$(node -e \"console.log(require('./package.json').version)\") concurrently npm:server npm:dev",
"dev": "react-scripts start",
"build": "node ./scripts/build.js",
"test": "cross-env TZ=utc jest",
"test": "cross-env TZ=utc jest --config jest.config.js",
"eject": "react-scripts eject",
"lint": "eslint src server",
"server": "ts-node -T -P server/tsconfig.json server/index.ts",
"typescript:server": "tsc --noEmit -p server/",
"test:ci": "cross-env TZ=utc jest --ci --runInBand --reporters=default --reporters=jest-junit --coverage --silent",
"test:ci": "cross-env TZ=utc jest --config jest.config.js --ci --runInBand --reporters=default --reporters=jest-junit --coverage --silent",
"cypress:open": "cypress open",
"cypress:run": "cypress run --browser chrome",
"cypress:ci": "cross-env CYPRESS_baseUrl=http://localhost:8081 start-server-and-test server http://localhost:8081 cypress:run",
Expand Down
3 changes: 2 additions & 1 deletion scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ You may serve it with a static server:
Find out more about deployment here:
bit.ly/CRA-deploy
https://cra.link/deployment
`;

Expand All @@ -40,6 +40,7 @@ class Filter extends Transform {

// Colors normally don't work when using spawn(), so here we re-enable colors.
process.env.FORCE_COLOR = require('supports-color').stdout.level;
process.env.REACT_APP_VERSION = require(__dirname + "/../package.json").version

const buildProcess = spawn('node', [require.resolve('react-scripts/scripts/build')]);

Expand Down
2 changes: 1 addition & 1 deletion src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { shallow } from 'enzyme';
import useHeight from './hooks/useHeight/useHeight';
import useRoomState from './hooks/useRoomState/useRoomState';

jest.mock('swiper/react/swiper-react.js', () => ({
jest.mock('swiper/react', () => ({
Swiper: jest.fn(),
SwiperSlide: jest.fn(),
}));
Expand Down
14 changes: 0 additions & 14 deletions src/__mocks__/@twilio/conversations.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/AboutDialog/AboutDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { render } from '@testing-library/react';
import { useAppState } from '../../state';

jest.mock('twilio-video', () => ({ version: '1.2', isSupported: true }));
jest.mock('../../../package.json', () => ({ version: '1.3' }));
jest.mock('../../state');

const mockUseAppState = useAppState as jest.Mock<any>;
Expand All @@ -22,6 +21,7 @@ describe('the AboutDialog component', () => {
});

it('should display the package.json version', () => {
process.env.REACT_APP_VERSION = '1.3';
const { getByText } = render(<AboutDialog open={true} onClose={() => {}} />);
expect(getByText('App Version: 1.3')).toBeTruthy();
});
Expand Down
3 changes: 1 addition & 2 deletions src/components/AboutDialog/AboutDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import Divider from '@material-ui/core/Divider';

import { version as appVersion } from '../../../package.json';
import Video from 'twilio-video';
import { useAppState } from '../../state';

Expand All @@ -25,7 +24,7 @@ function AboutDialog({ open, onClose }: PropsWithChildren<AboutDialogProps>) {
<DialogContent>
<DialogContentText>Browser supported: {String(Video.isSupported)}</DialogContentText>
<DialogContentText>SDK Version: {Video.version}</DialogContentText>
<DialogContentText>App Version: {appVersion}</DialogContentText>
<DialogContentText>App Version: {process.env.REACT_APP_VERSION}</DialogContentText>
<DialogContentText>Deployed Tag: {process.env.REACT_APP_GIT_TAG || 'N/A'}</DialogContentText>
<DialogContentText>Deployed Commit Hash: {process.env.REACT_APP_GIT_COMMIT || 'N/A'}</DialogContentText>
{roomType && <DialogContentText>Room Type: {roomType}</DialogContentText>}
Expand Down
97 changes: 64 additions & 33 deletions src/components/ChatProvider/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import React from 'react';
import { act, renderHook } from '@testing-library/react-hooks';
import { ChatProvider } from './index';
import { Client } from '@twilio/conversations';
import { mockConversation, mockClient } from '../../__mocks__/@twilio/conversations';
import useChatContext from '../../hooks/useChatContext/useChatContext';
import useVideoContext from '../../hooks/useVideoContext/useVideoContext';
import { setImmediate } from 'timers';
import EventEmitter from 'events';

jest.mock('@twilio/conversations');
const mockConversation: any = new EventEmitter();
mockConversation.getMessages = jest.fn(() => Promise.resolve({ items: ['mockMessage'] }));

const mockConversationsClient: any = new EventEmitter();
mockConversationsClient.getConversationByUniqueName = jest.fn(() => Promise.resolve(mockConversation));

jest.mock('@twilio/conversations', () => {
return { Client: jest.fn(() => mockConversationsClient) };
});
jest.mock('../../hooks/useVideoContext/useVideoContext');

const mockUseVideoContext = useVideoContext as jest.Mock<any>;
const mockOnError = jest.fn();

const mockClientCreate = Client.create as jest.Mock<any>;

const mockRoom = { sid: 'mockRoomSid' };

const wrapper: React.FC = ({ children }) => <ChatProvider>{children}</ChatProvider>;

describe('the ChatProvider component', () => {
Expand All @@ -25,12 +31,13 @@ describe('the ChatProvider component', () => {

it('should return a Conversation after connect has been called and after a room exists', async () => {
// Setup mock as if user is not connected to a room
mockUseVideoContext.mockImplementation(() => ({}));
mockUseVideoContext.mockImplementation(() => ({ onError: mockOnError }));
const { result, rerender, waitForNextUpdate } = renderHook(useChatContext, { wrapper });

result.current.connect('mockToken');
await waitForNextUpdate();
expect(mockClientCreate).toHaveBeenCalledWith('mockToken');
await act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});

// conversation should be null as there is no room
expect(result.current.conversation).toBe(null);
Expand All @@ -41,21 +48,27 @@ describe('the ChatProvider component', () => {
rerender();
await waitForNextUpdate();

expect(mockClient.getConversationByUniqueName).toHaveBeenCalledWith('mockRoomSid');
expect(mockConversationsClient.getConversationByUniqueName).toHaveBeenCalledWith('mockRoomSid');
expect(result.current.conversation).toBe(mockConversation);
});

it('should load all messages after obtaining a conversation', async () => {
const { result, waitForNextUpdate } = renderHook(useChatContext, { wrapper });
result.current.connect('mockToken');
act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});
await waitForNextUpdate();

expect(result.current.messages).toEqual(['mockMessage']);
});

it('should add new messages to the "messages" array', async () => {
const { result, waitForNextUpdate } = renderHook(useChatContext, { wrapper });
result.current.connect('mockToken');
act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});
await waitForNextUpdate();

act(() => {
Expand All @@ -70,7 +83,10 @@ describe('the ChatProvider component', () => {

expect(result.current.hasUnreadMessages).toBe(false);

result.current.connect('mockToken');
act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});
await waitForNextUpdate();

expect(result.current.hasUnreadMessages).toBe(true);
Expand All @@ -84,7 +100,10 @@ describe('the ChatProvider component', () => {
result.current.setIsChatWindowOpen(true);
});

result.current.connect('mockToken');
act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});
await waitForNextUpdate();

expect(result.current.hasUnreadMessages).toBe(false);
Expand All @@ -94,7 +113,10 @@ describe('the ChatProvider component', () => {
// Setup mock so that no messages are loaded after a conversation is obtained.
mockConversation.getMessages.mockImplementationOnce(() => Promise.resolve({ items: [] }));
const { result, waitForNextUpdate } = renderHook(useChatContext, { wrapper });
result.current.connect('mockToken');
act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});
await waitForNextUpdate();

expect(result.current.hasUnreadMessages).toBe(false);
Expand All @@ -110,7 +132,10 @@ describe('the ChatProvider component', () => {
// Setup mock so that no messages are loaded after a conversation is obtained.
mockConversation.getMessages.mockImplementationOnce(() => Promise.resolve({ items: [] }));
const { result, waitForNextUpdate } = renderHook(useChatContext, { wrapper });
result.current.connect('mockToken');
act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});
await waitForNextUpdate();

expect(result.current.hasUnreadMessages).toBe(false);
Expand All @@ -126,39 +151,45 @@ describe('the ChatProvider component', () => {

it('should set hasUnreadMessages to false when the chat window is opened', async () => {
const { result, waitForNextUpdate } = renderHook(useChatContext, { wrapper });
result.current.connect('mockToken');
act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});
await waitForNextUpdate();

expect(result.current.hasUnreadMessages).toBe(true);

act(() => {
result.current.setIsChatWindowOpen(true);
});

expect(result.current.hasUnreadMessages).toBe(false);
});

it('should call onError when there is an error connecting with the conversations client', done => {
mockClientCreate.mockImplementationOnce(() => Promise.reject('mockError'));
it('should call onError when there is an error connecting with the conversations client', () => {
const { result } = renderHook(useChatContext, { wrapper });

result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'failed');

expect(mockOnError).toHaveBeenCalledWith(
new Error("There was a problem connecting to Twilio's conversation service.")
);
});

it('should call onError when there is an error getting the conversation', done => {
mockConversationsClient.getConversationByUniqueName.mockImplementationOnce(() => Promise.reject('mockError'));
const { result } = renderHook(useChatContext, { wrapper });

act(() => {
result.current.connect('mockToken');
mockConversationsClient.emit('stateChanged', 'initialized');
});

setImmediate(() => {
expect(mockOnError).toHaveBeenCalledWith(
new Error("There was a problem connecting to Twilio's conversation service.")
new Error('There was a problem getting the Conversation associated with this room.')
);
done();
});
});

it('should call onError when there is an error obtaining the conversation', async () => {
mockClient.getConversationByUniqueName.mockImplementationOnce(() => Promise.reject('mockError'));
const { result, waitForNextUpdate } = renderHook(useChatContext, { wrapper });
result.current.connect('mockToken');
await waitForNextUpdate();

expect(mockOnError).toHaveBeenCalledWith(
new Error('There was a problem getting the Conversation associated with this room.')
);
});
});
25 changes: 16 additions & 9 deletions src/components/ChatProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { createContext, useCallback, useEffect, useRef, useState } from 'react';
import { Client } from '@twilio/conversations';
import { Conversation } from '@twilio/conversations/lib/conversation';
import { Message } from '@twilio/conversations/lib/message';
import { Conversation } from '@twilio/conversations/';
import { Message } from '@twilio/conversations/';
import useVideoContext from '../../hooks/useVideoContext/useVideoContext';

type ChatContextType = {
Expand All @@ -26,16 +26,23 @@ export const ChatProvider: React.FC = ({ children }) => {

const connect = useCallback(
(token: string) => {
Client.create(token)
.then(client => {
//@ts-ignore
const client = new Client(token);

const handleClientInitialized = (state: string) => {
if (state === 'initialized') {
// @ts-ignore
window.chatClient = client;
setChatClient(client);
})
.catch(e => {
console.error(e);
} else if (state === 'failed') {
onError(new Error("There was a problem connecting to Twilio's conversation service."));
});
}
};

client.on('stateChanged', handleClientInitialized);

return () => {
client.off('stateChanged', handleClientInitialized);
};
},
[onError]
);
Expand Down
Loading

0 comments on commit ffea3e1

Please sign in to comment.