Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions src/Spectre.Console.Tests/Unit/Prompts/TextPromptTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -489,4 +489,256 @@ public async Task Uses_case_insensitive_comparison_when_no_comparer_is_passed(st
// Then
result.ShouldBe("Yes");
}

[Fact]
public void Validate_BoolOverload_ShortCircuits()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;

prompt
.Validate(s => s.Length >= 3, "too short")
.Validate(s =>
{
secondInvoked = true;
return s.Contains("a");
}, "missing a");

// When
var result = prompt.Validator?.Invoke("ab");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Error("too short"));
secondInvoked.ShouldBeFalse();
}

[Fact]
public void Validate_BoolOverload_Returns_Chained_Validation_Error()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;

prompt
.Validate(s => s.Length >= 3, "too short")
.Validate(s =>
{
secondInvoked = true;
return s.Contains("a");
}, "missing a");

// When
var result = prompt.Validator?.Invoke("bbc");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Error("missing a"));
secondInvoked.ShouldBeTrue();
}

[Fact]
public void Validate_BoolOverload_Returns_Success_When_All_Validators_Pass()
{
// Given
var prompt = new TextPrompt<string>("Enter:");

prompt
.Validate(s => s.Length >= 3, "too short")
.Validate(s => s.Contains("a"), "missing a");

// When
var result = prompt.Validator?.Invoke("abc");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Success());
}

[Fact]
public void Validate_FuncOverload_ShortCircuits()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;

prompt
.Validate(s => s.Length < 3 ? ValidationResult.Error("too short") : ValidationResult.Success())
.Validate(s =>
{
secondInvoked = true;
return s.Contains("a") ? ValidationResult.Success() : ValidationResult.Error("missing a");
} );

// When
var result = prompt.Validator?.Invoke("ab");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Error("too short"));
secondInvoked.ShouldBeFalse();
}

[Fact]
public void Validate_FuncOverload_Returns_Chained_Validation_Error()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;

prompt
.Validate(s => s.Length < 3 ? ValidationResult.Error("too short") : ValidationResult.Success())
.Validate(s =>
{
secondInvoked = true;
return s.Contains("a") ? ValidationResult.Success() : ValidationResult.Error("missing a");
} );

// When
var result = prompt.Validator?.Invoke("bbc");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Error("missing a"));
secondInvoked.ShouldBeTrue();
}

[Fact]
public void Validate_FuncOverload_Returns_Success_When_All_Validators_Pass()
{
// Given
var prompt = new TextPrompt<string>("Enter:");

prompt
.Validate(s => s.Length < 3 ? ValidationResult.Error("too short") : ValidationResult.Success())
.Validate(s => s.Contains("a") ? ValidationResult.Success() : ValidationResult.Error("missing a") );

// When
var result = prompt.Validator?.Invoke("abc");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Success());
}

[Fact]
public void Validate_MixedOverloads_ShortCircuits()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;

prompt
.Validate(s => s.Length >= 3, "too short") .Validate(s =>
Comment thread
AntekOlszewski marked this conversation as resolved.
{
secondInvoked = true;
return s.Contains("a") ? ValidationResult.Success() : ValidationResult.Error("missing a");
} );

// When
var result = prompt.Validator?.Invoke("ab");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Error("too short"));
secondInvoked.ShouldBeFalse();
}

[Fact]
public void Validate_MixedOverloads_Returns_Chained_Validation_Error()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;

prompt
.Validate(s => s.Length >= 3, "too short")
.Validate(s =>
{
secondInvoked = true;
return s.Contains("a") ? ValidationResult.Success() : ValidationResult.Error("missing a");
} );

// When
var result = prompt.Validator?.Invoke("bbc");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Error("missing a"));
secondInvoked.ShouldBeTrue();
}

[Fact]
public void Validate_MixedOverloads_Returns_Success_When_All_Validators_Pass()
{
// Given
var prompt = new TextPrompt<string>("Enter:");

prompt
.Validate(s => s.Length >= 3, "too short")
.Validate(s => s.Contains("a") ? ValidationResult.Success() : ValidationResult.Error("missing a") );

// When
var result = prompt.Validator?.Invoke("abc");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Success());
}

[Fact]
public void Validate_MixedOverloads_WithThreeValidators_Returns_ThirdValidationError()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;
var thirdInvoked = false;

prompt
.Validate(s => s.Length >= 3, "too short")
.Validate(s =>
{
secondInvoked = true;
return s.Contains("a")
? ValidationResult.Success()
: ValidationResult.Error("missing a");
})
.Validate(s =>
{
thirdInvoked = true;
return s.EndsWith("z");
}, "must end with z");

// When
var result = prompt.Validator?.Invoke("abc");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Error("must end with z"));
secondInvoked.ShouldBeTrue();
thirdInvoked.ShouldBeTrue();
}

[Fact]
public void Validate_MixedOverloads_WithThreeValidators_Returns_Success_When_All_Validators_Pass()
{
// Given
var prompt = new TextPrompt<string>("Enter:");
var secondInvoked = false;
var thirdInvoked = false;

prompt
.Validate(s => s.Length >= 3, "too short")
.Validate(s =>
{
secondInvoked = true;
return s.Contains("a")
? ValidationResult.Success()
: ValidationResult.Error("missing a");
})
.Validate(s =>
{
thirdInvoked = true;
return s.EndsWith("z");
}, "must end with z");

// When
var result = prompt.Validator?.Invoke("abz");

// Then
result.ShouldBeEquivalentTo(ValidationResult.Success());
secondInvoked.ShouldBeTrue();
thirdInvoked.ShouldBeTrue();
}
}
28 changes: 27 additions & 1 deletion src/Spectre.Console/Prompts/TextPromptExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,20 @@ public static TextPrompt<T> DefaultValue<T>(this TextPrompt<T> obj, T value)
public static TextPrompt<T> Validate<T>(this TextPrompt<T> obj, Func<T, bool> validator, string? message = null)
{
ArgumentNullException.ThrowIfNull(obj);
ArgumentNullException.ThrowIfNull(validator);

var previous = obj.Validator;
obj.Validator = result =>
{
if (previous is not null)
{
var previousResult = previous(result);
if (!previousResult.Successful)
{
return previousResult;
}
}

if (validator(result))
{
return ValidationResult.Success();
Expand All @@ -202,8 +213,23 @@ public static TextPrompt<T> Validate<T>(this TextPrompt<T> obj, Func<T, bool> va
public static TextPrompt<T> Validate<T>(this TextPrompt<T> obj, Func<T, ValidationResult> validator)
{
ArgumentNullException.ThrowIfNull(obj);
ArgumentNullException.ThrowIfNull(validator);

var previous = obj.Validator;

obj.Validator = result =>
{
if (previous is not null)
{
var previousResult = previous(result);
if (!previousResult.Successful)
{
return previousResult;
}
}

obj.Validator = validator;
return validator(result);
};

return obj;
}
Expand Down