using FsCheck; using FsCheck.Xunit; using WorkCameraExport.Forms; using Xunit; namespace WorkCameraExport.Tests { /// /// ImageViewerForm 属性测试 /// Feature: work-camera-2.0.1, Property 2: 图片轮播索引边界 /// Validates: Requirements 3.6 /// public class ImageViewerPropertyTests { /// /// Property 2: 图片轮播索引边界 /// For any 图片列表,当前索引应始终在 [0, 图片数量-1] 范围内。 /// 测试:WrapIndex 方法应始终返回有效范围内的索引 /// [Property(MaxTest = 100)] public Property WrapIndex_ShouldAlwaysReturnValidIndex() { return Prop.ForAll( Arb.From(), Arb.From(), (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}]"); }); } /// /// Property 2 扩展: 点击下一张时,如果当前是最后一张则循环到第一张 /// [Property(MaxTest = 100)] public Property WrapIndex_NextFromLast_ShouldWrapToFirst() { return Prop.ForAll( Arb.From(), (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}"); }); } /// /// Property 2 扩展: 点击上一张时,如果当前是第一张则循环到最后一张 /// [Property(MaxTest = 100)] public Property WrapIndex_PreviousFromFirst_ShouldWrapToLast() { return Prop.ForAll( Arb.From(), (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}"); }); } /// /// Property 2 扩展: 连续多次 next 操作应正确循环 /// [Property(MaxTest = 100)] public Property WrapIndex_MultipleNextOperations_ShouldCycleCorrectly() { return Prop.ForAll( Arb.From(), Arb.From(), (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}]"); }); } /// /// Property 2 扩展: 连续多次 previous 操作应正确循环 /// [Property(MaxTest = 100)] public Property WrapIndex_MultiplePreviousOperations_ShouldCycleCorrectly() { return Prop.ForAll( Arb.From(), Arb.From(), (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}]"); }); } /// /// Property 2 扩展: 完整循环后应回到起始位置 /// [Property(MaxTest = 100)] public Property WrapIndex_FullCycle_ShouldReturnToStart() { return Prop.ForAll( Arb.From(), (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}"); }); } /// /// Property 2 扩展: 空列表或无效 count 应返回 0 /// [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)); } /// /// Property 2 扩展: 单张图片时索引应始终为 0 /// [Property(MaxTest = 100)] public Property WrapIndex_SingleImage_ShouldAlwaysReturnZero() { return Prop.ForAll( Arb.From(), (index) => { var result = ImageViewerForm.WrapIndex(index, 1); return (result == 0) .Label($"WrapIndex({index}, 1) should always be 0, got {result}"); }); } /// /// Property 2 扩展: 大负数索引应正确处理 /// [Property(MaxTest = 100)] public Property WrapIndex_LargeNegativeIndex_ShouldWrapCorrectly() { return Prop.ForAll( Arb.From(), Arb.From(), (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}]"); }); } } }