using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public static int Main(string[] args)
return new AutoRun().Execute(args);
public partial class CameraView : ComponentBase, IDisposable
private IJSRuntime JsRuntime { get; set; } = default!;
private ILogger<CameraView> Logger { get; set; } = default!;
public string VideoElementId { get; set; } = "cameraVideo";
private bool _isCameraActive = false;
private string _errorMessage = string.Empty;
private IJSObjectReference? _module;
protected override async Task OnAfterRenderAsync(bool firstRender)
_module = await JsRuntime.InvokeAsync<IJSObjectReference>("import", "./js/camera.js");
_errorMessage = $"Error loading JavaScript module: {ex.Message}";
Logger.LogError(ex, "Error loading camera.js");
public async Task StartCamera()
_errorMessage = "JavaScript module not loaded.";
Logger.LogError("JavaScript module not loaded.");
if (string.IsNullOrEmpty(VideoElementId))
_errorMessage = "Video element ID is not set.";
Logger.LogError("Video element ID is not set.");
await _module.InvokeVoidAsync("startCamera", VideoElementId, DotNetObjectReference.Create(this));
_errorMessage = string.Empty;
_errorMessage = $"Error starting camera: {ex.Message}";
Logger.LogError(ex, "Error starting camera.");
public async Task StopCamera()
if (_module is not null && _isCameraActive)
await _module.InvokeVoidAsync("stopCamera");
_errorMessage = string.Empty;
_errorMessage = $"Error stopping camera: {ex.Message}";
Logger.LogError(ex, "Error stopping camera.");
public void OnCameraError(string message)
_errorMessage = $"Camera error: {message}";
Logger.LogError("Camera error: {Message}", message);
namespace BlazorCamera.Tests
public class CameraViewTests
private Bunit.TestContext _testContext;
private Mock<IJSRuntime> _jsRuntimeMock;
private Mock<ILogger<CameraView>> _loggerMock;
_testContext = new Bunit.TestContext();
_jsRuntimeMock = new Mock<IJSRuntime>();
_loggerMock = new Mock<ILogger<CameraView>>();
_testContext.Services.AddSingleton(_jsRuntimeMock.Object);
_testContext.Services.AddSingleton(_loggerMock.Object);
public async Task StartCamera_ValidId_CallsJsInterop()
_jsRuntimeMock.Setup(j => j.InvokeVoidAsync(
.Returns(ValueTask.CompletedTask);
var component = _testContext.RenderComponent<CameraView>(parameters => parameters.Add(p => p.VideoElementId, "testVideo"));
await component.Instance.StartCamera();
_jsRuntimeMock.Verify(j => j.InvokeVoidAsync(
It.IsAny<object>()), Times.Once);
public async Task StartCamera_EmptyId_LogsError()
var component = _testContext.RenderComponent<CameraView>(parameters => parameters.Add(p => p.VideoElementId, ""));
await component.Instance.StartCamera();
It.Is<It.IsAnyType>((o, t) => o.ToString()!.Contains("Video element ID is not set.")),
(Func<It.IsAnyType, Exception?, string>)It.IsAny<object>()),
public async Task StopCamera_CameraActive_CallsJsInterop()
_jsRuntimeMock.Setup(j => j.InvokeVoidAsync("camera.stopCamera"))
.Returns(ValueTask.CompletedTask);
var component = _testContext.RenderComponent<CameraView>(parameters => parameters.Add(p => p.VideoElementId, "testVideo"));
await component.Instance.StartCamera();
await component.Instance.StopCamera();
_jsRuntimeMock.Verify(j => j.InvokeVoidAsync("camera.stopCamera"), Times.Once);
public async Task StopCamera_CameraNotActive_DoesNotCallJsInterop()
var component = _testContext.RenderComponent<CameraView>(parameters => parameters.Add(p => p.VideoElementId, "testVideo"));
await component.Instance.StopCamera();
_jsRuntimeMock.Verify(j => j.InvokeVoidAsync(It.IsAny<string>()), Times.Never);
public async Task OnCameraError_SetsErrorMessage()
var component = _testContext.RenderComponent<CameraView>();
component.Instance.OnCameraError("Permission Denied");
Assert.That(component.Find(".alert").TextContent, Is.EqualTo("Camera error: Permission Denied"));
public async Task StartCamera_JsException_LogsError()
_jsRuntimeMock.Setup(j => j.InvokeVoidAsync(
.ThrowsAsync(new JSException("Test Exception"));
var component = _testContext.RenderComponent<CameraView>(parameters => parameters.Add(p => p.VideoElementId, "testVideo"));
await component.Instance.StartCamera();
It.Is<It.IsAnyType>((o, t) => o.ToString()!.Contains("Error starting camera: Test Exception")),
(Func<It.IsAnyType, Exception?, string>)It.IsAny<object>()),
public async Task Dispose_StopsCamera()
_jsRuntimeMock.Setup(j => j.InvokeVoidAsync("camera.stopCamera"))
.Returns(ValueTask.CompletedTask);
var component = _testContext.RenderComponent<CameraView>(parameters => parameters.Add(p => p.VideoElementId, "testVideo"));
await component.Instance.StartCamera();
_jsRuntimeMock.Verify(j => j.InvokeVoidAsync("camera.stopCamera"), Times.Once);
public async Task StartStopStartCamera_MultipleCalls()
_jsRuntimeMock.Setup(j => j.InvokeVoidAsync(
.Returns(ValueTask.CompletedTask);
_jsRuntimeMock.Setup(j => j.InvokeVoidAsync("camera.stopCamera"))
.Returns(ValueTask.CompletedTask);
var component = _testContext.RenderComponent<CameraView>(parameters => parameters.Add(p => p.VideoElementId, "testVideo"));
await component.Instance.StartCamera();
await component.Instance.StopCamera();
await component.Instance.StartCamera();
_jsRuntimeMock.Verify(j => j.InvokeVoidAsync("camera.startCamera", It.IsAny<string>(), It.IsAny<object>()), Times.Exactly(2));
_jsRuntimeMock.Verify(j => j.InvokeVoidAsync("camera.stopCamera"), Times.Once);