init
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
using TrainingPlanner.Application;
|
||||
|
||||
namespace TrainingPlanner.Tests;
|
||||
|
||||
public class ExerciseImportTests
|
||||
{
|
||||
private readonly ExerciseImportOptions options = new() { MaxRows = 1000, MaxClipboardCharacters = 100000 };
|
||||
|
||||
[Fact]
|
||||
public void ClipboardPreservesEmptyCellsAndExcelQuotedValues()
|
||||
{
|
||||
var rows = new ClipboardTableParser(options).Parse("\"Жим \"\"узкий\"\"\nсидя\"\thttps://example.com/video\r\nТяга\t\r\n\t\r\n");
|
||||
Assert.Equal(2, rows.Count);
|
||||
Assert.Equal("Жим \"узкий\"\nсидя", rows[0][0]);
|
||||
Assert.Equal("https://example.com/video", rows[0][1]);
|
||||
Assert.Equal("", rows[1][1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClipboardHandlesLargePasteAndAllLineEndings()
|
||||
{
|
||||
var parser = new ClipboardTableParser(options);
|
||||
Assert.Equal(500, parser.Parse(string.Join('\n', Enumerable.Repeat("Упражнение\thttps://example.com", 500))).Count);
|
||||
Assert.Equal(3, parser.Parse("А\rБ\nВ\r\n").Count);
|
||||
Assert.Empty(parser.Parse("\r\n\t\r\n"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClipboardRejectsUnclosedQuotesAndConfiguredLimits()
|
||||
{
|
||||
var parser = new ClipboardTableParser(new ExerciseImportOptions { MaxRows = 1, MaxClipboardCharacters = 20 });
|
||||
Assert.Throws<InvalidOperationException>(() => parser.Parse("\"Открытая ячейка"));
|
||||
Assert.Throws<InvalidOperationException>(() => parser.Parse("А\nБ"));
|
||||
Assert.Throws<InvalidOperationException>(() => parser.Parse(new string('a', 21)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationSkipsBlankRowsAndAllowsMissingVideo()
|
||||
{
|
||||
var rows = new ExerciseImportValidator(options).Validate([
|
||||
new(), new() { Name = " Тяга ", VideoUrl = " " },
|
||||
new() { Name = "Жим", VideoUrl = " https://example.com " }]);
|
||||
Assert.Equal(2, rows.Count);
|
||||
Assert.Equal("Тяга", rows[0].Name);
|
||||
Assert.Equal("", rows[0].VideoUrl);
|
||||
Assert.Equal("https://example.com", rows[1].VideoUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationReportsOriginalRowAndRejectsUnsafeLinks()
|
||||
{
|
||||
var validator = new ExerciseImportValidator(options);
|
||||
var error = Assert.Throws<InvalidOperationException>(() => validator.Validate([
|
||||
new(), new() { Name = "Жим", VideoUrl = "javascript:alert(1)" }]));
|
||||
Assert.Contains("Строка 2", error.Message);
|
||||
Assert.Throws<InvalidOperationException>(() => validator.Validate([new() { VideoUrl = "https://example.com" }]));
|
||||
Assert.Throws<InvalidOperationException>(() => validator.Validate([new()]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using TrainingPlanner.Application;
|
||||
using TrainingPlanner.Domain;
|
||||
|
||||
namespace TrainingPlanner.Tests;
|
||||
|
||||
public class ExerciseSearchTests
|
||||
{
|
||||
[Fact]
|
||||
public void ExactAndPrefixMatchesComeBeforeMatchesInsideName()
|
||||
{
|
||||
var exercises = Names("Обратный жим", "Жим гантелей", "Жим", "Приседания");
|
||||
var result = ExerciseSearch.Find(exercises, " ЖИМ ");
|
||||
Assert.Equal(new[] { "Жим", "Жим гантелей", "Обратный жим" }, result.Select(exercise => exercise.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllWordsMustMatchIgnoringTheirOrderAndYoSpelling()
|
||||
{
|
||||
var result = ExerciseSearch.Find(Names("Жим гантелей лёжа", "Жим штанги лёжа", "Жим гантелей стоя"), "ЛЕЖА гант");
|
||||
Assert.Single(result);
|
||||
Assert.Equal("Жим гантелей лёжа", result[0].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyQueryReturnsAllExercisesAndUnknownQueryReturnsNone()
|
||||
{
|
||||
var exercises = Names("Тяга", "Жим");
|
||||
Assert.Equal(2, ExerciseSearch.Find(exercises, " ").Count);
|
||||
Assert.Empty(ExerciseSearch.Find(exercises, "Приседания"));
|
||||
}
|
||||
|
||||
private static List<LibraryExercise> Names(params string[] names)
|
||||
=> names.Select(name => new LibraryExercise { Name = name }).ToList();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using TrainingPlanner.Application;
|
||||
using TrainingPlanner.Domain;
|
||||
|
||||
namespace TrainingPlanner.Tests;
|
||||
|
||||
public class PlanEditingTests
|
||||
{
|
||||
private readonly PlannerOptions options = new()
|
||||
{
|
||||
DefaultWeekCount = 4, DefaultSetsCount = 3, DefaultRestMinutes = 1.5m, DefaultRepetitions = "10"
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void MonthlyCopyPreservesPrescriptionAndCanBeEditedIndependently()
|
||||
{
|
||||
var plan = CreatePlan();
|
||||
var workout = plan.Weeks[0].Workouts[0];
|
||||
workout.Duration = "1 ч 15 мин";
|
||||
var exercise = workout.Blocks[0].Exercises[0];
|
||||
exercise.Id = 17;
|
||||
exercise.Weight = "Подобрать";
|
||||
exercise.Repetitions = "По 10 (1 RIR)";
|
||||
exercise.WarmupSets = "1 — гриф; 2 — 40 кг";
|
||||
exercise.Notes = "Без рывков";
|
||||
exercise.IntensityPercent = 80;
|
||||
exercise.RequiresVideo = true;
|
||||
exercise.VideoUrl = "https://disk.yandex.ru/i/example";
|
||||
var copy = new PlanCopier().Copy(plan, "Октябрь");
|
||||
var copiedWorkout = copy.Weeks[0].Workouts[0];
|
||||
var copied = copiedWorkout.Blocks[0].Exercises[0];
|
||||
Assert.Equal(0, copied.Id);
|
||||
Assert.Equal(workout.Duration, copiedWorkout.Duration);
|
||||
Assert.Equal(exercise.WarmupSets, copied.WarmupSets);
|
||||
Assert.Equal(exercise.Notes, copied.Notes);
|
||||
Assert.Equal(exercise.Weight, copied.Weight);
|
||||
Assert.Equal(exercise.Repetitions, copied.Repetitions);
|
||||
Assert.Equal(exercise.IntensityPercent, copied.IntensityPercent);
|
||||
Assert.Equal(exercise.VideoUrl, copied.VideoUrl);
|
||||
Assert.True(copied.RequiresVideo);
|
||||
copied.Weight = "60";
|
||||
copiedWorkout.Blocks[0].SetsCount = 4;
|
||||
Assert.Equal("Подобрать", exercise.Weight);
|
||||
Assert.Equal(3, workout.Blocks[0].SetsCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RestIncludesLastApproachAndDoesNotMultiplyByRepetitions()
|
||||
{
|
||||
var workout = CreatePlan().Weeks[0].Workouts[0];
|
||||
workout.Blocks[0].RestMinutes = 2;
|
||||
workout.Blocks[0].Exercises[0].Repetitions = "По 10 (1 RIR)";
|
||||
Assert.Equal(6m, WorkoutTotals.RestMinutes(workout));
|
||||
workout.Blocks.Add(new WorkoutBlock { SetsCount = 2, RestMinutes = 1.5m });
|
||||
Assert.Equal(9m, WorkoutTotals.RestMinutes(workout));
|
||||
Assert.Equal(0m, WorkoutTotals.RestMinutes(new Workout()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationAllowsTextWeightAndRejectsSupersets()
|
||||
{
|
||||
var plan = CreatePlan();
|
||||
var block = plan.Weeks[0].Workouts[0].Blocks[0];
|
||||
block.Exercises[0].Weight = "Подобрать / собственный вес";
|
||||
new PlanValidator().Validate(plan);
|
||||
block.Exercises.Add(new WorkoutExercise { Name = "Второе", Repetitions = "10" });
|
||||
Assert.Throws<InvalidOperationException>(() => new PlanValidator().Validate(plan));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 1.5, 80)]
|
||||
[InlineData(3, -1, 80)]
|
||||
[InlineData(3, 2, 101)]
|
||||
[InlineData(3, 2, -1)]
|
||||
public void ValidationRejectsInvalidPrescription(int sets, double rest, int intensity)
|
||||
{
|
||||
var plan = CreatePlan();
|
||||
var block = plan.Weeks[0].Workouts[0].Blocks[0];
|
||||
block.SetsCount = sets;
|
||||
block.RestMinutes = (decimal)rest;
|
||||
block.Exercises[0].IntensityPercent = intensity;
|
||||
Assert.Throws<InvalidOperationException>(() => new PlanValidator().Validate(plan));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovePreservesPrescriptionAndValidationRenumbersBlocks()
|
||||
{
|
||||
var plan = CreatePlan();
|
||||
var workout = plan.Weeks[0].Workouts[0];
|
||||
var original = workout.Blocks[0];
|
||||
original.Exercises[0].WarmupSets = "Гриф";
|
||||
workout.Blocks.Add(new PlanFactory(options).CreateBlock(new LibraryExercise { Name = "Тяга" }));
|
||||
new BlockEditor().Move(workout, 0, 1);
|
||||
new PlanValidator().Validate(plan);
|
||||
Assert.Same(original, workout.Blocks[1]);
|
||||
Assert.Equal(1, original.Position);
|
||||
Assert.Equal("Гриф", original.Exercises[0].WarmupSets);
|
||||
Assert.Throws<InvalidOperationException>(() => PlanValidator.ValidateVideoUrl("javascript:alert(1)"));
|
||||
}
|
||||
|
||||
private TrainingPlan CreatePlan()
|
||||
{
|
||||
var factory = new PlanFactory(options);
|
||||
var plan = factory.Create(1, "Сентябрь");
|
||||
plan.Weeks[0].Workouts.Add(new Workout
|
||||
{
|
||||
Name = "Ноги", Blocks = [factory.CreateBlock(new LibraryExercise { Name = "Приседания" })]
|
||||
});
|
||||
return plan;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\TrainingPlanner.Infrastructure\TrainingPlanner.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user