blob: 0b35fcf64234500af95ba7e8944f340d97133f8a [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html';
import 'dart:ui';
import 'package:async/async.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_web/camera_web.dart';
import 'package:camera_web/src/camera.dart';
import 'package:camera_web/src/camera_settings.dart';
import 'package:camera_web/src/types/types.dart';
import 'package:flutter/widgets.dart' as widgets;
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:mocktail/mocktail.dart';
import 'helpers/helpers.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('CameraPlugin', () {
const cameraId = 0;
late Window window;
late Navigator navigator;
late MediaDevices mediaDevices;
late VideoElement videoElement;
late CameraSettings cameraSettings;
setUp(() async {
window = MockWindow();
navigator = MockNavigator();
mediaDevices = MockMediaDevices();
videoElement = getVideoElementWithBlankStream(Size(10, 10));
cameraSettings = MockCameraSettings();
when(() => window.navigator).thenReturn(navigator);
when(() => navigator.mediaDevices).thenReturn(mediaDevices);
when(
() => mediaDevices.getUserMedia(any()),
).thenAnswer((_) async => videoElement.captureStream());
CameraPlatform.instance = CameraPlugin(
cameraSettings: cameraSettings,
)..window = window;
});
setUpAll(() {
registerFallbackValue<MediaStreamTrack>(MockMediaStreamTrack());
});
testWidgets('CameraPlugin is the live instance', (tester) async {
expect(CameraPlatform.instance, isA<CameraPlugin>());
});
group('availableCameras', () {
setUp(() {
when(
() => cameraSettings.getFacingModeForVideoTrack(
any(),
),
).thenReturn(null);
});
testWidgets(
'throws CameraException '
'with notSupported error '
'when there are no media devices', (tester) async {
when(() => navigator.mediaDevices).thenReturn(null);
expect(
() => CameraPlatform.instance.availableCameras(),
throwsA(
isA<CameraException>().having(
(e) => e.code,
'code',
CameraErrorCodes.notSupported,
),
),
);
});
testWidgets(
'calls MediaDevices.getUserMedia '
'on the video input device', (tester) async {
final videoDevice = FakeMediaDeviceInfo(
'1',
'Camera 1',
MediaDeviceKind.videoInput,
);
when(mediaDevices.enumerateDevices).thenAnswer(
(_) => Future.value([videoDevice]),
);
final _ = await CameraPlatform.instance.availableCameras();
verify(
() => mediaDevices.getUserMedia(
CameraOptions(
video: VideoConstraints(
deviceId: videoDevice.deviceId,
),
).toJson(),
),
).called(1);
});
testWidgets(
'calls CameraSettings.getLensDirectionForVideoTrack '
'on the first video track of the video input device', (tester) async {
final videoDevice = FakeMediaDeviceInfo(
'1',
'Camera 1',
MediaDeviceKind.videoInput,
);
final videoStream =
FakeMediaStream([MockMediaStreamTrack(), MockMediaStreamTrack()]);
when(
() => mediaDevices.getUserMedia(
CameraOptions(
video: VideoConstraints(deviceId: videoDevice.deviceId),
).toJson(),
),
).thenAnswer((_) => Future.value(videoStream));
when(mediaDevices.enumerateDevices).thenAnswer(
(_) => Future.value([videoDevice]),
);
final _ = await CameraPlatform.instance.availableCameras();
verify(
() => cameraSettings.getFacingModeForVideoTrack(
videoStream.getVideoTracks().first,
),
).called(1);
});
testWidgets(
'returns appropriate camera descriptions '
'for multiple media devices', (tester) async {
final firstVideoDevice = FakeMediaDeviceInfo(
'1',
'Camera 1',
MediaDeviceKind.videoInput,
);
final secondVideoDevice = FakeMediaDeviceInfo(
'4',
'Camera 4',
MediaDeviceKind.videoInput,
);
// Create a video stream for the first video device.
final firstVideoStream =
FakeMediaStream([MockMediaStreamTrack(), MockMediaStreamTrack()]);
// Create a video stream for the second video device.
final secondVideoStream = FakeMediaStream([MockMediaStreamTrack()]);
// Mock media devices to return two video input devices
// and two audio devices.
when(mediaDevices.enumerateDevices).thenAnswer(
(_) => Future.value([
firstVideoDevice,
FakeMediaDeviceInfo(
'2',
'Camera 2',
MediaDeviceKind.audioInput,
),
FakeMediaDeviceInfo(
'3',
'Camera 3',
MediaDeviceKind.audioOutput,
),
secondVideoDevice,
]),
);
// Mock media devices to return the first video stream
// for the first video device.
when(
() => mediaDevices.getUserMedia(
CameraOptions(
video: VideoConstraints(deviceId: firstVideoDevice.deviceId),
).toJson(),
),
).thenAnswer((_) => Future.value(firstVideoStream));
// Mock media devices to return the second video stream
// for the second video device.
when(
() => mediaDevices.getUserMedia(
CameraOptions(
video: VideoConstraints(deviceId: secondVideoDevice.deviceId),
).toJson(),
),
).thenAnswer((_) => Future.value(secondVideoStream));
// Mock camera settings to return a user facing mode
// for the first video stream.
when(
() => cameraSettings.getFacingModeForVideoTrack(
firstVideoStream.getVideoTracks().first,
),
).thenReturn('user');
when(() => cameraSettings.mapFacingModeToLensDirection('user'))
.thenReturn(CameraLensDirection.front);
// Mock camera settings to return an environment facing mode
// for the second video stream.
when(
() => cameraSettings.getFacingModeForVideoTrack(
secondVideoStream.getVideoTracks().first,
),
).thenReturn('environment');
when(() => cameraSettings.mapFacingModeToLensDirection('environment'))
.thenReturn(CameraLensDirection.back);
final cameras = await CameraPlatform.instance.availableCameras();
// Expect two cameras and ignore two audio devices.
expect(
cameras,
equals([
CameraDescription(
name: firstVideoDevice.label!,
lensDirection: CameraLensDirection.front,
sensorOrientation: 0,
),
CameraDescription(
name: secondVideoDevice.label!,
lensDirection: CameraLensDirection.back,
sensorOrientation: 0,
)
]),
);
});
testWidgets(
'sets camera metadata '
'for the camera description', (tester) async {
final videoDevice = FakeMediaDeviceInfo(
'1',
'Camera 1',
MediaDeviceKind.videoInput,
);
final videoStream =
FakeMediaStream([MockMediaStreamTrack(), MockMediaStreamTrack()]);
when(mediaDevices.enumerateDevices).thenAnswer(
(_) => Future.value([videoDevice]),
);
when(
() => mediaDevices.getUserMedia(
CameraOptions(
video: VideoConstraints(deviceId: videoDevice.deviceId),
).toJson(),
),
).thenAnswer((_) => Future.value(videoStream));
when(
() => cameraSettings.getFacingModeForVideoTrack(
videoStream.getVideoTracks().first,
),
).thenReturn('left');
when(() => cameraSettings.mapFacingModeToLensDirection('left'))
.thenReturn(CameraLensDirection.external);
final camera = (await CameraPlatform.instance.availableCameras()).first;
expect(
(CameraPlatform.instance as CameraPlugin).camerasMetadata,
equals({
camera: CameraMetadata(
deviceId: videoDevice.deviceId!,
facingMode: 'left',
)
}),
);
});
});
group('createCamera', () {
testWidgets(
'throws CameraException '
'with missingMetadata error '
'if there is no metadata '
'for the given camera description', (tester) async {
expect(
() => CameraPlatform.instance.createCamera(
CameraDescription(
name: 'name',
lensDirection: CameraLensDirection.back,
sensorOrientation: 0,
),
ResolutionPreset.ultraHigh,
),
throwsA(
isA<CameraException>().having(
(e) => e.code,
'code',
CameraErrorCodes.missingMetadata,
),
),
);
});
group('creates a camera', () {
const ultraHighResolutionSize = Size(3840, 2160);
const maxResolutionSize = Size(3840, 2160);
final cameraDescription = CameraDescription(
name: 'name',
lensDirection: CameraLensDirection.front,
sensorOrientation: 0,
);
final cameraMetadata = CameraMetadata(
deviceId: 'deviceId',
facingMode: 'user',
);
setUp(() {
// Add metadata for the camera description.
(CameraPlatform.instance as CameraPlugin)
.camerasMetadata[cameraDescription] = cameraMetadata;
when(
() => cameraSettings.mapFacingModeToCameraType('user'),
).thenReturn(CameraType.user);
});
testWidgets('with appropriate options', (tester) async {
when(
() => cameraSettings
.mapResolutionPresetToSize(ResolutionPreset.ultraHigh),
).thenReturn(ultraHighResolutionSize);
final cameraId = await CameraPlatform.instance.createCamera(
cameraDescription,
ResolutionPreset.ultraHigh,
enableAudio: true,
);
expect(
(CameraPlatform.instance as CameraPlugin).cameras[cameraId],
isA<Camera>()
.having(
(camera) => camera.textureId,
'textureId',
cameraId,
)
.having(
(camera) => camera.window,
'window',
window,
)
.having(
(camera) => camera.options,
'options',
CameraOptions(
audio: AudioConstraints(enabled: true),
video: VideoConstraints(
facingMode: FacingModeConstraint(CameraType.user),
width: VideoSizeConstraint(
ideal: ultraHighResolutionSize.width.toInt(),
),
height: VideoSizeConstraint(
ideal: ultraHighResolutionSize.height.toInt(),
),
deviceId: cameraMetadata.deviceId,
),
),
),
);
});
testWidgets(
'with a max resolution preset '
'and enabled audio set to false '
'when no options are specified', (tester) async {
when(
() =>
cameraSettings.mapResolutionPresetToSize(ResolutionPreset.max),
).thenReturn(maxResolutionSize);
final cameraId = await CameraPlatform.instance.createCamera(
cameraDescription,
null,
);
expect(
(CameraPlatform.instance as CameraPlugin).cameras[cameraId],
isA<Camera>().having(
(camera) => camera.options,
'options',
CameraOptions(
audio: AudioConstraints(enabled: false),
video: VideoConstraints(
facingMode: FacingModeConstraint(CameraType.user),
width: VideoSizeConstraint(
ideal: maxResolutionSize.width.toInt(),
),
height: VideoSizeConstraint(
ideal: maxResolutionSize.height.toInt(),
),
deviceId: cameraMetadata.deviceId,
),
),
),
);
});
});
});
group('initializeCamera', () {
testWidgets(
'throws CameraException '
'with notFound error '
'if the camera does not exist', (tester) async {
expect(
() => CameraPlatform.instance.initializeCamera(cameraId),
throwsA(
isA<CameraException>().having(
(e) => e.code,
'code',
CameraErrorCodes.notFound,
),
),
);
});
testWidgets('initializes and plays the camera', (tester) async {
final camera = MockCamera();
when(camera.getVideoSize).thenAnswer((_) => Future.value(Size(10, 10)));
when(camera.initialize).thenAnswer((_) => Future.value());
when(camera.play).thenAnswer((_) => Future.value());
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
await CameraPlatform.instance.initializeCamera(cameraId);
verify(camera.initialize).called(1);
verify(camera.play).called(1);
});
});
testWidgets('lockCaptureOrientation throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.lockCaptureOrientation(
cameraId,
DeviceOrientation.landscapeLeft,
),
throwsUnimplementedError,
);
});
testWidgets('unlockCaptureOrientation throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.unlockCaptureOrientation(cameraId),
throwsUnimplementedError,
);
});
group('takePicture', () {
testWidgets(
'throws CameraException '
'with notFound error '
'if the camera does not exist', (tester) async {
expect(
() => CameraPlatform.instance.initializeCamera(cameraId),
throwsA(
isA<CameraException>().having(
(e) => e.code,
'code',
CameraErrorCodes.notFound,
),
),
);
});
testWidgets('captures a picture', (tester) async {
final camera = MockCamera();
final capturedPicture = MockXFile();
when(camera.takePicture)
.thenAnswer((_) => Future.value(capturedPicture));
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
final picture = await CameraPlatform.instance.takePicture(cameraId);
verify(camera.takePicture).called(1);
expect(picture, equals(capturedPicture));
});
});
testWidgets('prepareForVideoRecording throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.prepareForVideoRecording(),
throwsUnimplementedError,
);
});
testWidgets('startVideoRecording throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.startVideoRecording(cameraId),
throwsUnimplementedError,
);
});
testWidgets('stopVideoRecording throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.stopVideoRecording(cameraId),
throwsUnimplementedError,
);
});
testWidgets('pauseVideoRecording throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.pauseVideoRecording(cameraId),
throwsUnimplementedError,
);
});
testWidgets('resumeVideoRecording throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.resumeVideoRecording(cameraId),
throwsUnimplementedError,
);
});
testWidgets('setFlashMode throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.setFlashMode(
cameraId,
FlashMode.auto,
),
throwsUnimplementedError,
);
});
testWidgets('setExposureMode throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.setExposureMode(
cameraId,
ExposureMode.auto,
),
throwsUnimplementedError,
);
});
testWidgets('setExposurePoint throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.setExposurePoint(
cameraId,
const Point(0, 0),
),
throwsUnimplementedError,
);
});
testWidgets('getMinExposureOffset throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.getMinExposureOffset(cameraId),
throwsUnimplementedError,
);
});
testWidgets('getMaxExposureOffset throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.getMaxExposureOffset(cameraId),
throwsUnimplementedError,
);
});
testWidgets('getExposureOffsetStepSize throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.getExposureOffsetStepSize(cameraId),
throwsUnimplementedError,
);
});
testWidgets('setExposureOffset throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.setExposureOffset(
cameraId,
0,
),
throwsUnimplementedError,
);
});
testWidgets('setFocusMode throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.setFocusMode(
cameraId,
FocusMode.auto,
),
throwsUnimplementedError,
);
});
testWidgets('setFocusPoint throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.setFocusPoint(
cameraId,
const Point(0, 0),
),
throwsUnimplementedError,
);
});
testWidgets('getMaxZoomLevel throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.getMaxZoomLevel(cameraId),
throwsUnimplementedError,
);
});
testWidgets('getMinZoomLevel throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.getMinZoomLevel(cameraId),
throwsUnimplementedError,
);
});
testWidgets('setZoomLevel throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.setZoomLevel(
cameraId,
1.0,
),
throwsUnimplementedError,
);
});
testWidgets(
'buildPreview returns an HtmlElementView '
'with an appropriate view type', (tester) async {
final camera = Camera(
textureId: cameraId,
window: window,
);
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
expect(
CameraPlatform.instance.buildPreview(cameraId),
isA<widgets.HtmlElementView>().having(
(view) => view.viewType,
'viewType',
camera.getViewType(),
),
);
});
testWidgets('dispose throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.dispose(cameraId),
throwsUnimplementedError,
);
});
group('getCamera', () {
testWidgets('returns the correct camera', (tester) async {
final camera = Camera(textureId: cameraId, window: window);
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
expect(
(CameraPlatform.instance as CameraPlugin).getCamera(cameraId),
equals(camera),
);
});
testWidgets(
'throws CameraException '
'with notFound error '
'if the camera does not exist', (tester) async {
expect(
() => (CameraPlatform.instance as CameraPlugin).getCamera(cameraId),
throwsA(
isA<CameraException>().having(
(e) => e.code,
'code',
CameraErrorCodes.notFound,
),
),
);
});
});
group('events', () {
testWidgets(
'onCameraInitialized emits a CameraInitializedEvent '
'on initializeCamera', (tester) async {
// Mock the camera to use a blank video stream of size 1280x720.
const videoSize = Size(1280, 720);
videoElement = getVideoElementWithBlankStream(videoSize);
when(
() => mediaDevices.getUserMedia(any()),
).thenAnswer((_) async => videoElement.captureStream());
final camera = Camera(
textureId: cameraId,
window: window,
);
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
final Stream<CameraInitializedEvent> eventStream =
CameraPlatform.instance.onCameraInitialized(cameraId);
final streamQueue = StreamQueue(eventStream);
await CameraPlatform.instance.initializeCamera(cameraId);
expect(
await streamQueue.next,
CameraInitializedEvent(
cameraId,
videoSize.width,
videoSize.height,
ExposureMode.auto,
false,
FocusMode.auto,
false,
),
);
await streamQueue.cancel();
});
testWidgets('onCameraResolutionChanged throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.onCameraResolutionChanged(cameraId),
throwsUnimplementedError,
);
});
testWidgets('onCameraClosing throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.onCameraClosing(cameraId),
throwsUnimplementedError,
);
});
testWidgets('onCameraError throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.onCameraError(cameraId),
throwsUnimplementedError,
);
});
testWidgets('onVideoRecordedEvent throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.onVideoRecordedEvent(cameraId),
throwsUnimplementedError,
);
});
testWidgets('onDeviceOrientationChanged throws UnimplementedError',
(tester) async {
expect(
() => CameraPlatform.instance.onDeviceOrientationChanged(),
throwsUnimplementedError,
);
});
});
});
}