Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit ea0a31a

Browse files
committed
test: add availableCameras tests
1 parent f692cce commit ea0a31a

File tree

2 files changed

+229
-7
lines changed

2 files changed

+229
-7
lines changed

packages/camera/camera_web/example/integration_test/camera_web_test.dart

Lines changed: 198 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import 'dart:html';
66

77
import 'package:camera_platform_interface/camera_platform_interface.dart';
88
import 'package:camera_web/camera_web.dart';
9+
import 'package:camera_web/src/camera_settings.dart';
10+
import 'package:camera_web/src/types/types.dart';
911
import 'package:flutter/services.dart';
1012
import 'package:flutter_test/flutter_test.dart';
1113
import 'package:integration_test/integration_test.dart';
@@ -23,6 +25,7 @@ void main() {
2325
late Navigator navigator;
2426
late MediaDevices mediaDevices;
2527
late VideoElement videoElement;
28+
late CameraSettings cameraSettings;
2629

2730
setUp(() async {
2831
window = MockWindow();
@@ -33,26 +36,214 @@ void main() {
3336
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'
3437
..preload = 'true'
3538
..width = 10
36-
..height = 10;
39+
..height = 10
40+
..crossOrigin = 'anonymous';
41+
42+
cameraSettings = MockCameraSettings();
3743

3844
when(() => window.navigator).thenReturn(navigator);
3945
when(() => navigator.mediaDevices).thenReturn(mediaDevices);
4046
when(
4147
() => mediaDevices.getUserMedia(any()),
4248
).thenAnswer((_) async => videoElement.captureStream());
4349

44-
CameraPlatform.instance = CameraPlugin()..window = window;
50+
CameraPlatform.instance = CameraPlugin(
51+
cameraSettings: cameraSettings,
52+
)..window = window;
53+
});
54+
55+
setUpAll(() {
56+
registerFallbackValue<MediaStreamTrack>(MockMediaStreamTrack());
4557
});
4658

4759
testWidgets('CameraPlugin is the live instance', (tester) async {
4860
expect(CameraPlatform.instance, isA<CameraPlugin>());
4961
});
5062

51-
testWidgets('availableCameras throws UnimplementedError', (tester) async {
52-
expect(
53-
() => CameraPlatform.instance.availableCameras(),
54-
throwsUnimplementedError,
55-
);
63+
group('availableCameras', () {
64+
setUp(() {
65+
when(
66+
() => cameraSettings.getLensDirectionForVideoTrack(
67+
any(),
68+
),
69+
).thenReturn(CameraLensDirection.external);
70+
});
71+
72+
testWidgets(
73+
'throws CameraException '
74+
'with notSupported error '
75+
'when there are no media devices', (tester) async {
76+
when(() => navigator.mediaDevices).thenReturn(null);
77+
78+
expect(
79+
() => CameraPlatform.instance.availableCameras(),
80+
throwsA(
81+
isA<CameraException>().having(
82+
(e) => e.code,
83+
'code',
84+
CameraErrorCodes.notSupported,
85+
),
86+
),
87+
);
88+
});
89+
90+
testWidgets(
91+
'calls MediaDevices.getUserMedia '
92+
'on the video input device', (tester) async {
93+
final videoDevice = FakeMediaDeviceInfo(
94+
'1',
95+
'Camera 1',
96+
MediaDeviceKind.videoInput,
97+
);
98+
99+
when(mediaDevices.enumerateDevices).thenAnswer(
100+
(_) => Future.value([videoDevice]),
101+
);
102+
103+
final _ = await CameraPlatform.instance.availableCameras();
104+
105+
verify(
106+
() => mediaDevices.getUserMedia(
107+
CameraOptions(
108+
video: VideoConstraints(
109+
deviceId: videoDevice.deviceId,
110+
),
111+
).toJson(),
112+
),
113+
).called(1);
114+
});
115+
116+
testWidgets(
117+
'calls CameraSettings.getLensDirectionForVideoTrack '
118+
'on the first video track of the video input device', (tester) async {
119+
final videoDevice = FakeMediaDeviceInfo(
120+
'1',
121+
'Camera 1',
122+
MediaDeviceKind.videoInput,
123+
);
124+
125+
final videoStream =
126+
FakeMediaStream([MockMediaStreamTrack(), MockMediaStreamTrack()]);
127+
128+
when(
129+
() => mediaDevices.getUserMedia(
130+
CameraOptions(
131+
video: VideoConstraints(deviceId: videoDevice.deviceId),
132+
).toJson(),
133+
),
134+
).thenAnswer((_) => Future.value(videoStream));
135+
136+
when(mediaDevices.enumerateDevices).thenAnswer(
137+
(_) => Future.value([videoDevice]),
138+
);
139+
140+
final _ = await CameraPlatform.instance.availableCameras();
141+
142+
verify(
143+
() => cameraSettings.getLensDirectionForVideoTrack(
144+
videoStream.getVideoTracks().first,
145+
),
146+
).called(1);
147+
});
148+
149+
testWidgets(
150+
'returns appropriate camera descriptions '
151+
'for multiple media devices', (tester) async {
152+
final firstVideoDevice = FakeMediaDeviceInfo(
153+
'1',
154+
'Camera 1',
155+
MediaDeviceKind.videoInput,
156+
);
157+
158+
final secondVideoDevice = FakeMediaDeviceInfo(
159+
'4',
160+
'', // The device label might be empty.
161+
MediaDeviceKind.videoInput,
162+
);
163+
164+
// Create a video stream for the first video device.
165+
final firstVideoStream =
166+
FakeMediaStream([MockMediaStreamTrack(), MockMediaStreamTrack()]);
167+
168+
// Create a video stream for the second video device.
169+
final secondVideoStream = FakeMediaStream([MockMediaStreamTrack()]);
170+
171+
// Mock media devices to return two video input devices
172+
// and two audio devices.
173+
when(mediaDevices.enumerateDevices).thenAnswer(
174+
(_) => Future.value([
175+
firstVideoDevice,
176+
FakeMediaDeviceInfo(
177+
'2',
178+
'Camera 2',
179+
MediaDeviceKind.audioInput,
180+
),
181+
FakeMediaDeviceInfo(
182+
'3',
183+
'Camera 3',
184+
MediaDeviceKind.audioOutput,
185+
),
186+
secondVideoDevice,
187+
]),
188+
);
189+
190+
// Mock media devices to return the first video stream
191+
// for the first video device.
192+
when(
193+
() => mediaDevices.getUserMedia(
194+
CameraOptions(
195+
video: VideoConstraints(deviceId: firstVideoDevice.deviceId),
196+
).toJson(),
197+
),
198+
).thenAnswer((_) => Future.value(firstVideoStream));
199+
200+
// Mock media devices to return the second video stream
201+
// for the second video device.
202+
when(
203+
() => mediaDevices.getUserMedia(
204+
CameraOptions(
205+
video: VideoConstraints(deviceId: secondVideoDevice.deviceId),
206+
).toJson(),
207+
),
208+
).thenAnswer((_) => Future.value(secondVideoStream));
209+
210+
// Mock camera settings to return a front lens direction
211+
// for the first video stream.
212+
when(
213+
() => cameraSettings.getLensDirectionForVideoTrack(
214+
firstVideoStream.getVideoTracks().first,
215+
),
216+
).thenReturn(CameraLensDirection.front);
217+
218+
// Mock camera settings to return a back lens direction
219+
// for the second video stream.
220+
when(
221+
() => cameraSettings.getLensDirectionForVideoTrack(
222+
secondVideoStream.getVideoTracks().first,
223+
),
224+
).thenReturn(CameraLensDirection.back);
225+
226+
final cameras = await CameraPlatform.instance.availableCameras();
227+
228+
// Expect two cameras and ignore two audio devices.
229+
expect(
230+
cameras,
231+
equals([
232+
CameraDescription(
233+
// Uses the device label as a camera name.
234+
name: firstVideoDevice.label!,
235+
lensDirection: CameraLensDirection.front,
236+
sensorOrientation: 0,
237+
),
238+
CameraDescription(
239+
// Fallbacks to the device id if the label is empty.
240+
name: secondVideoDevice.deviceId!,
241+
lensDirection: CameraLensDirection.back,
242+
sensorOrientation: 0,
243+
)
244+
]),
245+
);
246+
});
56247
});
57248

58249
testWidgets('createCamera throws UnimplementedError', (tester) async {

packages/camera/camera_web/example/integration_test/helpers/mocks.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:html';
66

7+
import 'package:camera_web/src/camera_settings.dart';
78
import 'package:mocktail/mocktail.dart';
89

910
class MockWindow extends Mock implements Window {}
@@ -12,8 +13,38 @@ class MockNavigator extends Mock implements Navigator {}
1213

1314
class MockMediaDevices extends Mock implements MediaDevices {}
1415

16+
class MockCameraSettings extends Mock implements CameraSettings {}
17+
1518
class MockMediaStreamTrack extends Mock implements MediaStreamTrack {}
1619

20+
/// A fake [MediaStream] that returns the provided [_videoTracks].
21+
class FakeMediaStream extends Fake implements MediaStream {
22+
FakeMediaStream(this._videoTracks);
23+
24+
final List<MediaStreamTrack> _videoTracks;
25+
26+
@override
27+
List<MediaStreamTrack> getVideoTracks() => _videoTracks;
28+
}
29+
30+
/// A fake [MediaDeviceInfo] that returns the provided [_deviceId], [_label] and [_kind].
31+
class FakeMediaDeviceInfo extends Fake implements MediaDeviceInfo {
32+
FakeMediaDeviceInfo(this._deviceId, this._label, this._kind);
33+
34+
final String _deviceId;
35+
final String _label;
36+
final String _kind;
37+
38+
@override
39+
String? get deviceId => _deviceId;
40+
41+
@override
42+
String? get label => _label;
43+
44+
@override
45+
String? get kind => _kind;
46+
}
47+
1748
/// A fake [DomException] that returns the provided error [_name].
1849
class FakeDomException extends Fake implements DomException {
1950
FakeDomException(this._name);

0 commit comments

Comments
 (0)