214 lines
8.2 KiB
C#
214 lines
8.2 KiB
C#
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}]");
|
||
});
|
||
}
|
||
}
|
||
}
|