Kaynağa Gözat

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.
master
paweldomas 7 yıl önce
ebeveyn
işleme
4d942440db

+ 3
- 0
config.js Dosyayı Görüntüle

@@ -57,6 +57,9 @@ var config = {
57 57
         // P2P test mode disables automatic switching to P2P when there are 2
58 58
         // participants in the conference.
59 59
         p2pTestMode: false
60
+
61
+        // Enables the test specific features consumed by jitsi-meet-torture
62
+        // testMode: false
60 63
     },
61 64
 
62 65
     // Disables ICE/UDP by filtering out local and remote UDP candidates in

+ 2
- 0
react/features/conference/components/Conference.native.js Dosyayı Görüntüle

@@ -15,6 +15,7 @@ import { createDesiredLocalTracks } from '../../base/tracks';
15 15
 import { ConferenceNotification } from '../../calendar-sync';
16 16
 import { Filmstrip } from '../../filmstrip';
17 17
 import { LargeVideo } from '../../large-video';
18
+import { TestConnectionInfo } from '../../testing';
18 19
 import { setToolboxVisible, Toolbox } from '../../toolbox';
19 20
 
20 21
 import styles from './styles';
@@ -233,6 +234,7 @@ class Conference extends Component<Props> {
233 234
                       */}
234 235
                     <Filmstrip />
235 236
                 </View>
237
+                <TestConnectionInfo />
236 238
 
237 239
                 <ConferenceNotification />
238 240
 

+ 9
- 0
react/features/testing/actionTypes.js Dosyayı Görüntüle

@@ -0,0 +1,9 @@
1
+/**
2
+ * The type of redux action which sets the configuration of the feature
3
+ * base/logging.
4
+ *
5
+ * {
6
+ *     type: SET_CONNECTION_STATE
7
+ * }
8
+ */
9
+export const SET_CONNECTION_STATE = Symbol('SET_CONNECTION_STATE');

+ 20
- 0
react/features/testing/actions.js Dosyayı Görüntüle

@@ -0,0 +1,20 @@
1
+/* @flow */
2
+
3
+import { SET_CONNECTION_STATE } from './actionTypes';
4
+
5
+/**
6
+ * Sets the conference connection state of the testing feature.
7
+ *
8
+ * @param {string} connectionState - This is the lib-jitsi-meet event name. Can
9
+ * be on of:
10
+ * @returns {{
11
+ *     type: SET_CONNECTION_STATE,
12
+ *     connectionState: string
13
+ * }}
14
+ */
15
+export function setConnectionState(connectionState: string) {
16
+    return {
17
+        type: SET_CONNECTION_STATE,
18
+        connectionState
19
+    };
20
+}

+ 23
- 0
react/features/testing/components/AbstractTestHint.js Dosyayı Görüntüle

@@ -0,0 +1,23 @@
1
+/* @flow */
2
+
3
+/**
4
+ * Describes the {@link TestHint}'s properties.
5
+ *
6
+ * A test hint is meant to resemble the lack of the ability to execute
7
+ * JavaScript by the mobile torture tests. They are used to expose some of
8
+ * the app's internal state that is not always expressed in a feasible manner by
9
+ * the UI.
10
+ */
11
+export type TestHintProps = {
12
+
13
+    /**
14
+     * The test hint's identifier string. Must be unique in the app instance
15
+     * scope.
16
+     */
17
+    id: string,
18
+
19
+    /**
20
+     * The test hint's (text) value which is to be consumed by the tests.
21
+     */
22
+    value: string
23
+}

+ 221
- 0
react/features/testing/components/TestConnectionInfo.js Dosyayı Görüntüle

