266 lines
10 KiB
C#
266 lines
10 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Windows;
|
|
using Moq;
|
|
using Xunit;
|
|
using XplorePlane.Services.Recording;
|
|
using XP.Common.Logging.Interfaces;
|
|
using XplorePlane.Services.Storage;
|
|
|
|
namespace XplorePlane.Tests.ViewModels
|
|
{
|
|
/// <summary>
|
|
/// Unit tests for MainViewModel recording state transitions and command behavior.
|
|
/// Tests the integration between MainViewModel and IViewportRecordingService.
|
|
///
|
|
/// Note: These tests focus on the ViewportRecordingService behavior rather than
|
|
/// full MainViewModel integration due to complex dependencies.
|
|
/// </summary>
|
|
public class MainViewModelRecordingTests
|
|
{
|
|
/// <summary>
|
|
/// Test: RecordingService state transitions trigger correct state changes
|
|
/// **Validates: Requirements 2.1, 3.3**
|
|
/// </summary>
|
|
[StaFact]
|
|
public void RecordingService_StateChanged_IdleToRecording_TriggersEvent()
|
|
{
|
|
// Arrange
|
|
var mockLogger = new Mock<ILoggerService>();
|
|
var mockDataPathService = new Mock<IXpDataPathService>();
|
|
mockLogger.Setup(x => x.ForModule<ViewportRecordingService>()).Returns(mockLogger.Object);
|
|
|
|
string testPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"XP_Test_{Guid.NewGuid():N}");
|
|
mockDataPathService.Setup(x => x.RootPath).Returns(testPath);
|
|
|
|
var service = new ViewportRecordingService(mockDataPathService.Object, mockLogger.Object);
|
|
|
|
RecordingState? newState = null;
|
|
service.StateChanged += (sender, args) =>
|
|
{
|
|
newState = args.NewState;
|
|
};
|
|
|
|
// Act
|
|
var captureTarget = CreateMockFrameworkElement(800, 600);
|
|
bool started = service.StartRecording(captureTarget);
|
|
|
|
// If codec not available, skip
|
|
if (!started)
|
|
{
|
|
service.Dispose();
|
|
return;
|
|
}
|
|
|
|
// Assert
|
|
Assert.Equal(RecordingState.Recording, newState);
|
|
Assert.Equal(RecordingState.Recording, service.CurrentState);
|
|
|
|
// Cleanup
|
|
service.Dispose();
|
|
if (System.IO.Directory.Exists(testPath))
|
|
{
|
|
try { System.IO.Directory.Delete(testPath, true); } catch { }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test: RecordingService transitions from Recording to Saving to Idle
|
|
/// **Validates: Requirements 3.1, 3.3**
|
|
/// </summary>
|
|
[StaFact]
|
|
public void RecordingService_StopRecording_TransitionsToSavingThenIdle()
|
|
{
|
|
// Arrange
|
|
var mockLogger = new Mock<ILoggerService>();
|
|
var mockDataPathService = new Mock<IXpDataPathService>();
|
|
mockLogger.Setup(x => x.ForModule<ViewportRecordingService>()).Returns(mockLogger.Object);
|
|
|
|
string testPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"XP_Test_{Guid.NewGuid():N}");
|
|
mockDataPathService.Setup(x => x.RootPath).Returns(testPath);
|
|
|
|
var service = new ViewportRecordingService(mockDataPathService.Object, mockLogger.Object);
|
|
|
|
var states = new System.Collections.Generic.List<RecordingState>();
|
|
service.StateChanged += (sender, args) =>
|
|
{
|
|
states.Add(args.NewState);
|
|
};
|
|
|
|
var captureTarget = CreateMockFrameworkElement(640, 480);
|
|
bool started = service.StartRecording(captureTarget);
|
|
|
|
// If codec not available, skip
|
|
if (!started)
|
|
{
|
|
service.Dispose();
|
|
return;
|
|
}
|
|
|
|
// Act
|
|
Thread.Sleep(100); // Let it record briefly
|
|
service.StopRecording();
|
|
|
|
// Wait for state to settle
|
|
Thread.Sleep(500);
|
|
|
|
// Assert
|
|
Assert.Contains(RecordingState.Recording, states);
|
|
Assert.Contains(RecordingState.Saving, states);
|
|
Assert.Contains(RecordingState.Idle, states);
|
|
Assert.Equal(RecordingState.Idle, service.CurrentState);
|
|
|
|
// Cleanup
|
|
service.Dispose();
|
|
if (System.IO.Directory.Exists(testPath))
|
|
{
|
|
try { System.IO.Directory.Delete(testPath, true); } catch { }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test: RecordingService cannot start when already recording
|
|
/// **Validates: Requirements 2.4**
|
|
/// </summary>
|
|
[StaFact]
|
|
public void RecordingService_StartRecording_WhenAlreadyRecording_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var mockLogger = new Mock<ILoggerService>();
|
|
var mockDataPathService = new Mock<IXpDataPathService>();
|
|
mockLogger.Setup(x => x.ForModule<ViewportRecordingService>()).Returns(mockLogger.Object);
|
|
|
|
string testPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"XP_Test_{Guid.NewGuid():N}");
|
|
mockDataPathService.Setup(x => x.RootPath).Returns(testPath);
|
|
|
|
var service = new ViewportRecordingService(mockDataPathService.Object, mockLogger.Object);
|
|
|
|
var captureTarget = CreateMockFrameworkElement(800, 600);
|
|
bool firstStart = service.StartRecording(captureTarget);
|
|
|
|
// If codec not available, skip
|
|
if (!firstStart)
|
|
{
|
|
service.Dispose();
|
|
return;
|
|
}
|
|
|
|
// Act
|
|
bool secondStartResult = service.StartRecording(captureTarget);
|
|
|
|
// Assert
|
|
Assert.False(secondStartResult);
|
|
Assert.Equal(RecordingState.Recording, service.CurrentState);
|
|
|
|
// Cleanup
|
|
service.Dispose();
|
|
if (System.IO.Directory.Exists(testPath))
|
|
{
|
|
try { System.IO.Directory.Delete(testPath, true); } catch { }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test: RecordingService ElapsedTime increases during recording
|
|
/// **Validates: Requirements 2.2**
|
|
/// </summary>
|
|
[StaFact]
|
|
public void RecordingService_ElapsedTime_IncreasesWhileRecording()
|
|
{
|
|
// Arrange
|
|
var mockLogger = new Mock<ILoggerService>();
|
|
var mockDataPathService = new Mock<IXpDataPathService>();
|
|
mockLogger.Setup(x => x.ForModule<ViewportRecordingService>()).Returns(mockLogger.Object);
|
|
|
|
string testPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"XP_Test_{Guid.NewGuid():N}");
|
|
mockDataPathService.Setup(x => x.RootPath).Returns(testPath);
|
|
|
|
var service = new ViewportRecordingService(mockDataPathService.Object, mockLogger.Object);
|
|
|
|
var captureTarget = CreateMockFrameworkElement(640, 480);
|
|
bool started = service.StartRecording(captureTarget);
|
|
|
|
// If codec not available, skip
|
|
if (!started)
|
|
{
|
|
service.Dispose();
|
|
return;
|
|
}
|
|
|
|
// Act
|
|
Thread.Sleep(1500); // Wait 1.5 seconds
|
|
var elapsed = service.ElapsedTime;
|
|
|
|
// Assert
|
|
Assert.True(elapsed.TotalSeconds >= 1.0, $"Expected elapsed time >= 1 second, got {elapsed.TotalSeconds}");
|
|
Assert.True(elapsed.TotalSeconds < 3.0, $"Expected elapsed time < 3 seconds, got {elapsed.TotalSeconds}");
|
|
|
|
// Cleanup
|
|
service.Dispose();
|
|
if (System.IO.Directory.Exists(testPath))
|
|
{
|
|
try { System.IO.Directory.Delete(testPath, true); } catch { }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test: RecordingService ElapsedTime is zero when idle
|
|
/// **Validates: Requirements 2.2**
|
|
/// </summary>
|
|
[Fact]
|
|
public void RecordingService_ElapsedTime_IsZeroWhenIdle()
|
|
{
|
|
// Arrange
|
|
var mockLogger = new Mock<ILoggerService>();
|
|
var mockDataPathService = new Mock<IXpDataPathService>();
|
|
mockLogger.Setup(x => x.ForModule<ViewportRecordingService>()).Returns(mockLogger.Object);
|
|
mockDataPathService.Setup(x => x.RootPath).Returns(@"C:\TestPath");
|
|
|
|
var service = new ViewportRecordingService(mockDataPathService.Object, mockLogger.Object);
|
|
|
|
// Act & Assert
|
|
Assert.Equal(TimeSpan.Zero, service.ElapsedTime);
|
|
Assert.Equal(RecordingState.Idle, service.CurrentState);
|
|
|
|
// Cleanup
|
|
service.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test: FormatElapsedTime produces correct MM:SS format
|
|
/// **Validates: Requirements 2.2**
|
|
/// </summary>
|
|
[Fact]
|
|
public void FormatElapsedTime_ProducesCorrectFormat()
|
|
{
|
|
// Act & Assert
|
|
Assert.Equal("00:00", ViewportRecordingService.FormatElapsedTime(0));
|
|
Assert.Equal("00:30", ViewportRecordingService.FormatElapsedTime(30));
|
|
Assert.Equal("01:00", ViewportRecordingService.FormatElapsedTime(60));
|
|
Assert.Equal("02:30", ViewportRecordingService.FormatElapsedTime(150));
|
|
Assert.Equal("59:59", ViewportRecordingService.FormatElapsedTime(3599));
|
|
}
|
|
|
|
// ── Helper Methods ───────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Creates a mock FrameworkElement with specified dimensions for testing.
|
|
/// </summary>
|
|
private FrameworkElement CreateMockFrameworkElement(int width, int height)
|
|
{
|
|
var canvas = new System.Windows.Controls.Canvas
|
|
{
|
|
Width = width,
|
|
Height = height,
|
|
Background = System.Windows.Media.Brushes.White
|
|
};
|
|
|
|
// Force layout to set ActualWidth/ActualHeight
|
|
canvas.Measure(new Size(width, height));
|
|
canvas.Arrange(new Rect(0, 0, width, height));
|
|
|
|
return canvas;
|
|
}
|
|
}
|
|
}
|