WorkCamera/client/WorkCameraExport.Tests/ImageViewerPropertyTests.cs
2026-01-05 23:58:56 +08:00

214 lines
8.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FsCheck;
using FsCheck.Xunit;
using WorkCameraExport.Forms;
using Xunit;
namespace WorkCameraExport.Tests
{
/// <summary>
/// ImageViewerForm 属性测试
/// Feature: work-camera-2.0.1, Property 2: 图片轮播索引边界
/// Validates: Requirements 3.6
/// </summary>
public class ImageViewerPropertyTests
{
/// <summary>
/// Property 2: 图片轮播索引边界
/// For any 图片列表,当前索引应始终在 [0, 图片数量-1] 范围内。
/// 测试WrapIndex 方法应始终返回有效范围内的索引
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_ShouldAlwaysReturnValidIndex()
{
return Prop.ForAll(
Arb.From<int>(),
Arb.From<PositiveInt>(),
(index, countGen) =>
{
var count = countGen.Get;
var result = ImageViewerForm.WrapIndex(index, count);
return (result >= 0 && result < count)
.Label($"WrapIndex({index}, {count}) = {result} should be in [0, {count - 1}]");
});
}
/// <summary>
/// Property 2 扩展: 点击下一张时,如果当前是最后一张则循环到第一张
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_NextFromLast_ShouldWrapToFirst()
{
return Prop.ForAll(
Arb.From<PositiveInt>(),
(countGen) =>
{
var count = countGen.Get;
var lastIndex = count - 1;
var nextIndex = lastIndex + 1;
var result = ImageViewerForm.WrapIndex(nextIndex, count);
return (result == 0)
.Label($"WrapIndex({nextIndex}, {count}) should be 0 (wrap to first), got {result}");
});
}
/// <summary>
/// Property 2 扩展: 点击上一张时,如果当前是第一张则循环到最后一张
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_PreviousFromFirst_ShouldWrapToLast()
{
return Prop.ForAll(
Arb.From<PositiveInt>(),
(countGen) =>
{
var count = countGen.Get;
var previousIndex = -1;
var result = ImageViewerForm.WrapIndex(previousIndex, count);
return (result == count - 1)
.Label($"WrapIndex(-1, {count}) should be {count - 1} (wrap to last), got {result}");
});
}
/// <summary>
/// Property 2 扩展: 连续多次 next 操作应正确循环
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_MultipleNextOperations_ShouldCycleCorrectly()
{
return Prop.ForAll(
Arb.From<PositiveInt>(),
Arb.From<PositiveInt>(),
(countGen, stepsGen) =>
{
var count = (countGen.Get % 20) + 1; // 1-20 张图片
var steps = stepsGen.Get % 100; // 最多 100 步
var currentIndex = 0;
for (int i = 0; i < steps; i++)
{
currentIndex = ImageViewerForm.WrapIndex(currentIndex + 1, count);
// 每一步都应该在有效范围内
if (currentIndex < 0 || currentIndex >= count)
{
return false.Label($"Index {currentIndex} out of range after {i + 1} steps");
}
}
return true.Label($"All {steps} next operations stayed in valid range [0, {count - 1}]");
});
}
/// <summary>
/// Property 2 扩展: 连续多次 previous 操作应正确循环
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_MultiplePreviousOperations_ShouldCycleCorrectly()
{
return Prop.ForAll(
Arb.From<PositiveInt>(),
Arb.From<PositiveInt>(),
(countGen, stepsGen) =>
{
var count = (countGen.Get % 20) + 1; // 1-20 张图片
var steps = stepsGen.Get % 100; // 最多 100 步
var currentIndex = 0;
for (int i = 0; i < steps; i++)
{
currentIndex = ImageViewerForm.WrapIndex(currentIndex - 1, count);
// 每一步都应该在有效范围内
if (currentIndex < 0 || currentIndex >= count)
{
return false.Label($"Index {currentIndex} out of range after {i + 1} steps");
}
}
return true.Label($"All {steps} previous operations stayed in valid range [0, {count - 1}]");
});
}
/// <summary>
/// Property 2 扩展: 完整循环后应回到起始位置
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_FullCycle_ShouldReturnToStart()
{
return Prop.ForAll(
Arb.From<PositiveInt>(),
(countGen) =>
{
var count = (countGen.Get % 50) + 1; // 1-50 张图片
var currentIndex = 0;
// 执行 count 次 next 操作
for (int i = 0; i < count; i++)
{
currentIndex = ImageViewerForm.WrapIndex(currentIndex + 1, count);
}
return (currentIndex == 0)
.Label($"After {count} next operations, index should be 0, got {currentIndex}");
});
}
/// <summary>
/// Property 2 扩展: 空列表或无效 count 应返回 0
/// </summary>
[Fact]
public void WrapIndex_ZeroOrNegativeCount_ShouldReturnZero()
{
Assert.Equal(0, ImageViewerForm.WrapIndex(0, 0));
Assert.Equal(0, ImageViewerForm.WrapIndex(5, 0));
Assert.Equal(0, ImageViewerForm.WrapIndex(-5, 0));
Assert.Equal(0, ImageViewerForm.WrapIndex(0, -1));
Assert.Equal(0, ImageViewerForm.WrapIndex(5, -5));
}
/// <summary>
/// Property 2 扩展: 单张图片时索引应始终为 0
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_SingleImage_ShouldAlwaysReturnZero()
{
return Prop.ForAll(
Arb.From<int>(),
(index) =>
{
var result = ImageViewerForm.WrapIndex(index, 1);
return (result == 0)
.Label($"WrapIndex({index}, 1) should always be 0, got {result}");
});
}
/// <summary>
/// Property 2 扩展: 大负数索引应正确处理
/// </summary>
[Property(MaxTest = 100)]
public Property WrapIndex_LargeNegativeIndex_ShouldWrapCorrectly()
{
return Prop.ForAll(
Arb.From<PositiveInt>(),
Arb.From<PositiveInt>(),
(countGen, multiplierGen) =>
{
var count = (countGen.Get % 20) + 1;
var multiplier = multiplierGen.Get;
var largeNegativeIndex = -(count * multiplier);
var result = ImageViewerForm.WrapIndex(largeNegativeIndex, count);
return (result >= 0 && result < count)
.Label($"WrapIndex({largeNegativeIndex}, {count}) = {result} should be in [0, {count - 1}]");
});
}
}
}