@@ -0,0 +1,221 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+
7
+import { Fragment } from '../../base/react';
8
+import { getLocalParticipant } from '../../base/participants';
9
+import { statsEmitter } from '../../connection-indicator';
10
+
11
+import { TestHint } from './index';
12
+
13
+/**
14
+ * Defines the TestConnectionInfo's properties.
15
+ */
16
+type Props = {
17
+
18
+    /**
19
+     * The JitsiConference's connection state. It's the lib-jitsi-meet's event
20
+     * name converted to a string directly. At the time of this writing these
21
+     * are the possible values:
22
+     * 'conference.connectionEstablished'
23
+     * 'conference.connectionInterrupted'
24
+     * 'conference.connectionRestored'
25
+     */
26
+    _conferenceConnectionState: string,
27
+
28
+    /**
29
+     * This will be a boolean converted to a string. The value will be 'true'
30
+     * once the conference is joined (the XMPP MUC room to be specific).
31
+     */
32
+    _conferenceJoinedState: string,
33
+
34
+    /**
35
+     * The local participant's ID. Required to be able to observe the local RTP
36
+     * stats.
37
+     */
38
+    _localUserId: string,
39
+
40
+    /**
41
+     * Indicates whether or not the test mode is currently on. Otherwise the
42
+     * TestConnectionInfo component will not render.
43
+     */
44
+    _testMode: boolean
45
+}
46
+
47
+/**
48
+ * Describes the TestConnectionInfo's state.
49
+ */
50
+type State = {
51
+
52
+    /**
53
+     * The RTP stats section.
54
+     */
55
+    stats: {
56
+
57
+        /**
58
+         * The local bitrate.
59
+         */
60
+        bitrate: {
61
+
62
+            /**
63
+             * The local download RTP bitrate.
64
+             */
65
+            download: number,
66
+
67
+            /**
68
+             * The local upload RTP bitrate.
69
+             */
70
+            upload: number
71
+        }
72
+    }
73
+}
74
+
75
+/**
76
+ * The component will expose some of the app state to the jitsi-meet-torture
77
+ * through the UI accessibility layer which is visible to the tests. The Web
78
+ * tests currently will execute JavaScript and access globals variables to learn
79
+ * this information, but there's no such option on React Native(maybe that's
80
+ * a good thing).
81
+ */
82
+class TestConnectionInfo extends Component<Props, State> {
83
+
84
+    _onStatsUpdated: Object => void
85
+
86
+    /**
87
+     * Initializes new <tt>TestConnectionInfo</tt> instance.
88
+     *
89
+     * @param {Object} props - The read-only properties with which the new
90
+     * instance is to be initialized.
91
+     */
92
+    constructor(props: Object) {
93
+        super(props);
94
+
95
+        this._onStatsUpdated = this._onStatsUpdated.bind(this);
96
+
97
+        this.state = {
98
+            stats: {
99
+                bitrate: {
100
+                    download: 0,
101
+                    upload: 0
102
+                }
103
+            }
104
+        };
105
+    }
106
+
107
+    /**
108
+     * The {@link statsEmitter} callback hoked up for the local participant.
109
+     *
110
+     * @param {Object} stats - These are the RTP stats. Look in
111
+     * the lib-jitsi-meet for more details on the actual structure or add
112
+     * a console print and figure out there.
113
+     * @returns {void}
114
+     * @private
115
+     */
116
+    _onStatsUpdated(stats = {}) {
117
+        this.setState({
118
+            stats: {
119
+                bitrate: {
120
+                    download: stats.bitrate.download,
121
+                    upload: stats.bitrate.upload
122
+                }
123
+            }
124
+        });
125
+    }
126
+
127
+    /**
128
+     * Starts listening for the local RTP stat updates.
129
+     *
130
+     * @inheritdoc
131
+     * returns {void}
132
+     */
133
+    componentDidMount() {
134
+        statsEmitter.subscribeToClientStats(
135
+            this.props._localUserId, this._onStatsUpdated);
136
+    }
137
+
138
+    /**
139
+     * Updates which user's stats are being listened to (the local participant's
140
+     * id changes).
141
+     *
142
+     * @inheritdoc
143
+     * returns {void}
144
+     */
145
+    componentDidUpdate(prevProps) {
146
+        if (prevProps._localUserId !== this.props._localUserId) {
147
+            statsEmitter.unsubscribeToClientStats(
148
+                prevProps._localUserId, this._onStatsUpdated);
149
+            statsEmitter.subscribeToClientStats(
150
+                this.props._localUserId, this._onStatsUpdated);
151
+        }
152
+    }
153
+
154
+    /**
155
+     * Removes the local stats listener.
156
+     *
157
+     * @private
158
+     * @returns {void}
159
+     */
160
+    componentWillUnmount() {
161
+        statsEmitter.unsubscribeToClientStats(
162
+            this.props._localUserId, this._onStatsUpdated);
163
+    }
164
+
165
+    /**
166
+     * Renders the component if the app is currently running in the test mode
167
+     * (config.testing.testMode == true).
168
+     *
169
+     * @returns {ReactElement|null}
170
+     */
171
+    render() {
172
+        if (!this.props._testMode) {
173
+            return null;
174
+        }
175
+
176
+        return (
177
+            <Fragment accessible = { false } >
178
+                <TestHint
179
+                    id = 'org.jitsi.meet.conference.connectionState'
180
+                    value = { this.props._conferenceConnectionState } />
181
+                <TestHint
182
+                    id = 'org.jitsi.meet.conference.joinedState'
183
+                    value = { this.props._conferenceJoinedState } />
184
+                <TestHint
185
+                    id = 'org.jitsi.meet.stats.rtp'
186
+                    value = { JSON.stringify(this.state.stats) } />
187
+            </Fragment>
188
+        );
189
+    }
190
+}
191
+
192
+
193
+/**
194
+ * Maps (parts of) the Redux state to the associated TestConnectionInfo's props.
195
+ *
196
+ * @param {Object} state - The Redux state.
197
+ * @private
198
+ * @returns {{
199
+ *     _conferenceConnectionState: string,
200
+ *     _conferenceJoinedState: string,
201
+ *     _localUserId: string,
202
+ *     _testMode: boolean
203
+ * }}
204
+ */
205
+function _mapStateToProps(state) {
206
+    const conferenceJoined
207
+        = Boolean(state['features/base/conference'].conference);
208
+    const localParticipant = getLocalParticipant(state);
209
+
210
+    const testingConfig = state['features/base/config'].testing;
211
+    const testMode = Boolean(testingConfig && testingConfig.testMode);
212
+
213
+    return {
214
+        _conferenceConnectionState: state['features/testing'].connectionState,
215
+        _conferenceJoinedState: conferenceJoined.toString(),
216
+        _localUserId: localParticipant && localParticipant.id,
217
+        _testMode: testMode
218
+    };
219
+}
220
+
221
+export default connect(_mapStateToProps)(TestConnectionInfo);

+ 39
- 0
react/features/testing/components/TestHint.android.js Dosyayı Görüntüle

@@ -0,0 +1,39 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { Text } from 'react-native';
5
+
6
+import type { TestHintProps } from './AbstractTestHint';
7
+
8
+/**
9
+ * The Android version of <code>TestHint</code>. It will put the identifier,
10
+ * as the 'accessibilityLabel'.
11
+ *
12
+ * FIXME The 'testID' attribute (which is used on iOS) does not work with
13
+ * the react-native as expected, because is mapped to component's tag instead of
14
+ * any attribute visible to the UI automation. Because of that it can not be
15
+ * used to find the element.
16
+ * On the other hand it's not possible to use 'accessibilityLabel' on the iOS
17
+ * for the id purpose, because it will merge the value with any text content or
18
+ * 'accessibilityLabel' values of it's children. So as a workaround a TestHint
19
+ * class was introduced in 'jitsi-meet-torture' which will accept generic 'id'
20
+ * attribute and then do the search 'under the hood' either by the accessibility
21
+ * label or the id, depending on the participant's platform. On the client side
22
+ * the TestHint class is to be the abstraction layer which masks the problem by
23
+ * exposing id and value properties.
24
+ */
25
+export default class TestHint extends Component<TestHintProps> {
26
+
27
+    /**
28
+     * Renders the test hint on Android.
29
+     *
30
+     * @returns {ReactElement}
31
+     */
32
+    render() {
33
+        return (
34
+            <Text accessibilityLabel = { this.props.id } >
35
+                { this.props.value }
36
+            </Text>
37
+        );
38
+    }
39
+}

+ 28
- 0
react/features/testing/components/TestHint.ios.js Dosyayı Görüntüle

@@ -0,0 +1,28 @@
1
+/* @flow */
2
+
3
+import React, { Component } from 'react';
4
+import { Text } from 'react-native';
5
+
6
+import type { TestHintProps } from './AbstractTestHint';
7
+
8
+/**
9
+ * This is the iOS version of the TestHint.
10
+ *
11
+ * Be sure to check the description in TestHint.android and AbstractTestHint
12
+ * files to understand what a test hint is and why different iOS and Android
13
+ * components are necessary.
14
+ */
15
+export default class TestHint extends Component<TestHintProps> {
16
+    /**
17
+     *  Renders the test hint on Android.
18
+     *
19
+     * @returns {ReactElement}
20
+     */
21
+    render() {
22
+        return (
23
+            <Text
24
+                accessibilityLabel = { this.props.value }
25
+                testID = { this.props.id } />
26
+        );
27
+    }
28
+}

+ 2
- 0
react/features/testing/components/index.js Dosyayı Görüntüle

@@ -0,0 +1,2 @@
1
+export { default as TestConnectionInfo } from './TestConnectionInfo';
2
+export { default as TestHint } from './TestHint';

+ 4
- 0
react/features/testing/index.js Dosyayı Görüntüle

@@ -0,0 +1,4 @@
1
+export * from './components';
2
+
3
+import './middleware';
4
+import './reducer';

+ 80
- 0
react/features/testing/middleware.js Dosyayı Görüntüle

