Commit 99a473c0 authored by Tomo Masakura's avatar Tomo Masakura

Merge branch '39-' into 'master'

Resolve "タイトルや説明が長すぎる場合、もしくはタイトルがない場合など、エラーにする" Closes #39 See merge request !13
parents 4506c66d c0748964
Pipeline #14823627 passed with stages
in 14 minutes 59 seconds
......@@ -3,7 +3,7 @@ using Xunit;
namespace AcceptanceTest
{
public class NewIssueTest : UseWebDriver
public sealed class NewIssueTest : UseWebDriver
{
[Fact]
public void ShowNewIssuePage()
......@@ -59,13 +59,17 @@ namespace AcceptanceTest
Assert.Equal("Description3", last.Description);
}
private IssuePage Submit(string title, string description)
private CreationIssuePage GoToCreationPage()
{
var issuePage = Navigator
return Navigator
.GoToIssues()
.ClickNewIssue();
}
return issuePage
private IssuePage Submit(string title, string description)
{
return GoToCreationPage()
.Submit(new {Title = title, Description = description});
}
}
......
using AcceptanceTest.Pages;
using Xunit;
namespace AcceptanceTest
{
public sealed class NewIssueValidationTest : UseWebDriver
{
private readonly CreationIssuePage _target;
public NewIssueValidationTest()
{
_target = Shortcuts.NewIssue();
}
[Fact]
public void EmptyTitleError()
{
_target.Submit(new {Title = string.Empty, Description = "Description1"});
Assert.True(_target.Title.HasError);
Assert.Equal(RequiredError("Title"), _target.Title.Errors);
Assert.True(_target.IsDisabledSubmit);
}
[Fact]
public void LongTitleError()
{
_target.Submit(new {Title = LongText(129), Description = "Description1"});
Assert.True(_target.Title.HasError);
Assert.Equal(MaxLengthError("Title", 128), _target.Title.Errors);
Assert.True(_target.IsDisabledSubmit);
}
[Fact]
public void LongTitleSuccess()
{
var text = LongText(128);
var createdPage = _target.Submit(new {Title = text, Description = "Description1"});
Assert.Equal(text, createdPage.Title);
}
[Fact]
public void LongDescriptionError()
{
_target.Submit(new {Title = "Title1", Description = LongText(5001)});
Assert.True(_target.Description.HasError);
Assert.Equal(MaxLengthError("Description", 5000), _target.Description.Errors);
Assert.True(_target.IsDisabledSubmit);
}
[Fact]
public void LongDescriptionSuccess()
{
var text = LongText(5000);
var createdPage = _target.Submit(new {Title = "Title1", Description = text});
Assert.Equal(text, createdPage.Description);
}
private static string LongText(int size)
{
return string.Join(string.Empty, new int[size]);
}
private static string RequiredError(string name)
{
return $"The {name} field is required.";
}
private static string MaxLengthError(string name, int maximum)
{
return $"The field {name} must be a string with a maximum length of {maximum}.";
}
}
}
\ No newline at end of file
using OpenQA.Selenium;
namespace AcceptanceTest.Pages
{
public sealed class CreateIssuePage : PageBase
{
public CreateIssuePage(IWebDriver driver) : base(driver)
{
}
public IssuePage Submit(dynamic issue)
{
Find(".issue-edit .title").NewInput((string) issue.Title);
Find(".issue-edit .description").NewInput((string) issue.Description);
Find(".issue-edit .submit").Click();
return new IssuePage(Driver);
}
}
}
\ No newline at end of file
using OpenQA.Selenium;
namespace AcceptanceTest.Pages
{
public sealed class CreationIssuePage : PageBase
{
public CreationIssuePage(IWebDriver driver) : base(driver)
{
}
private WebControl TitleElement => FindControl(".issue-edit .title");
private WebControl DescriptionElement => FindControl(".issue-edit .description");
private IWebElement SubmitElement => Find(".issue-edit .submit");
public WebControl Title => TitleElement;
public WebControl Description => DescriptionElement;
public bool IsDisabledSubmit => string.IsNullOrEmpty(SubmitElement.GetCssValue("disabled"));
public IssuePage Submit(dynamic issue)
{
Title.NewInput((string) issue.Title);
Description.NewInput((string) issue.Description);
SubmitElement.Click();
return new IssuePage(Driver);
}
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ namespace AcceptanceTest.Pages
{
}
public string Title => Find(".issue .title").Text;
public string Description => Find(".issue .description").Text;
public WebControl Title => FindControl(".issue .title");
public WebControl Description => FindControl(".issue .description");
}
}
\ No newline at end of file
......@@ -8,11 +8,11 @@ namespace AcceptanceTest.Pages
{
}
public CreateIssuePage ClickNewIssue()
public CreationIssuePage ClickNewIssue()
{
Find(".new-issue").Click();
return new CreateIssuePage(Driver);
return new CreationIssuePage(Driver);
}
}
}
\ No newline at end of file
......@@ -23,5 +23,10 @@ namespace AcceptanceTest.Pages
{
return Driver.FindElements(By.CssSelector(cssSelectorToFind));
}
protected WebControl FindControl(string cssSelectorToFind)
{
return new WebControl(Driver, cssSelectorToFind);
}
}
}
\ No newline at end of file
using OpenQA.Selenium;
using System;
using System.Linq;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.Extensions;
namespace AcceptanceTest.Pages
{
public static class SeleniumExtensions
{
public static void NewInput(this IWebElement element, string text)
public static void NewInput(this IWebElement element, string text, IWebDriver driver = null)
{
if ((text ?? "").Length > 256 && driver != null)
{
element.NewInputSmart(text, driver);
}
else
{
element.NewInputDirect(text);
}
}
public static bool HasClass(this IWebElement element, string @class)
{
return element.GetAttribute("class").Split(" ").Any(c => c == @class);
}
private static void NewInputDirect(this IWebElement element, string text)
{
element.Clear();
element.SendKeys(text);
}
private static void NewInputSmart(this IWebElement element, string text, IWebDriver driver)
{
driver.ExecuteJavaScript("arguments[0].value = arguments[1];", element, text);
}
public static void GoToPage(this IWebDriver driver, string path)
{
var url = new Uri(Environment.GetEnvironmentVariable("TARGET_URL") ?? "http://localhost:5000");
if (!string.IsNullOrWhiteSpace(path))
{
url = new Uri(url, path);
}
driver.Navigate().GoToUrl(url);
}
}
}
\ No newline at end of file
using OpenQA.Selenium;
namespace AcceptanceTest.Pages
{
public sealed class Shortcuts : PageBase
{
public Shortcuts(IWebDriver driver) : base(driver)
{
}
public CreationIssuePage NewIssue()
{
Driver.GoToPage("/Issues/Create");
return new CreationIssuePage(Driver);
}
}
}
\ No newline at end of file
using OpenQA.Selenium;
namespace AcceptanceTest.Pages
{
public sealed class WebControl
{
private readonly IWebDriver _driver;
private readonly By _by;
public WebControl(IWebDriver driver, string cssSelectorToFind) : this(driver, By.CssSelector(cssSelectorToFind))
{
}
private WebControl(IWebDriver driver, By by)
{
_driver = driver;
_by = by;
}
private IWebElement Target => _driver.FindElement(_by);
public void NewInput(string text)
{
Target.NewInput(text, _driver);
}
public bool HasError => Target.HasClass("input-validation-error");
public string Errors
{
get
{
var name = Target.GetAttribute("name");
var error = _driver.FindElement(By.CssSelector($"[data-valmsg-for=\"{name}\"]"));
return error.Text;
}
}
public override string ToString()
{
return Target.Text;
}
public static implicit operator string(WebControl control)
{
return $"{control}";
}
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ namespace AcceptanceTest
private readonly IWebDriver _driver;
protected Navigator Navigator => new Navigator(_driver);
protected Shortcuts Shortcuts => new Shortcuts(_driver);
protected void Refresh()
{
......
using System;
using AcceptanceTest.Pages;
using AcceptanceTest.Selenium;
using OpenQA.Selenium;
......@@ -10,7 +11,8 @@ namespace AcceptanceTest
{
var factory = GetFactory();
var driver = factory.CreateDriver(GetBrowserName());
driver.Navigate().GoToUrl(Environment.GetEnvironmentVariable("TARGET_URL") ?? "http://localhost:5000");
driver.GoToPage("");
// driver.Navigate().GoToUrl(Environment.GetEnvironmentVariable("TARGET_URL") ?? "http://localhost:5000");
return driver;
}
......
......@@ -6,16 +6,33 @@
<div class="issue-edit">
<form method="post">
<div class="form-group">
<label asp-for="Issue.Title"></label>
<input asp-for="Issue.Title" class="title">
<div class="row">
<div class="form-group">
<div class="col-sm-2">
<label asp-for="Issue.Title" class="control-label"></label>
</div>
<div class="col-sm-10">
<input asp-for="Issue.Title" class="title form-control">
<span asp-validation-for="Issue.Title" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<label asp-for="Issue.Description"></label>
<textarea asp-for="Issue.Description" class="description"></textarea>
<div class="row">
<div class="form-group">
<div class="col-sm-2">
<label asp-for="Issue.Description"></label>
</div>
<div class="col-sm-10">
<textarea asp-for="Issue.Description" class="description form-control"></textarea>
<span asp-validation-for="Issue.Description" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<button type="submit" class="submit">Submit</button>
<div class="row">
<div class="btn-group">
<button type="submit" class="submit btn btn-success">Submit</button>
</div>
</div>
</form>
</div>
\ No newline at end of file
......@@ -19,6 +19,8 @@ namespace Issues.WebApp.Pages.Issues
// ReSharper disable once UnusedMember.Global
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
var submittedIssueId = await _registry.SubmitAsync(Issue);
return RedirectToPage("/Issues/Show", new { id = submittedIssueId });
......
......@@ -62,5 +62,8 @@
</environment>
@RenderSection("Scripts", required: false)
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"></script>
</body>
</html>
......@@ -34,6 +34,24 @@ body {
}
}
/* Input Issue */
.issue-edit .form-group {
padding-bottom: 4rem;
}
.issue-edit .form-group .description {
height: 32rem;
}
/* Form errors */
.form-control.input-validation-error {
/* ReSharper disable CssBrowserCompatibility */
/* ReSharper disable RequiresFallbackColor */
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 80, 110, .6);
/* ReSharper restore RequiresFallbackColor */
/* ReSharper restore CssBrowserCompatibility */
}
/* Use by program */
/* ReSharper disable DeclarationIsEmpty */
.navigator {}
......
......@@ -7,4 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\Issues.Contents\Issues.Contents.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.DataAnnotations" Version="2.0.1" />
</ItemGroup>
</Project>
\ No newline at end of file
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Issues.Domains;
namespace Issues
......@@ -6,9 +7,12 @@ namespace Issues
public sealed class IssueContentViewModel
{
[DisplayName("Title")]
[Required]
[StringLength(128)]
public string Title { get; set; }
[DisplayName("Description")]
[StringLength(5000)]
public string Description { get; set; }
public static explicit operator IssueContent(IssueContentViewModel viewModel)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment