feat: add TestConnectionInfo for mobile
Adds TestConnectionInfo component which exposes some internal app state to the jitsi-meet-torture through the UI accessibility layer. This component will render only if config.testing.testMode is set to true.
This commit is contained in:
parent
461540d874
commit
4d942440db
|
@ -57,6 +57,9 @@ var config = {
|
||||||
// P2P test mode disables automatic switching to P2P when there are 2
|
// P2P test mode disables automatic switching to P2P when there are 2
|
||||||
// participants in the conference.
|
// participants in the conference.
|
||||||
p2pTestMode: false
|
p2pTestMode: false
|
||||||
|
|
||||||
|
// Enables the test specific features consumed by jitsi-meet-torture
|
||||||
|
// testMode: false
|
||||||
},
|
},
|
||||||
|
|
||||||
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
// Disables ICE/UDP by filtering out local and remote UDP candidates in
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { createDesiredLocalTracks } from '../../base/tracks';
|
||||||
import { ConferenceNotification } from '../../calendar-sync';
|
import { ConferenceNotification } from '../../calendar-sync';
|
||||||
import { Filmstrip } from '../../filmstrip';
|
import { Filmstrip } from '../../filmstrip';
|
||||||
import { LargeVideo } from '../../large-video';
|
import { LargeVideo } from '../../large-video';
|
||||||
|
import { TestConnectionInfo } from '../../testing';
|
||||||
import { setToolboxVisible, Toolbox } from '../../toolbox';
|
import { setToolboxVisible, Toolbox } from '../../toolbox';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -233,6 +234,7 @@ class Conference extends Component<Props> {
|
||||||
*/}
|
*/}
|
||||||
<Filmstrip />
|
<Filmstrip />
|
||||||
</View>
|
</View>
|
||||||
|
<TestConnectionInfo />
|
||||||
|
|
||||||
<ConferenceNotification />
|
<ConferenceNotification />
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* The type of redux action which sets the configuration of the feature
|
||||||
|
* base/logging.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_CONNECTION_STATE
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_CONNECTION_STATE = Symbol('SET_CONNECTION_STATE');
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { SET_CONNECTION_STATE } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the conference connection state of the testing feature.
|
||||||
|
*
|
||||||
|
* @param {string} connectionState - This is the lib-jitsi-meet event name. Can
|
||||||
|
* be on of:
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_CONNECTION_STATE,
|
||||||
|
* connectionState: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setConnectionState(connectionState: string) {
|
||||||
|
return {
|
||||||
|
type: SET_CONNECTION_STATE,
|
||||||
|
connectionState
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the {@link TestHint}'s properties.
|
||||||
|
*
|
||||||
|
* A test hint is meant to resemble the lack of the ability to execute
|
||||||
|
* JavaScript by the mobile torture tests. They are used to expose some of
|
||||||
|
* the app's internal state that is not always expressed in a feasible manner by
|
||||||
|
* the UI.
|
||||||
|
*/
|
||||||
|
export type TestHintProps = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test hint's identifier string. Must be unique in the app instance
|
||||||
|
* scope.
|
||||||
|
*/
|
||||||
|
id: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The test hint's (text) value which is to be consumed by the tests.
|
||||||
|
*/
|
||||||
|
value: string
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
|
||||||
|
import { Fragment } from '../../base/react';
|
||||||
|
import { getLocalParticipant } from '../../base/participants';
|
||||||
|
import { statsEmitter } from '../../connection-indicator';
|
||||||
|
|
||||||
|
import { TestHint } from './index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the TestConnectionInfo's properties.
|
||||||
|
*/
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JitsiConference's connection state. It's the lib-jitsi-meet's event
|
||||||
|
* name converted to a string directly. At the time of this writing these
|
||||||
|
* are the possible values:
|
||||||
|
* 'conference.connectionEstablished'
|
||||||
|
* 'conference.connectionInterrupted'
|
||||||
|
* 'conference.connectionRestored'
|
||||||
|
*/
|
||||||
|
_conferenceConnectionState: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will be a boolean converted to a string. The value will be 'true'
|
||||||
|
* once the conference is joined (the XMPP MUC room to be specific).
|
||||||
|
*/
|
||||||
|
_conferenceJoinedState: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local participant's ID. Required to be able to observe the local RTP
|
||||||
|
* stats.
|
||||||
|
*/
|
||||||
|
_localUserId: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the test mode is currently on. Otherwise the
|
||||||
|
* TestConnectionInfo component will not render.
|
||||||
|
*/
|
||||||
|
_testMode: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the TestConnectionInfo's state.
|
||||||
|
*/
|
||||||
|
type State = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The RTP stats section.
|
||||||
|
*/
|
||||||
|
stats: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local bitrate.
|
||||||
|
*/
|
||||||
|
bitrate: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local download RTP bitrate.
|
||||||
|
*/
|
||||||
|
download: number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local upload RTP bitrate.
|
||||||
|
*/
|
||||||
|
upload: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component will expose some of the app state to the jitsi-meet-torture
|
||||||
|
* through the UI accessibility layer which is visible to the tests. The Web
|
||||||
|
* tests currently will execute JavaScript and access globals variables to learn
|
||||||
|
* this information, but there's no such option on React Native(maybe that's
|
||||||
|
* a good thing).
|
||||||
|
*/
|
||||||
|
class TestConnectionInfo extends Component<Props, State> {
|
||||||
|
|
||||||
|
_onStatsUpdated: Object => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes new <tt>TestConnectionInfo</tt> instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._onStatsUpdated = this._onStatsUpdated.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
stats: {
|
||||||
|
bitrate: {
|
||||||
|
download: 0,
|
||||||
|
upload: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link statsEmitter} callback hoked up for the local participant.
|
||||||
|
*
|
||||||
|
* @param {Object} stats - These are the RTP stats. Look in
|
||||||
|
* the lib-jitsi-meet for more details on the actual structure or add
|
||||||
|
* a console print and figure out there.
|
||||||
|
* @returns {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onStatsUpdated(stats = {}) {
|
||||||
|
this.setState({
|
||||||
|
stats: {
|
||||||
|
bitrate: {
|
||||||
|
download: stats.bitrate.download,
|
||||||
|
upload: stats.bitrate.upload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts listening for the local RTP stat updates.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* returns {void}
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
statsEmitter.subscribeToClientStats(
|
||||||
|
this.props._localUserId, this._onStatsUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates which user's stats are being listened to (the local participant's
|
||||||
|
* id changes).
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* returns {void}
|
||||||
|
*/
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps._localUserId !== this.props._localUserId) {
|
||||||
|
statsEmitter.unsubscribeToClientStats(
|
||||||
|
prevProps._localUserId, this._onStatsUpdated);
|
||||||
|
statsEmitter.subscribeToClientStats(
|
||||||
|
this.props._localUserId, this._onStatsUpdated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the local stats listener.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
statsEmitter.unsubscribeToClientStats(
|
||||||
|
this.props._localUserId, this._onStatsUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the component if the app is currently running in the test mode
|
||||||
|
* (config.testing.testMode == true).
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
if (!this.props._testMode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment accessible = { false } >
|
||||||
|
<TestHint
|
||||||
|
id = 'org.jitsi.meet.conference.connectionState'
|
||||||
|
value = { this.props._conferenceConnectionState } />
|
||||||
|
<TestHint
|
||||||
|
id = 'org.jitsi.meet.conference.joinedState'
|
||||||
|
value = { this.props._conferenceJoinedState } />
|
||||||
|
<TestHint
|
||||||
|
id = 'org.jitsi.meet.stats.rtp'
|
||||||
|
value = { JSON.stringify(this.state.stats) } />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated TestConnectionInfo's props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _conferenceConnectionState: string,
|
||||||
|
* _conferenceJoinedState: string,
|
||||||
|
* _localUserId: string,
|
||||||
|
* _testMode: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const conferenceJoined
|
||||||
|
= Boolean(state['features/base/conference'].conference);
|
||||||
|
const localParticipant = getLocalParticipant(state);
|
||||||
|
|
||||||
|
const testingConfig = state['features/base/config'].testing;
|
||||||
|
const testMode = Boolean(testingConfig && testingConfig.testMode);
|
||||||
|
|
||||||
|
return {
|
||||||
|
_conferenceConnectionState: state['features/testing'].connectionState,
|
||||||
|
_conferenceJoinedState: conferenceJoined.toString(),
|
||||||
|
_localUserId: localParticipant && localParticipant.id,
|
||||||
|
_testMode: testMode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(TestConnectionInfo);
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import type { TestHintProps } from './AbstractTestHint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Android version of <code>TestHint</code>. It will put the identifier,
|
||||||
|
* as the 'accessibilityLabel'.
|
||||||
|
*
|
||||||
|
* FIXME The 'testID' attribute (which is used on iOS) does not work with
|
||||||
|
* the react-native as expected, because is mapped to component's tag instead of
|
||||||
|
* any attribute visible to the UI automation. Because of that it can not be
|
||||||
|
* used to find the element.
|
||||||
|
* On the other hand it's not possible to use 'accessibilityLabel' on the iOS
|
||||||
|
* for the id purpose, because it will merge the value with any text content or
|
||||||
|
* 'accessibilityLabel' values of it's children. So as a workaround a TestHint
|
||||||
|
* class was introduced in 'jitsi-meet-torture' which will accept generic 'id'
|
||||||
|
* attribute and then do the search 'under the hood' either by the accessibility
|
||||||
|
* label or the id, depending on the participant's platform. On the client side
|
||||||
|
* the TestHint class is to be the abstraction layer which masks the problem by
|
||||||
|
* exposing id and value properties.
|
||||||
|
*/
|
||||||
|
export default class TestHint extends Component<TestHintProps> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the test hint on Android.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Text accessibilityLabel = { this.props.id } >
|
||||||
|
{ this.props.value }
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import type { TestHintProps } from './AbstractTestHint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the iOS version of the TestHint.
|
||||||
|
*
|
||||||
|
* Be sure to check the description in TestHint.android and AbstractTestHint
|
||||||
|
* files to understand what a test hint is and why different iOS and Android
|
||||||
|
* components are necessary.
|
||||||
|
*/
|
||||||
|
export default class TestHint extends Component<TestHintProps> {
|
||||||
|
/**
|
||||||
|
* Renders the test hint on Android.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
accessibilityLabel = { this.props.value }
|
||||||
|
testID = { this.props.id } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as TestConnectionInfo } from './TestConnectionInfo';
|
||||||
|
export { default as TestHint } from './TestHint';
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './components';
|
||||||
|
|
||||||
|
import './middleware';
|
||||||
|
import './reducer';
|
|
@ -0,0 +1,80 @@
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import Logger from 'jitsi-meet-logger';
|
||||||
|
|
||||||
|
const logger = Logger.getLogger(__filename);
|
||||||
|
|
||||||
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import { CONFERENCE_WILL_JOIN } from '../base/conference';
|
||||||
|
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||||
|
import { setConnectionState } from './actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Redux middleware of the feature testing.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The Redux store.
|
||||||
|
* @returns {Function}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
switch (action.type) {
|
||||||
|
case CONFERENCE_WILL_JOIN:
|
||||||
|
_bindConferenceConnectionListener(action.conference, store);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a handler which will listen for the connection related conference
|
||||||
|
* events (in the lib-jitsi-meet internals those are associated with the ICE
|
||||||
|
* connection state).
|
||||||
|
*
|
||||||
|
* @param {JitsiConference} conference - The {@link JitsiConference} for which
|
||||||
|
* the conference will join even is dispatched.
|
||||||
|
* @param {Store} store - The redux store in which the specified action is being
|
||||||
|
* dispatched.
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _bindConferenceConnectionListener(conference, { dispatch }) {
|
||||||
|
|
||||||
|
conference.on(
|
||||||
|
JitsiConferenceEvents.CONNECTION_ESTABLISHED,
|
||||||
|
_onConnectionEvent.bind(
|
||||||
|
null, JitsiConferenceEvents.CONNECTION_ESTABLISHED, dispatch));
|
||||||
|
conference.on(
|
||||||
|
JitsiConferenceEvents.CONNECTION_RESTORED,
|
||||||
|
_onConnectionEvent.bind(
|
||||||
|
null, JitsiConferenceEvents.CONNECTION_RESTORED, dispatch));
|
||||||
|
conference.on(
|
||||||
|
JitsiConferenceEvents.CONNECTION_INTERRUPTED,
|
||||||
|
_onConnectionEvent.bind(
|
||||||
|
null, JitsiConferenceEvents.CONNECTION_INTERRUPTED, dispatch));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The handler function for conference connection events which wil store the
|
||||||
|
* latest even name in the Redux store of feature testing.
|
||||||
|
*
|
||||||
|
* @param {string} event - One of the lib-jitsi-meet JitsiConferenceEvents.
|
||||||
|
* @param {Function} dispatch - The dispatch function of the current Redux
|
||||||
|
* store.
|
||||||
|
* @returns {void}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _onConnectionEvent(event, dispatch) {
|
||||||
|
switch (event) {
|
||||||
|
case JitsiConferenceEvents.CONNECTION_ESTABLISHED:
|
||||||
|
case JitsiConferenceEvents.CONNECTION_INTERRUPTED:
|
||||||
|
case JitsiConferenceEvents.CONNECTION_RESTORED:
|
||||||
|
dispatch(setConnectionState(event));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.error(`onConnectionEvent - unsupported event type: ${event}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { assign, ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import { SET_CONNECTION_STATE } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial state of the feature testing.
|
||||||
|
*
|
||||||
|
* @type {{
|
||||||
|
* connectionState: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
const INITIAL_STATE = {
|
||||||
|
connectionState: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
ReducerRegistry.register(
|
||||||
|
'features/testing',
|
||||||
|
(state = INITIAL_STATE, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_CONNECTION_STATE:
|
||||||
|
return _setConnectionState(state, action);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces a specific Redux action SET_CONNECTION_STATE of the feature
|
||||||
|
* testing.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature base/logging.
|
||||||
|
* @param {Action} action - The Redux action SET_CONNECTION_STATE to reduce.
|
||||||
|
* @private
|
||||||
|
* @returns {Object} The new state of the feature testing after the
|
||||||
|
* reduction of the specified action.
|
||||||
|
*/
|
||||||
|
function _setConnectionState(state, action) {
|
||||||
|
return assign(state, { connectionState: action.connectionState });
|
||||||
|
}
|
Loading…
Reference in New Issue