@@ -0,0 +1,80 @@
1
+/* @flow */
2
+
3
+import Logger from 'jitsi-meet-logger';
4
+
5
+const logger = Logger.getLogger(__filename);
6
+
7
+import { MiddlewareRegistry } from '../base/redux';
8
+
9
+import { CONFERENCE_WILL_JOIN } from '../base/conference';
10
+import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
11
+import { setConnectionState } from './actions';
12
+
13
+/**
14
+ * The Redux middleware of the feature testing.
15
+ *
16
+ * @param {Store} store - The Redux store.
17
+ * @returns {Function}
18
+ * @private
19
+ */
20
+MiddlewareRegistry.register(store => next => action => {
21
+    switch (action.type) {
22
+    case CONFERENCE_WILL_JOIN:
23
+        _bindConferenceConnectionListener(action.conference, store);
24
+        break;
25
+    }
26
+
27
+    return next(action);
28
+});
29
+
30
+/**
31
+ * Binds a handler which will listen for the connection related conference
32
+ * events (in the lib-jitsi-meet internals those are associated with the ICE
33
+ * connection state).
34
+ *
35
+ * @param {JitsiConference} conference - The {@link JitsiConference} for which
36
+ * the conference will join even is dispatched.
37
+ * @param {Store} store - The redux store in which the specified action is being
38
+ * dispatched.
39
+ * @private
40
+ * @returns {void}
41
+ */
42
+function _bindConferenceConnectionListener(conference, { dispatch }) {
43
+
44
+    conference.on(
45
+        JitsiConferenceEvents.CONNECTION_ESTABLISHED,
46
+        _onConnectionEvent.bind(
47
+            null, JitsiConferenceEvents.CONNECTION_ESTABLISHED, dispatch));
48
+    conference.on(
49
+        JitsiConferenceEvents.CONNECTION_RESTORED,
50
+        _onConnectionEvent.bind(
51
+            null, JitsiConferenceEvents.CONNECTION_RESTORED, dispatch));
52
+    conference.on(
53
+        JitsiConferenceEvents.CONNECTION_INTERRUPTED,
54
+        _onConnectionEvent.bind(
55
+            null, JitsiConferenceEvents.CONNECTION_INTERRUPTED, dispatch));
56
+}
57
+
58
+/**
59
+ * The handler function for conference connection events which wil store the
60
+ * latest even name in the Redux store of feature testing.
61
+ *
62
+ * @param {string} event - One of the lib-jitsi-meet JitsiConferenceEvents.
63
+ * @param {Function} dispatch - The dispatch function of the current Redux
64
+ * store.
65
+ * @returns {void}
66
+ * @private
67
+ */
68
+function _onConnectionEvent(event, dispatch) {
69
+    switch (event) {
70
+    case JitsiConferenceEvents.CONNECTION_ESTABLISHED:
71
+    case JitsiConferenceEvents.CONNECTION_INTERRUPTED:
72
+    case JitsiConferenceEvents.CONNECTION_RESTORED:
73
+        dispatch(setConnectionState(event));
74
+        break;
75
+    default:
76
+        logger.error(`onConnectionEvent - unsupported event type: ${event}`);
77
+        break;
78
+    }
79
+}
80
+

+ 40
- 0
react/features/testing/reducer.js Dosyayı Görüntüle

@@ -0,0 +1,40 @@
1
+import { assign, ReducerRegistry } from '../base/redux';
2
+
3
+import { SET_CONNECTION_STATE } from './actionTypes';
4
+
5
+/**
6
+ * The initial state of the feature testing.
7
+ *
8
+ * @type {{
9
+ *     connectionState: string
10
+ * }}
11
+ */
12
+const INITIAL_STATE = {
13
+    connectionState: ''
14
+};
15
+
16
+ReducerRegistry.register(
17
+    'features/testing',
18
+    (state = INITIAL_STATE, action) => {
19
+        switch (action.type) {
20
+        case SET_CONNECTION_STATE:
21
+            return _setConnectionState(state, action);
22
+
23
+        default:
24
+            return state;
25
+        }
26
+    });
27
+
28
+/**
29
+ * Reduces a specific Redux action SET_CONNECTION_STATE of the feature
30
+ * testing.
31
+ *
32
+ * @param {Object} state - The Redux state of the feature base/logging.
33
+ * @param {Action} action - The Redux action SET_CONNECTION_STATE to reduce.
34
+ * @private
35
+ * @returns {Object} The new state of the feature testing after the
36
+ * reduction of the specified action.
37
+ */
38
+function _setConnectionState(state, action) {
39
+    return assign(state, { connectionState: action.connectionState });
40
+}

Loading…
İptal
Kaydet